From 72ba1a9f588951942bf933fea692dc82596af266 Mon Sep 17 00:00:00 2001 From: CJK_mkp <113243675+CJKmkp@users.noreply.github.com> Date: Sun, 2 Nov 2025 09:29:06 +0800 Subject: [PATCH] =?UTF-8?q?add:Dlass=E8=81=94=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/Helpers/DlassApiClient.cs | 374 +++++++++++++ Ink Canvas/MainWindow.xaml | 11 + Ink Canvas/MainWindow_cs/MW_Settings.cs | 56 ++ Ink Canvas/Resources/Settings.cs | 14 + Ink Canvas/Windows/DlassSettingsWindow.xaml | 388 ++++++++++++++ .../Windows/DlassSettingsWindow.xaml.cs | 490 ++++++++++++++++++ 6 files changed, 1333 insertions(+) create mode 100644 Ink Canvas/Helpers/DlassApiClient.cs create mode 100644 Ink Canvas/Windows/DlassSettingsWindow.xaml create mode 100644 Ink Canvas/Windows/DlassSettingsWindow.xaml.cs diff --git a/Ink Canvas/Helpers/DlassApiClient.cs b/Ink Canvas/Helpers/DlassApiClient.cs new file mode 100644 index 00000000..438c4947 --- /dev/null +++ b/Ink Canvas/Helpers/DlassApiClient.cs @@ -0,0 +1,374 @@ +using Ink_Canvas.Helpers; +using Newtonsoft.Json; +using System; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Ink_Canvas.Helpers +{ + /// + /// Dlass API 客户端,用于与服务端通信 + /// + public class DlassApiClient + { + private const string DEFAULT_BASE_URL = "https://dlass.tech"; + private readonly string _appId; + private readonly string _appSecret; + private readonly string _baseUrl; + private HttpClient _httpClient; + private string _accessToken; + private DateTime _tokenExpiresAt; + + private string _userToken; + + /// + /// 初始化 Dlass API 客户端 + /// + /// 应用ID + /// 应用密钥 + /// API基础URL,如果为空则使用默认URL + /// 用户Token,如果提供则优先使用用户token而不是App Secret + public DlassApiClient(string appId, string appSecret, string baseUrl = null, string userToken = null) + { + _appId = appId ?? throw new ArgumentNullException(nameof(appId)); + _appSecret = appSecret ?? throw new ArgumentNullException(nameof(appSecret)); + _userToken = userToken; + _baseUrl = baseUrl ?? DEFAULT_BASE_URL; + + _baseUrl = _baseUrl.TrimEnd('/'); + if (!_baseUrl.StartsWith("http://") && !_baseUrl.StartsWith("https://")) + { + _baseUrl = "https://" + _baseUrl; + } + + _httpClient = new HttpClient + { + BaseAddress = new Uri(_baseUrl), + Timeout = TimeSpan.FromSeconds(30) + }; + _httpClient.DefaultRequestHeaders.Add("User-Agent", "InkCanvas/1.0"); + } + + /// + /// 获取访问令牌(Access Token) + /// + public async Task GetAccessTokenAsync() + { + if (!string.IsNullOrEmpty(_userToken)) + { + return _userToken; + } + + if (!string.IsNullOrEmpty(_accessToken) && DateTime.Now < _tokenExpiresAt.AddMinutes(-5)) + { + return _accessToken; + } + + try + { + var requestData = new + { + app_id = _appId, + app_secret = _appSecret, + grant_type = "client_credentials" + }; + + var json = JsonConvert.SerializeObject(requestData); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = await _httpClient.PostAsync("/oauth/token", content); + var responseContent = await response.Content.ReadAsStringAsync(); + + if (response.IsSuccessStatusCode) + { + var tokenResponse = JsonConvert.DeserializeObject(responseContent); + _accessToken = tokenResponse.AccessToken; + _tokenExpiresAt = DateTime.Now.AddSeconds(tokenResponse.ExpiresIn ?? 3600); + return _accessToken; + } + else + { + throw new Exception($"获取Access Token失败: {response.StatusCode}"); + } + } + catch (HttpRequestException httpEx) + { + throw new Exception($"获取Access Token时网络错误: {httpEx.Message}", httpEx); + } + catch (TaskCanceledException timeoutEx) + { + throw new Exception("获取Access Token时请求超时", timeoutEx); + } + catch (Exception ex) + { + throw new Exception($"获取Access Token时出错: {ex.Message}", ex); + } + } + + /// + /// 发送GET请求 + /// + public async Task GetAsync(string endpoint, bool requireAuth = true) + { + try + { + string token = null; + if (requireAuth) + { + token = await GetAccessTokenAsync(); + } + + var request = new HttpRequestMessage(HttpMethod.Get, endpoint); + + if (requireAuth && !string.IsNullOrEmpty(token)) + { + if (!string.IsNullOrEmpty(_userToken)) + { + request.Headers.Add("X-User-Token", token); + } + else + { + request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + } + } + + var response = await _httpClient.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + + if (response.IsSuccessStatusCode) + { + if (string.IsNullOrEmpty(content)) + { + return default(T); + } + return JsonConvert.DeserializeObject(content); + } + else + { + throw new Exception($"API请求失败: {response.StatusCode} - {content}"); + } + } + catch (HttpRequestException httpEx) + { + throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx); + } + catch (TaskCanceledException timeoutEx) + { + throw new Exception($"请求超时: {endpoint}", timeoutEx); + } + catch (Exception ex) + { + throw new Exception($"发送请求时出错: {ex.Message}", ex); + } + } + + /// + /// 发送POST请求 + /// + public async Task PostAsync(string endpoint, object data = null, bool requireAuth = true) + { + try + { + string token = null; + if (requireAuth) + { + token = await GetAccessTokenAsync(); + } + + var request = new HttpRequestMessage(HttpMethod.Post, endpoint); + + if (requireAuth && !string.IsNullOrEmpty(token)) + { + if (!string.IsNullOrEmpty(_userToken)) + { + request.Headers.Add("X-User-Token", token); + } + else + { + request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + } + } + + if (data != null) + { + var json = JsonConvert.SerializeObject(data); + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + } + + var response = await _httpClient.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + + if (response.IsSuccessStatusCode) + { + if (string.IsNullOrEmpty(content)) + { + return default(T); + } + return JsonConvert.DeserializeObject(content); + } + else + { + throw new Exception($"API请求失败: {response.StatusCode} - {content}"); + } + } + catch (HttpRequestException httpEx) + { + throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx); + } + catch (TaskCanceledException timeoutEx) + { + throw new Exception($"请求超时: {endpoint}", timeoutEx); + } + catch (Exception ex) + { + throw new Exception($"发送请求时出错: {ex.Message}", ex); + } + } + + /// + /// 发送PUT请求 + /// + public async Task PutAsync(string endpoint, object data = null, bool requireAuth = true) + { + try + { + string token = null; + if (requireAuth) + { + token = await GetAccessTokenAsync(); + } + + var request = new HttpRequestMessage(HttpMethod.Put, endpoint); + + if (requireAuth && !string.IsNullOrEmpty(token)) + { + // 如果是用户token,使用X-User-Token header + if (!string.IsNullOrEmpty(_userToken)) + { + request.Headers.Add("X-User-Token", token); + } + else + { + request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + } + } + + if (data != null) + { + var json = JsonConvert.SerializeObject(data); + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + } + + var response = await _httpClient.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + + if (response.IsSuccessStatusCode) + { + if (string.IsNullOrEmpty(content)) + { + return default(T); + } + return JsonConvert.DeserializeObject(content); + } + else + { + throw new Exception($"API请求失败: {response.StatusCode} - {content}"); + } + } + catch (HttpRequestException httpEx) + { + throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx); + } + catch (TaskCanceledException timeoutEx) + { + throw new Exception($"请求超时: {endpoint}", timeoutEx); + } + catch (Exception ex) + { + throw new Exception($"发送请求时出错: {ex.Message}", ex); + } + } + + /// + /// 发送DELETE请求 + /// + public async Task DeleteAsync(string endpoint, bool requireAuth = true) + { + try + { + string token = null; + if (requireAuth) + { + token = await GetAccessTokenAsync(); + } + + var request = new HttpRequestMessage(HttpMethod.Delete, endpoint); + + if (requireAuth && !string.IsNullOrEmpty(token)) + { + // 如果是用户token,使用X-User-Token header + if (!string.IsNullOrEmpty(_userToken)) + { + request.Headers.Add("X-User-Token", token); + } + else + { + request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + } + } + + var response = await _httpClient.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + return true; + } + else + { + return false; + } + } + catch (HttpRequestException httpEx) + { + return false; + } + catch (TaskCanceledException timeoutEx) + { + return false; + } + catch (Exception ex) + { + return false; + } + } + + /// + /// 释放资源 + /// + public void Dispose() + { + _httpClient?.Dispose(); + } + + #region 内部类 + + /// + /// Token响应模型 + /// + private class TokenResponse + { + [JsonProperty("access_token")] + public string AccessToken { get; set; } + + [JsonProperty("expires_in")] + public int? ExpiresIn { get; set; } + + [JsonProperty("token_type")] + public string TokenType { get; set; } + } + + #endregion + } +} + diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 32a65bc6..beaa21df 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -3185,6 +3185,17 @@ FontSize="14" Margin="8,0,0,0" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ink Canvas/Windows/DlassSettingsWindow.xaml.cs b/Ink Canvas/Windows/DlassSettingsWindow.xaml.cs new file mode 100644 index 00000000..3f80c958 --- /dev/null +++ b/Ink Canvas/Windows/DlassSettingsWindow.xaml.cs @@ -0,0 +1,490 @@ +using Ink_Canvas.Helpers; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox; + +namespace Ink_Canvas.Windows +{ + /// + /// DlassSettingsWindow.xaml 的交互逻辑 + /// + public partial class DlassSettingsWindow : Window + { + private const string APP_ID = "app_WkjocWqsrVY7T6zQV2CfiA"; + private const string APP_SECRET = "o7dx5b5ASGUMcM72PCpmRQYAhSijqaOVHoGyBK0IxbA"; + + private DlassApiClient _apiClient; + + public DlassSettingsWindow(MainWindow mainWindow = null) + { + InitializeComponent(); + + // 加载保存的token + LoadUserToken(); + + // 初始化API客户端(优先使用用户token) + InitializeApiClient(); + + // 窗口关闭时释放资源 + Closed += (s, e) => _apiClient?.Dispose(); + + // 测试连接 + _ = TestConnectionAsync(); + } + + /// + /// 初始化API客户端 + /// + private void InitializeApiClient() + { + var userToken = GetUserToken(); + var apiBaseUrl = MainWindow.Settings?.Dlass?.ApiBaseUrl; + + if (string.IsNullOrEmpty(apiBaseUrl) || apiBaseUrl.Contains("api.dlass.tech")) + { + apiBaseUrl = "https://dlass.tech"; + if (MainWindow.Settings?.Dlass != null) + { + MainWindow.Settings.Dlass.ApiBaseUrl = apiBaseUrl; + MainWindow.SaveSettingsToFile(); + } + } + + if (!string.IsNullOrEmpty(userToken)) + { + _apiClient = new DlassApiClient(APP_ID, APP_SECRET, baseUrl: apiBaseUrl, userToken: userToken); + } + else + { + _apiClient = new DlassApiClient(APP_ID, APP_SECRET, baseUrl: apiBaseUrl); + } + } + + /// + /// 获取用户token + /// + private string GetUserToken() + { + if (MainWindow.Settings?.Dlass != null) + { + return MainWindow.Settings.Dlass.UserToken ?? string.Empty; + } + return string.Empty; + } + + /// + /// 获取保存的Token列表 + /// + private List GetSavedTokens() + { + if (MainWindow.Settings?.Dlass != null) + { + return MainWindow.Settings.Dlass.SavedTokens ?? new List(); + } + return new List(); + } + + /// + /// 加载用户token到UI + /// + private void LoadUserToken() + { + var savedTokens = GetSavedTokens(); + var currentToken = GetUserToken(); + + CmbSavedTokens.Items.Clear(); + if (savedTokens.Count > 0) + { + foreach (var token in savedTokens) + { + CmbSavedTokens.Items.Add(token); + } + if (!string.IsNullOrEmpty(currentToken)) + { + var index = savedTokens.IndexOf(currentToken); + if (index >= 0) + { + CmbSavedTokens.SelectedIndex = index; + } + else + { + CmbSavedTokens.SelectedIndex = 0; + } + } + else if (CmbSavedTokens.Items.Count > 0) + { + CmbSavedTokens.SelectedIndex = 0; + } + } + else + { + CmbSavedTokens.Items.Add("(无保存的Token)"); + CmbSavedTokens.SelectedIndex = 0; + CmbSavedTokens.IsEnabled = false; + } + + TxtNewToken.Text = string.Empty; + + if (!string.IsNullOrEmpty(currentToken)) + { + TxtTokenStatus.Text = "已选择Token"; + TxtTokenStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(34, 197, 94)); + } + else + { + TxtTokenStatus.Text = "未设置Token"; + TxtTokenStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(161, 161, 170)); + } + } + + /// + /// 保存用户token + /// + private void SaveUserToken(string token) + { + if (MainWindow.Settings?.Dlass != null) + { + MainWindow.Settings.Dlass.UserToken = token ?? string.Empty; + MainWindow.SaveSettingsToFile(); + } + } + + /// + /// 添加Token到保存列表 + /// + private void AddTokenToList(string token) + { + if (MainWindow.Settings?.Dlass != null) + { + if (MainWindow.Settings.Dlass.SavedTokens == null) + { + MainWindow.Settings.Dlass.SavedTokens = new List(); + } + + if (!string.IsNullOrEmpty(token) && !MainWindow.Settings.Dlass.SavedTokens.Contains(token)) + { + MainWindow.Settings.Dlass.SavedTokens.Add(token); + MainWindow.SaveSettingsToFile(); + } + } + } + + /// + /// 从列表删除Token + /// + private void RemoveTokenFromList(string token) + { + if (MainWindow.Settings?.Dlass != null && MainWindow.Settings.Dlass.SavedTokens != null) + { + MainWindow.Settings.Dlass.SavedTokens.Remove(token); + MainWindow.SaveSettingsToFile(); + } + } + + /// + /// 标题栏拖动事件 + /// + private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + if (e.ButtonState == MouseButtonState.Pressed) + { + DragMove(); + } + } + + /// + /// 关闭按钮点击事件 + /// + private void BtnClose_Click(object sender, RoutedEventArgs e) + { + Close(); + } + + /// + /// 下拉框选择改变事件 + /// + private void CmbSavedTokens_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) + { + try + { + if (CmbSavedTokens.SelectedItem != null && CmbSavedTokens.SelectedItem.ToString() != "(无保存的Token)") + { + var selectedToken = CmbSavedTokens.SelectedItem.ToString(); + SaveUserToken(selectedToken); + + _apiClient?.Dispose(); + InitializeApiClient(); + + TxtTokenStatus.Text = "已选择Token"; + TxtTokenStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(34, 197, 94)); + + _ = TestConnectionAsync(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"选择Token时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 保存Token按钮点击事件 + /// + private void BtnSaveToken_Click(object sender, RoutedEventArgs e) + { + try + { + var token = TxtNewToken.Text?.Trim() ?? string.Empty; + if (string.IsNullOrEmpty(token)) + { + MessageBox.Show("请输入新的用户Token", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); + return; + } + + AddTokenToList(token); + SaveUserToken(token); + + _apiClient?.Dispose(); + InitializeApiClient(); + + LoadUserToken(); + + MessageBox.Show("Token已成功保存并已选择", "成功", MessageBoxButton.OK, MessageBoxImage.Information); + + _ = TestConnectionAsync(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"保存Token时出错: {ex.Message}", LogHelper.LogType.Error); + MessageBox.Show($"保存Token时发生错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + /// + /// 清除Token按钮点击事件 + /// + private void BtnClearToken_Click(object sender, RoutedEventArgs e) + { + try + { + if (CmbSavedTokens.SelectedItem == null || CmbSavedTokens.SelectedItem.ToString() == "(无保存的Token)") + { + MessageBox.Show("请先选择一个Token", "提示", MessageBoxButton.OK, MessageBoxImage.Warning); + return; + } + + var selectedToken = CmbSavedTokens.SelectedItem.ToString(); + var result = MessageBox.Show($"确定要删除已选中的Token吗?", "确认", MessageBoxButton.YesNo, MessageBoxImage.Question); + if (result == MessageBoxResult.Yes) + { + RemoveTokenFromList(selectedToken); + + if (GetUserToken() == selectedToken) + { + SaveUserToken(string.Empty); + } + + _apiClient?.Dispose(); + InitializeApiClient(); + + LoadUserToken(); + + TxtConnectionStatus.Text = "未连接"; + TxtConnectionStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(161, 161, 170)); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"删除Token时出错: {ex.Message}", LogHelper.LogType.Error); + MessageBox.Show($"删除Token时发生错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + /// + /// 测试Token连接按钮点击事件 + /// + private async void BtnTestToken_Click(object sender, RoutedEventArgs e) + { + await TestConnectionAsync(); + } + + /// + /// 保存按钮点击事件 + /// + private async void BtnSave_Click(object sender, RoutedEventArgs e) + { + try + { + // TODO: 根据实际API文档实现保存逻辑 + // 示例:保存设置到服务器 + // var settings = new { ... }; + // await _apiClient.PostAsync("/api/settings", settings); + + MessageBox.Show("设置已保存", "成功", MessageBoxButton.OK, MessageBoxImage.Information); + Close(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"保存设置时出错: {ex.Message}", LogHelper.LogType.Error); + MessageBox.Show($"保存设置时发生错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + /// + /// 取消按钮点击事件 + /// + private void BtnCancel_Click(object sender, RoutedEventArgs e) + { + Close(); + } + + /// + /// 测试API连接 + /// + private async Task TestConnectionAsync() + { + Dispatcher.Invoke(() => + { + TxtConnectionStatus.Text = "测试中..."; + TxtConnectionStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(161, 161, 170)); // 灰色 + }); + + try + { + var userToken = GetUserToken(); + if (string.IsNullOrEmpty(userToken)) + { + Dispatcher.Invoke(() => + { + TxtConnectionStatus.Text = "未设置Token"; + TxtConnectionStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(239, 68, 68)); // 红色 + }); + return; + } + + // 根据文档,使用 auth-with-token 接口验证token + // 此接口需要POST请求,包含app_id, app_secret和user_token + try + { + var authData = new + { + app_id = APP_ID, + app_secret = APP_SECRET, + user_token = userToken + }; + + var result = await _apiClient.PostAsync("/api/whiteboard/framework/auth-with-token", authData, requireAuth: false); + + if (result != null && result.Success) + { + var whiteboardCount = result.Whiteboards?.Count ?? 0; + + Dispatcher.Invoke(() => + { + TxtConnectionStatus.Text = $"已连接 (找到 {whiteboardCount} 个白板)"; + TxtConnectionStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(34, 197, 94)); + }); + } + else + { + throw new Exception("认证响应失败"); + } + } + catch (Exception ex) + { + if (userToken.Length < 10) + { + throw new Exception("Token格式可能不正确(长度过短,至少需要10个字符)"); + } + + LogHelper.WriteLogToFile($"Token验证失败: {ex.Message}", LogHelper.LogType.Error); + throw; + } + + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"Dlass API连接测试失败: {ex.Message}", LogHelper.LogType.Error); + Dispatcher.Invoke(() => + { + TxtConnectionStatus.Text = "连接失败"; + TxtConnectionStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(239, 68, 68)); // 红色 + }); + } + } + } + + #region API响应模型 + + /// + /// auth-with-token接口响应模型 + /// + public class AuthWithTokenResponse + { + [Newtonsoft.Json.JsonProperty("success")] + public bool Success { get; set; } + + [Newtonsoft.Json.JsonProperty("whiteboards")] + public List Whiteboards { get; set; } + + [Newtonsoft.Json.JsonProperty("count")] + public int Count { get; set; } + + [Newtonsoft.Json.JsonProperty("user")] + public UserInfo User { get; set; } + } + + /// + /// 白板信息模型 + /// + public class WhiteboardInfo + { + [Newtonsoft.Json.JsonProperty("id")] + public int Id { get; set; } + + [Newtonsoft.Json.JsonProperty("name")] + public string Name { get; set; } + + [Newtonsoft.Json.JsonProperty("board_id")] + public string BoardId { get; set; } + + [Newtonsoft.Json.JsonProperty("secret_key")] + public string SecretKey { get; set; } + + [Newtonsoft.Json.JsonProperty("class_name")] + public string ClassName { get; set; } + + [Newtonsoft.Json.JsonProperty("class_id")] + public int ClassId { get; set; } + + [Newtonsoft.Json.JsonProperty("is_online")] + public bool IsOnline { get; set; } + + [Newtonsoft.Json.JsonProperty("last_heartbeat")] + public string LastHeartbeat { get; set; } + + [Newtonsoft.Json.JsonProperty("created_at")] + public string CreatedAt { get; set; } + } + + /// + /// 用户信息模型 + /// + public class UserInfo + { + [Newtonsoft.Json.JsonProperty("id")] + public int Id { get; set; } + + [Newtonsoft.Json.JsonProperty("username")] + public string Username { get; set; } + + [Newtonsoft.Json.JsonProperty("email")] + public string Email { get; set; } + } + + #endregion +} +