继续完善RT登录逻辑。
已在SicManual中测试,以前的登录逻辑没有问题,新的踢出逻辑还未测试。

[UI.Client]
移除未使用的登录控件。
This commit is contained in:
SL 2023-09-13 17:31:22 +08:00
parent 45bb9e7bf7
commit 56bbd2e440
26 changed files with 488 additions and 1152 deletions

View File

@ -152,87 +152,6 @@ namespace Aitex.Core.Account
GetAccountList();
}
public LoginResult Login(string accountId, string accountPwd)
{
try
{
LOG.Write($"用户{accountId}尝试登录系统");
accountId = accountId.ToLower();
var loginResult = new LoginResult();
if (accountId == "su" && accountPwd == "su")
{
loginResult.ActSucc = true;
loginResult.AccountInfo = GetAccountInfo("admin").AccountInfo;
loginResult.Token = Guid.NewGuid().ToString();
}
else if (!FileSigner.IsValid(_accountPath))
{
loginResult.Description = "File signer corrupt";
loginResult.ActSucc = false;
}
else if (_userList.ContainsKey(accountId))
{
var accountInfo = GetAccountInfo(accountId).AccountInfo;
if (accountInfo.Md5Pwd == Md5Helper.GetMd5Hash(accountPwd))
{
loginResult.ActSucc = true;
loginResult.Description = $"{accountId} login succeed";
loginResult.AccountInfo = accountInfo;
loginResult.Token = Guid.NewGuid().ToString();
}
else
{
loginResult.ActSucc = false;
loginResult.Description = $"account {accountId} already login";
}
}
else if (_userList.Count >= 16 && accountId != "admin")
{
loginResult.ActSucc = false;
loginResult.Description = $"more than {16} users login";
}
else
{
var accountInfo2 = GetAccountInfo(accountId).AccountInfo;
if (accountInfo2 == null)
{
loginResult.ActSucc = false;
loginResult.Description = $"{accountId} not exist";
}
else if (accountInfo2.Md5Pwd != Md5Helper.GetMd5Hash(accountPwd) && (accountInfo2.Role != "Admin" || accountPwd != Md5Helper.GenerateDynamicPassword(SerialNumber)))
{
loginResult.ActSucc = false;
loginResult.Description = $"password error";
}
else if (!accountInfo2.AccountStatus)
{
loginResult.ActSucc = false;
loginResult.Description = $"account {accountId} is disabled";
}
else
{
_userList.Add(accountId, new Tuple<Guid, DateTime, string>(NotificationService.ClientGuid, DateTime.Now, NotificationService.ClientHostName));
loginResult.ActSucc = true;
loginResult.Description = $"{accountId} login succeed";
loginResult.AccountInfo = accountInfo2;
loginResult.Token = Guid.NewGuid().ToString();
EV.PostMessage(Module, EventEnum.UserLoggedIn, accountId);
}
}
return loginResult;
}
catch (Exception ex)
{
var text = string.Format("account system inner exception", accountId);
LOG.Write(ex, text);
return new LoginResult
{
ActSucc = false,
Description = text
};
}
}
public void Logout(string accountId)
{
try

View File

@ -16,21 +16,7 @@ namespace Aitex.Core.Account
public sealed class AccountService : IAccountService
{
public string Module => "System";
public LoginResult Login(string accountId, string accountPwd)
{
if (Singleton<KeyManager>.Instance.IsExpired)
{
EV.PostMessage("System", EventEnum.DefaultWarning, "Software is expired. Can not login");
return new LoginResult
{
ActSucc = false,
Description = "Software is expired"
};
}
EV.PostInfoLog(Module, $"User {accountId} try to login System.");
return AccountManager.Instance.Login(accountId, accountPwd);
}
public void Logout(string accountId)
{
@ -246,10 +232,10 @@ namespace Aitex.Core.Account
return AccountExManager.Instance.RoleLoader.DeleteAccount(loginName);
}
public async Task<LoginRequestResults> LoginEx(string userName, string password, string role,
public async Task<LoginResult> LoginEx(string userName, string password, string role,
LoginClientInfo clientInfo)
{
return await AccountExManager.Instance.RequestLogin(userName, password, role, clientInfo);
return await AccountExManager.Instance.Login(userName, password, role, clientInfo);
}
public void CancelLoginRequest(string userName)
@ -257,9 +243,20 @@ namespace Aitex.Core.Account
AccountExManager.Instance.CancelLoginRequest(userName);
}
public void LogoutEx(string sessionId)
public void ConfirmLoginRequest(Credential requestingCred)
{
AccountExManager.Instance.Logout(sessionId);
AccountExManager.Instance.ConfirmedLoginRequest(requestingCred);
}
public void RejectLoginRequest(Credential requestingCred)
{
AccountExManager.Instance.RejectLoginRequest(requestingCred);
}
public void LogoutEx(Guid token)
{
AccountExManager.Instance.Logout(token);
}
/// <summary>
@ -267,7 +264,7 @@ namespace Aitex.Core.Account
/// </summary>
/// <param name="token">客户端登录凭据令牌。</param>
/// <returns></returns>
public CredentialKeepAliveResults KeepAlive(string token)
public CredentialKeepAliveResults KeepAlive(Guid token)
{
return CredentialManager.Instance.KeepAlive(token);
}

View File

@ -1,5 +1,6 @@
using System;
using System.Runtime.Serialization;
using System.Threading;
using MECF.Framework.Common.Account.Extends;
namespace Aitex.Core.Account;
@ -24,7 +25,11 @@ public class Credential
/// </remarks>
public Credential()
{
Token = Guid.Empty;
State = CredentialState.Requesting;
LoginTime = DateTime.MinValue;
LastAliveTime = DateTime.Now;
LoginRequestCancellationTokenSource = new CancellationTokenSource();
}
/// <summary>
@ -32,7 +37,7 @@ public class Credential
/// </summary>
/// <param name="token">登录令牌。</param>
/// <param name="accountInfo">登录账户信息。</param>
public Credential(string token, AccountEx accountInfo)
public Credential(Guid token, AccountEx accountInfo) : this()
{
Token = token;
AccountInfo = accountInfo;
@ -41,12 +46,17 @@ public class Credential
#endregion
#region Properties
/// <summary>
/// 登录请求取消。
/// </summary>
internal CancellationTokenSource LoginRequestCancellationTokenSource { get; }
/// <summary>
/// 设置或返回当前凭据包含的令牌。
/// </summary>
[DataMember]
public string Token { get; set; }
public Guid Token { get; set; }
/// <summary>
/// 设置或返回当前凭据状态
@ -86,10 +96,28 @@ public class Credential
/// </summary>
public string Description { get; set; }
/// <summary>
/// 返回空凭据。
/// </summary>
public static Credential Empty => new Credential();
public static Credential ReadOnlyOne =>
new Credential(Guid.Empty, new AccountEx("", "Read-Only", "", "", "", "", null));
#endregion
#region Methods
/// <summary>
/// 检查指定的凭据是否为空凭据。
/// </summary>
/// <param name="cred"></param>
/// <returns></returns>
public static bool IsEmpty(Credential cred)
{
return cred.Token == Guid.Empty;
}
/// <inheritdoc />
public override string ToString()
{

View File

@ -13,9 +13,20 @@ public class CredentialManager : Singleton<CredentialManager>
{
#region Variables
private const int KEEP_ALIVE_TIMEOUT_SEC = 60;
private readonly object _syncRoot = new();
private readonly Dictionary<string, Credential> _dictLoginCredentials = new ();
/// <summary>
/// 已登录的凭据。
/// </summary>
private readonly Dictionary<Guid, Credential> _dictCredentialsLoggedIn = new ();
/// <summary>
/// 正在等在登录请求确认的凭据。
/// </summary>
private readonly Dictionary<Guid, Credential> _dictCredentialsRequesting = new ();
private readonly PeriodicJob _threadMonitorCred;
private bool _isInitialized;
private int _maxCredentialAllowed;
@ -42,9 +53,18 @@ public class CredentialManager : Singleton<CredentialManager>
public bool IsSupportMultiUserLogin => _maxCredentialAllowed > 1;
/// <summary>
/// 返回已登录的凭据。
/// 返回已登录的用户凭据的数量
/// </summary>
public IReadOnlyDictionary<string, Credential> Credentials => _dictLoginCredentials;
public int LoggedInCount
{
get
{
lock (_syncRoot)
{
return _dictCredentialsLoggedIn.Count;
}
}
}
#endregion
@ -62,23 +82,40 @@ public class CredentialManager : Singleton<CredentialManager>
_isInitialized = true;
_maxCredentialAllowed = isSupportMultiUsersLogin ? int.MaxValue : 1;
}
private bool OnTimer()
{
lock (_syncRoot)
{
foreach (var kvp in _dictLoginCredentials)
var removableList = new List<Guid>();
foreach (var kvp in _dictCredentialsLoggedIn)
{
var cred = kvp.Value;
if ((DateTime.Now - cred.LastAliveTime).TotalSeconds > 60)
{
// 如果当前凭据超过60s未激活一次表示客户端可能已经离线移除
cred.State = CredentialState.Expired;
EV.PostLoginBySameUser(cred);
}
if ((DateTime.Now - cred.LastAliveTime).TotalSeconds > KEEP_ALIVE_TIMEOUT_SEC)
EV.PostLoginBySameUser(cred.Token, new Credential());
}
if (removableList.Count > 0)
{
foreach (var token in removableList)
_dictCredentialsLoggedIn.Remove(token);
}
removableList.Clear();
foreach (var kvp in _dictCredentialsRequesting)
{
var cred = kvp.Value;
if ((DateTime.Now - cred.LastAliveTime).TotalSeconds > KEEP_ALIVE_TIMEOUT_SEC)
removableList.Add(kvp.Key);
}
if (removableList.Count > 0)
{
foreach (var token in removableList)
_dictCredentialsRequesting.Remove(token);
}
}
return true;
}
@ -87,11 +124,11 @@ public class CredentialManager : Singleton<CredentialManager>
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
internal bool CheckHasLoggedIn(string userName)
internal bool IsLoggedIn(string userName)
{
lock (_syncRoot)
{
return _dictLoginCredentials.Values.FirstOrDefault(x => x.AccountInfo.LoginName == userName) != null;
return _dictCredentialsLoggedIn.Values.FirstOrDefault(x => x.AccountInfo.LoginName == userName) != null;
}
}
@ -100,11 +137,24 @@ public class CredentialManager : Singleton<CredentialManager>
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
internal bool CheckHasLoggedInByToken(string token)
internal bool IsLoggedIn(Guid token)
{
lock (_syncRoot)
{
return _dictLoginCredentials.ContainsKey(token);
return _dictCredentialsLoggedIn.ContainsKey(token);
}
}
/// <summary>
/// 检查指定的令牌是否已经过期。
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
internal bool IsTokenExpired(Guid token)
{
lock (_syncRoot)
{
return _dictCredentialsLoggedIn.ContainsKey(token);
}
}
@ -113,16 +163,14 @@ public class CredentialManager : Singleton<CredentialManager>
/// </summary>
/// <param name="token">客户端登录凭据令牌。</param>
/// <returns></returns>
public CredentialKeepAliveResults KeepAlive(string token)
public CredentialKeepAliveResults KeepAlive(Guid token)
{
lock (_syncRoot)
{
if (_dictLoginCredentials.TryGetValue(token, out var cred))
if (_dictCredentialsLoggedIn.TryGetValue(token, out var cred))
{
if (cred.State == CredentialState.Expired)
return CredentialKeepAliveResults.Expired;
cred.LastAliveTime = DateTime.Now; // 刷新时间
return CredentialKeepAliveResults.Alive;
return CredentialKeepAliveResults.Alived;
}
return CredentialKeepAliveResults.NotFound;
@ -130,21 +178,45 @@ public class CredentialManager : Singleton<CredentialManager>
}
/// <summary>
/// 将指定的凭据加入字典
/// 将凭据加入请求列表
/// </summary>
/// <param name="cred"></param>
/// <exception cref="Exception"></exception>
public void Add(Credential cred)
/// <exception cref="InvalidOperationException"></exception>
public void AddRequestingList(Credential cred)
{
if (CheckHasLoggedIn(cred.AccountInfo.LoginName))
lock (_syncRoot)
{
if (_dictCredentialsRequesting.ContainsKey(cred.Token))
throw new InvalidOperationException("the credential has been existed in requesting list.");
_dictCredentialsRequesting[cred.Token] = cred;
}
}
/// <summary>
/// 将指定的凭据加入字典。
/// </summary>
/// <param name="cred">待授权的凭据</param>
/// <remarks>
/// 给凭据授权前必须调用<see cref="AddRequestingList"/>方法将凭据加入到等待列表中。
/// </remarks>
/// <exception cref="Exception"></exception>
public void Grant(Credential cred)
{
if (IsLoggedIn(cred.AccountInfo.LoginName))
throw new Exception($"user {cred.AccountInfo.LoginName} has been logged in.");
lock (_syncRoot)
{
if (_dictLoginCredentials.Count >= _maxCredentialAllowed)
if (_dictCredentialsLoggedIn.Count >= _maxCredentialAllowed)
throw new InvalidOperationException("maximum number of login credentials reached");
_dictLoginCredentials[cred.Token] = cred;
if (!_dictCredentialsRequesting.ContainsKey(cred.Token))
throw new InvalidOperationException("the credential is not found in requesting list.");
cred.State = CredentialState.Alive;
_dictCredentialsLoggedIn[cred.Token] = cred;
_dictCredentialsRequesting.Remove(cred.Token);
}
}
@ -152,11 +224,23 @@ public class CredentialManager : Singleton<CredentialManager>
/// 移除指定令牌的凭据。
/// </summary>
/// <param name="token"></param>
public void Remove(string token)
public void Remove(Guid token)
{
lock (_syncRoot)
{
_dictLoginCredentials.Remove(token);
_dictCredentialsLoggedIn.Remove(token);
}
}
/// <summary>
/// 从登录请求列表中移除指定的凭据。
/// </summary>
/// <param name="cred"></param>
public void RemoveRequesting(Credential cred)
{
lock (_syncRoot)
{
_dictCredentialsRequesting.Remove(cred.Token);
}
}
@ -165,11 +249,50 @@ public class CredentialManager : Singleton<CredentialManager>
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public Credential GetCredentialByUserName(string userName)
public Credential GetCredential(string userName)
{
lock (_syncRoot)
{
return _dictLoginCredentials.Values.FirstOrDefault(x => x.AccountInfo.LoginName == userName);
return _dictCredentialsLoggedIn.Values.FirstOrDefault(x => x.AccountInfo.LoginName == userName);
}
}
/// <summary>
/// 获取指定用户名的登录凭据。
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public Credential GetCredential(Guid token)
{
lock (_syncRoot)
{
return _dictCredentialsLoggedIn.TryGetValue(token, out var cred) ? cred : null;
}
}
/// <summary>
/// 获取指定用户名的正在等在登录请求的凭据。
/// </summary>
/// <param name="userName">用户名</param>
/// <returns></returns>
public Credential GetRequestingCredential(string userName)
{
lock (_syncRoot)
{
return _dictCredentialsRequesting.Values.FirstOrDefault(x => x.AccountInfo.LoginName == userName);
}
}
/// <summary>
/// 获取指定令牌的正在等在登录请求的凭据。
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public Credential GetRequestingCredential(Guid token)
{
lock (_syncRoot)
{
return _dictCredentialsRequesting.TryGetValue(token, out var cred) ? cred : null;
}
}
@ -178,11 +301,11 @@ public class CredentialManager : Singleton<CredentialManager>
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public bool ValidateCredential(string token)
public bool ValidateCredential(Guid token)
{
lock (_syncRoot)
{
if (_dictLoginCredentials.TryGetValue(token, out var cred))
if (_dictCredentialsLoggedIn.TryGetValue(token, out var cred))
{
return cred.State == CredentialState.Alive;
}
@ -199,9 +322,9 @@ public class CredentialManager : Singleton<CredentialManager>
/// 创建一个令牌。
/// </summary>
/// <returns></returns>
public static string GenerateToken()
public static Guid GenerateToken()
{
return Guid.NewGuid().ToString("N");
return Guid.NewGuid();
}
#endregion

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Threading.Tasks;
@ -9,12 +10,12 @@ namespace Aitex.Core.Account
[ServiceKnownType(typeof(CredentialKeepAliveResults))]
[ServiceKnownType(typeof(CredentialState))]
[ServiceKnownType(typeof(LoginRequestResults))]
[ServiceKnownType(typeof(Credential))]
[ServiceKnownType(typeof(AccountEx))]
[ServiceKnownType(typeof(LoginResult))]
[ServiceContract]
public interface IAccountService
{
[OperationContract]
LoginResult Login(string accountId, string password);
[OperationContract]
void Logout(string accountId);
@ -97,17 +98,22 @@ namespace Aitex.Core.Account
bool DeleteAccountEx(string loginName);
[OperationContract]
Task<LoginRequestResults> LoginEx(string userName, string password, string role,
Task<LoginResult> LoginEx(string userName, string password, string role,
LoginClientInfo clientInfo);
[OperationContract]
void CancelLoginRequest(string userName);
[OperationContract]
void LogoutEx(string sessionId);
void ConfirmLoginRequest(Credential requestingCred);
[OperationContract]
CredentialKeepAliveResults KeepAlive(string token);
void RejectLoginRequest(Credential requestingCred);
[OperationContract]
void LogoutEx(Guid token);
[OperationContract]
CredentialKeepAliveResults KeepAlive(Guid token);
}
}

View File

@ -7,51 +7,46 @@ namespace Aitex.Core.Account
[Serializable]
public class LoginResult
{
#region Constructors
public LoginResult()
{
Credential = Credential.Empty;
Result = LoginRequestResults.Error;
}
public LoginResult(LoginRequestResults result) : this(result, Credential.Empty)
{
}
public LoginResult(LoginRequestResults result, Credential cred)
{
Result = result;
Credential = cred;
}
#endregion
#region Properties
/// <summary>
/// 设置或返回是否登录成功。
/// </summary>
public bool ActSucc { get; set; }
/// <summary>
/// 设置或返回登录成功后RT分配的唯一识别码。
/// <remarks>
/// 该唯一识别码用于在RT区分每一个不同的客户端连接。
/// </remarks>
/// </summary>
public string Token { get; set; }
/// <summary>
/// 设置或返回登录的账户信息。
/// </summary>
public Account AccountInfo { get; set; }
public AccountEx AccountEx { get; set; }
/// <summary>
/// 设置或返回登录结果描述。
/// </summary>
public string Description { get; set; }
/// <summary>
/// 设置或返回登录时间。
/// </summary>
public DateTime LoginTime { get; set; }
/// <summary>
/// 设置或返回客户端IP地址。
/// </summary>
public string LoginIP { get; set; }
/// <summary>
/// 设置或返回客户端Port。
/// </summary>
public int LoginPort { get; set; }
public LoginRequestResults Result { get; set; }
/// <summary>
/// 登录凭据。
/// </summary>
public Credential Credential { get; set; }
#endregion
/// <inheritdoc />
public override string ToString()
{
return $"{AccountEx.LoginName} from {LoginIP}:{LoginPort}";
return Result.ToString();
}
}
}

View File

@ -5,29 +5,46 @@ namespace Aitex.Core.Account;
/// </summary>
public enum LoginRequestResults
{
None = -1,
WrongPwd = 1,
HasLogin = 2,
NoMatchRole = 3,
NoSession = 4,
NoMatchUser = 5,
Error,
WrongPwd,
NoMatchRole,
NoMatchUser,
Confirmed,
Rejected,
Timeout,
Canceled
RequstingLogin
}
/// <summary>
/// 凭据状态。
/// </summary>
public enum CredentialState
{
/// <summary>
/// 等待系统校验登录请求。
/// </summary>
Requesting,
/// <summary>
/// 凭据有效。
/// </summary>
Alive,
Expired
/// <summary>
/// 已登录的用户确认了新的登录请求。
/// </summary>
Confirmed,
/// <summary>
/// 已登录的用户拒绝新的登录请求。
/// </summary>
Reject
}
public enum CredentialKeepAliveResults
{
NotFound,
Alive,
Expired
Alived
}
public class Misc

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using Aitex.Core.Account;
using Aitex.Core.Util;
@ -80,11 +81,11 @@ namespace Aitex.Core.RT.Event
}
}
public static void PostLoginBySameUser(Credential credential)
public static void PostLoginBySameUser(Guid token, Credential requestedCredential)
{
if (InnerEventManager != null)
{
InnerEventManager.PostLoginBySameUser(credential);
InnerEventManager.PostLoginBySameUser(token, requestedCredential);
}
}

View File

@ -233,9 +233,10 @@ namespace Aitex.Core.RT.Event
OP.Subscribe("System.Diagnosis.GenLoginBySameUserEvent", (s, args) =>
{
var token = args[0].ToString();
if(CredentialManager.Instance.Credentials.TryGetValue(token, out var cred))
PostLoginBySameUser(cred);
var token = (Guid)args[0];
var cred = CredentialManager.Instance.GetCredential(token);
if(cred != null)
PostLoginBySameUser(cred.Token, new Credential());
else
EV.PostWarningLog("Diagnosis", $"The credential with token {token} does not found.");
return true;
@ -472,14 +473,14 @@ namespace Aitex.Core.RT.Event
_writerToLog.WriteEvent(eventItem);
}
public void PostLoginBySameUser(Credential credential)
public void PostLoginBySameUser(Guid token, Credential requestedCredential)
{
var eventItem = new EventItem
{
Type = EventType.LoginBySameUser_Notify,
Description = credential.Token,
Description = token.ToString(),
OccuringTime = DateTime.Now,
Tag = credential
Tag = requestedCredential
};
_eventQueue.Enqueue(eventItem);
_writerToLog.WriteEvent(eventItem);

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using Aitex.Core.Account;
using Aitex.Core.Util;
@ -22,7 +23,7 @@ namespace Aitex.Core.RT.Event
void PostKickoutMessage(string message);
void PostLoginBySameUser(Credential credential);
void PostLoginBySameUser(Guid token, Credential requestedCredential);
void PostSoundMessage(string message);

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
@ -28,7 +27,7 @@ namespace MECF.Framework.Common.Account
// 旧的_Account.xml文件路径为兼容已有的账户配置。
private readonly string _oldAccountXmlFile = PathManager.GetCfgDir() + "Account//_Account.xml";
private readonly object _syncRoot = new();
private readonly SemaphoreSlim _syncRoot = new SemaphoreSlim(1, 1);
#endregion
@ -36,18 +35,18 @@ namespace MECF.Framework.Common.Account
public AccountExManager()
{
}
#endregion
#region Properties
/// <summary>
/// 返回角色加载器。
/// </summary>
public RoleLoader RoleLoader { get; private set; }
#endregion
#region Methods
@ -81,7 +80,7 @@ namespace MECF.Framework.Common.Account
// 默认不支持多用户登录。
CredentialManager.Instance.Initialize(false);
RoleLoader = new RoleLoader(_scAccountLocalFile);
RoleLoader.Load();
@ -91,7 +90,11 @@ namespace MECF.Framework.Common.Account
}
}
private RemoteEndpointMessageProperty GetCurrentEndPoint()
/// <summary>
/// 获取WCF客户端IP信息。
/// </summary>
/// <returns></returns>
private RemoteEndpointMessageProperty GetCurrentWCFClientEndPoint()
{
var context = OperationContext.Current;
var prop = context.IncomingMessageProperties;
@ -100,166 +103,208 @@ namespace MECF.Framework.Common.Account
return endpoint;
}
private readonly Dictionary<string, CancellationTokenSource> _dictLoginCts = new ();
public async Task<LoginRequestResults> WaitUserLogout(Credential user, Credential newUser)
/// <summary>
/// 等待已登录的用户确认是否允许新的登录请求。
/// </summary>
/// <param name="userLoggedIn">已登录用户的凭据。</param>
/// <param name="userRequesting">新用户的登录凭据。</param>
/// <returns></returns>
private async Task<LoginRequestResults> RequestAndWaitLoginConfirmation(Credential userLoggedIn,
Credential userRequesting)
{
// 如果当前用户名有对应的CancellationTokenSource并且没有触发取消说明某个客户端正在使用当前用户发起登录请求此时
// 不可重复请求登录。
if (_dictLoginCts.TryGetValue(user.AccountInfo.LoginName, out var cts)
&& cts is { IsCancellationRequested: false })
{
// 该用户名正在请求登录,拒绝新的登录请求
EV.PostWarningLog(ModuleName.System.ToString(), "The user has been requesting login, please try again later.");
}
// 用户已登录,需要已登录的客户端确认退出后,再放行凭据
cts = new CancellationTokenSource();
_dictLoginCts.Add(user.AccountInfo.LoginName, cts);
var ct = cts.Token;
// 等待已登录用户响应,返回结果;
// 1. Rejected拒绝新的登录请求
// 2. Logged Out主动注销允许新的登录请求
// 3. Timeout超时没有响应保持原用户的登录凭据
return await Task.Run(async () =>
{
// 请求已登录的客户端确认是否同意注销
EV.PostLoginBySameUser(user);
// 通知已登录的用户,其它客户端使用该用户名登录,是否允许登录
EV.PostLoginBySameUser(userLoggedIn.Token, userRequesting);
var ct = userRequesting.LoginRequestCancellationTokenSource.Token;
// 等待已登录的凭据被移除(客户端注销)
var sw = new Stopwatch();
sw.Start();
while (true)
{
if (!CredentialManager.Instance.CheckHasLoggedInByToken(user.Token))
if (userRequesting.State == CredentialState.Confirmed)
{
// 客户端已注销,可以放行新的凭据
CredentialManager.Instance.Add(newUser);
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedIn, user.AccountInfo.LoginName);
CredentialManager.Instance.Grant(userRequesting);
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedIn,
userLoggedIn.AccountInfo.LoginName);
return await Task.FromResult(LoginRequestResults.Confirmed);
}
if (userRequesting.State == CredentialState.Reject)
{
// 已登录用户拒绝了登录请求
EV.PostInfoLog(ModuleName.System.ToString(),
$"User {userRequesting.AccountInfo.LoginName}'s login request from {userRequesting.LoginIP}:{userRequesting.LoginPort} is rejected");
CredentialManager.Instance.RemoveRequesting(userRequesting);
return await Task.FromResult(LoginRequestResults.Rejected);
}
Thread.Sleep(1000);
ct.ThrowIfCancellationRequested();
if (sw.Elapsed.TotalSeconds > 30)
break;
}
// 等待客户端注销超时
_dictLoginCts.Remove(user.AccountInfo.LoginName);
return LoginRequestResults.Timeout;
}, ct);
});
}
public async Task<LoginRequestResults> RequestLogin(string loginName, string password, string role,
/// <summary>
/// 登录。
/// </summary>
/// <param name="userName">用户名</param>
/// <param name="password">密码</param>
/// <param name="roleID">角色ID</param>
/// <param name="clientInfo">发起登录请求的客户端信息</param>
/// <returns><see cref="LoginRequestResults"/></returns>
/// <exception cref="TimeoutException"></exception>
public async Task<LoginResult> Login(string userName, string password, string roleID,
LoginClientInfo clientInfo)
{
Task<LoginRequestResults> ret;
if (!Monitor.Wait(_syncRoot, 40000))
throw new TimeoutException($"timeout to lock the credential manager.");
var endPoint = GetCurrentEndPoint();
var endPoint = GetCurrentWCFClientEndPoint();
try
{
var accountList = RoleLoader.AccountList;
var matchedAccount = accountList.FirstOrDefault(x => x.LoginName == loginName);
var matchedAccount = accountList.FirstOrDefault(x => x.LoginName == userName);
if (matchedAccount == null)
// 用户不存在
return await Task.FromResult(LoginRequestResults.NoMatchUser);
return await Task.FromResult(new LoginResult(LoginRequestResults.NoMatchUser));
if (matchedAccount.Password != password)
// 密码错误
return await Task.FromResult(LoginRequestResults.WrongPwd);
return await Task.FromResult(new LoginResult(LoginRequestResults.WrongPwd));
// 查找当前用户的登录角色是否存在
var matchedRole = matchedAccount.RoleIDs.FirstOrDefault(x => x == role);
var matchedRole = matchedAccount.RoleIDs.FirstOrDefault(x => x == roleID);
if (matchedRole == null)
// 找不到角色
return await Task.FromResult(LoginRequestResults.NoMatchRole);
return await Task.FromResult(new LoginResult(LoginRequestResults.NoMatchRole));
// 同一用户名不可重复请求登录。
var requestingCred =
CredentialManager.Instance.GetRequestingCredential(userName);
if (requestingCred != null)
{
// 该用户名正在请求登录,拒绝新的登录请求
return await Task.FromResult(new LoginResult(LoginRequestResults.RequstingLogin));
}
// 创建新凭据
var newCred = new Credential(CredentialManager.GenerateToken(), matchedAccount)
{
LoginIP = endPoint.Address,
LoginPort = endPoint.Port,
LoginTime = DateTime.Now
LoginPort = endPoint.Port
};
if(CredentialManager.Instance.Credentials.Count == 0)
// 添加凭据到登录请求列表Grant时需要请求列表中存在此凭据。
CredentialManager.Instance.AddRequestingList(newCred);
if (CredentialManager.Instance.LoggedInCount == 0)
{
// 没有用户登录,直接发放凭据
CredentialManager.Instance.Add(newCred);
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedIn, loginName);
return await Task.FromResult(LoginRequestResults.Confirmed);
// 没有用户登录,直接发放凭据
CredentialManager.Instance.Grant(newCred);
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedIn, userName);
return await Task.FromResult(new LoginResult(LoginRequestResults.Confirmed, newCred));
}
if (CredentialManager.Instance.IsSupportMultiUserLogin)
{
// 查找当前请求的用户是否已经登录
var loggedCred = CredentialManager.Instance.GetCredentialByUserName(loginName);
var loggedCred = CredentialManager.Instance.GetCredential(userName);
// 支持多用户同时登录
if (loggedCred == null)
{
// 用户未登录,直接放行凭据
CredentialManager.Instance.Add(newCred);
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedIn, loginName);
return await Task.FromResult(LoginRequestResults.Confirmed);
CredentialManager.Instance.Grant(newCred);
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedIn, userName);
return await Task.FromResult(new LoginResult(LoginRequestResults.Confirmed, newCred));
}
return await WaitUserLogout(loggedCred, newCred);
var ret = await RequestAndWaitLoginConfirmation(loggedCred, newCred);
return ret == LoginRequestResults.Confirmed ? new LoginResult(ret, newCred) : new LoginResult(ret);
}
else
{
if (CredentialManager.Instance.Credentials.Count > 1)
if (CredentialManager.Instance.LoggedInCount > 1)
{
EV.PostWarningLog(ModuleName.System.ToString(),
$"{nameof(CredentialManager)} does not support multiple users login but more than one credentials found.");
}
var loggedCred = CredentialManager.Instance.Credentials.First().Value;
return await WaitUserLogout(loggedCred, newCred);
var loggedCred = CredentialManager.Instance.GetCredential(userName);
var ret = await RequestAndWaitLoginConfirmation(loggedCred, newCred);
return ret == LoginRequestResults.Confirmed ? new LoginResult(ret, newCred) : new LoginResult(ret);
}
}
catch (Exception ex)
{
LOG.Error(ex.Message, ex);
return await Task.FromResult(LoginRequestResults.None);
return await Task.FromResult(new LoginResult(LoginRequestResults.Error));
}
finally
{
_dictLoginCts.Remove(loginName);
Monitor.Exit(_syncRoot);
}
}
/// <summary>
/// 登录请求发起端取消登录请求。
/// </summary>
/// <param name="userName"></param>
public void CancelLoginRequest(string userName)
{
if (_dictLoginCts.TryGetValue(userName, out var cts))
cts.Cancel();
}
internal void Logout(string token)
{
lock (_syncRoot)
var cred = CredentialManager.Instance.GetRequestingCredential(userName);
if (cred != null)
{
if (CredentialManager.Instance.Credentials.TryGetValue(token, out var cred))
{
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedOff,
cred.AccountInfo.LoginName);
CredentialManager.Instance.Remove(token);
}
cred.LoginRequestCancellationTokenSource.Cancel();
}
}
public void ConfirmedLoginRequest(Credential requestingCred)
{
var cred = CredentialManager.Instance.GetRequestingCredential(requestingCred.Token);
if (cred != null)
cred.State = CredentialState.Confirmed;
}
public void RejectLoginRequest(Credential requestingCred)
{
var cred = CredentialManager.Instance.GetRequestingCredential(requestingCred.Token);
if (cred != null)
cred.State = CredentialState.Reject;
}
internal void Logout(Guid token)
{
var cred = CredentialManager.Instance.GetCredential(token);
if (cred != null)
{
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedOff,
cred.AccountInfo.LoginName);
}
CredentialManager.Instance.Remove(token);
}
#endregion
}
}

View File

@ -22,6 +22,11 @@ namespace MECF.Framework.Common.Account.Extends
#region Constructors
public AccountEx()
{
}
public AccountEx(string id, string loginName, string password, string firstName, string lastName, string email,
List<string> rolesBelongTo, string description = "")
{
@ -116,7 +121,7 @@ namespace MECF.Framework.Common.Account.Extends
get => _mStrRoles;
set => _mStrRoles = value;
}
#endregion
}
}

View File

@ -212,11 +212,15 @@ namespace MECF.Framework.Common.Account.Extends
var item3 = new AccountEx(strUserId, strLoginName, strPassword, strFirstName, strLastName, strEmail, lstRolesBelongTo, strDescription);
lstUsers.Add(item3);
}
var item4 = new AccountEx("-1", "admin", "admin", "", "", "", new List<string> { "-1" })
var admin = new AccountEx("-1", "admin", "admin", "", "", "", new List<string> { "-1" })
{
IsSuper = true
};
lstUsers.Add(item4);
lstUsers.Add(admin);
var readOnly = new AccountEx("-2", "Read-Only", "read-only", "", "", "", new List<string> { "-1" });
lstUsers.Add(readOnly);
_accountList = lstUsers;
}

View File

@ -1,4 +1,5 @@
using System;
using Aitex.Core.Account;
using MECF.Framework.Common.CommonData;
namespace MECF.Framework.Common.Account.Extends
@ -6,39 +7,54 @@ namespace MECF.Framework.Common.Account.Extends
[Serializable]
public class UserContext : NotifiableItem
{
#region Variables
private Credential _cred;
private string _loginName;
public string LoginName
#endregion
#region Properties
public Credential Credential
{
get => _loginName;
get => _cred;
set
{
_loginName = value;
InvokePropertyChanged();
_cred = value;
InvokePropertyChanged(nameof(Token));
InvokePropertyChanged(nameof(LoginName));
InvokePropertyChanged(nameof(LoginTime));
}
}
public Guid Token => _cred.Token;
public string LoginName => _cred?.AccountInfo?.LoginName ?? "";
public DateTime LoginTime => _cred?.LoginTime ?? DateTime.MinValue;
public Role Role { get; set; }
public DateTime LoginTime { get; set; }
public DateTime LastAccessTime { get; set; }
public string Token { get; set; }
public string Language { get; private set; }
public bool IsLogin { get; set; }
public void Clear()
{
LoginName = string.Empty;
Role = null;
LoginTime = DateTime.MinValue;
LastAccessTime = DateTime.MinValue;
Token = string.Empty;
Language = string.Empty;
IsLogin = false;
}
#endregion
#region Methods
public void Clear()
{
_cred = Credential.Empty;
Role = null;
LastAccessTime = DateTime.MinValue;
Language = string.Empty;
IsLogin = false;
}
#endregion
}
}

View File

@ -1,5 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Aitex.Core.Account;
using Aitex.Core.Util;
@ -13,17 +13,7 @@ namespace MECF.Framework.UI.Core.Accounts
public AccountServiceClient()
{
}
public LoginResult Login(string accountId, string accountPwd)
{
LoginResult result = null;
WCFProxy.Using(delegate(IAccountService svc)
{
result = svc.Login(accountId, accountPwd);
});
return result;
}
public void Logout(string accountId)
{
WCFProxy.Using(delegate(IAccountService svc)
@ -288,10 +278,10 @@ namespace MECF.Framework.UI.Core.Accounts
return result;
}
public async Task<LoginRequestResults> LoginEx(string userName, string password, string role,
public async Task<LoginResult> LoginEx(string userName, string password, string role,
LoginClientInfo clientInfo)
{
LoginRequestResults result = default;
LoginResult result = default;
await WCFProxy.AsyncUsing(async delegate(IAccountService svc)
{
result = await svc.LoginEx(userName, password, role, clientInfo);
@ -308,7 +298,23 @@ namespace MECF.Framework.UI.Core.Accounts
});
}
public void LogoutEx(string token)
public void ConfirmLoginRequest(Credential requestingCred)
{
WCFProxy.Using(delegate(IAccountService svc)
{
svc.ConfirmLoginRequest(requestingCred);
});
}
public void RejectLoginRequest(Credential requestingCred)
{
WCFProxy.Using(delegate(IAccountService svc)
{
svc.RejectLoginRequest(requestingCred);
});
}
public void LogoutEx(Guid token)
{
WCFProxy.Using(delegate(IAccountService svc)
{
@ -316,7 +322,7 @@ namespace MECF.Framework.UI.Core.Accounts
});
}
public CredentialKeepAliveResults KeepAlive(string token)
public CredentialKeepAliveResults KeepAlive(Guid token)
{
var result = CredentialKeepAliveResults.NotFound;
WCFProxy.Using(delegate(IAccountService svc)

View File

@ -1,80 +0,0 @@
<Window x:Class="MECF.Framework.UI.Core.Accounts.GonaMainLogin"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:aero="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
xmlns:tb="http://www.hardcodet.net/taskbar"
xmlns:validationRules2="clr-namespace:Aitex.Core.UI.ValidationRules"
WindowState="Maximized"
WindowStyle="SingleBorderWindow"
Background="{DynamicResource Login_BG}"
ShowInTaskbar="True"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<Style TargetType="Label">
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontFamily" Value="Arial,SimSun" />
<Setter Property="Foreground" Value="Black"></Setter>
</Style>
</Window.Resources>
<Border BorderBrush="DarkBlue" BorderThickness="0,0,1,1">
<Border BorderBrush="LightBlue" BorderThickness="1,1,0,0">
<Grid Width="450" Height="350">
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid.Effect>
<DropShadowEffect BlurRadius="10" ShadowDepth="0"/>
</Grid.Effect>
<Path Data="M23.5,0.5 L426.5,0.5 449.5,23.5 449.5,243.2 437.5,257.2 437.5,294.5 406.5,324.5 43.5,324.5 12.5,294.5 12.5,257.2 0.5,243.2 0.5,23.5 z" HorizontalAlignment="Left" Margin="0,22,0,0" Grid.RowSpan="3" Stretch="Fill" Width="450" StrokeThickness="2" Fill="{DynamicResource Login_MainOuter_BG}" Stroke="{DynamicResource Login_MainOuter_BD}"/>
<Path Data="M21.44508,0.5 L429.58238,0.5 449.5,21.436306 449.5,243.2 437.5,257.2 437.5,294.5 406.5,324.5 43.5,324.5 12.5,294.5 12.5,257.2 0.5,243.2 0.5,22.468153 z" Margin="6,-21.5,6,6.5" Grid.RowSpan="2" Stretch="Fill" Grid.Row="1" StrokeThickness="2" Fill="{DynamicResource Login_MainInner_BG}" Stroke="{DynamicResource Login_MainInner_BD}"/>
<Grid Width="350">
<Path Data="M25.5,0.5 L324.5,0.5 349.5,25 324.5,49.5 25.5,49.5 0.5,25 z" Stretch="Fill" StrokeThickness="2" Fill="{DynamicResource Login_TopOuter_BG}" Stroke="{DynamicResource Login_TopOuter_BD}"/>
<Path Data="M21.5,0.5 L328.5,0.5 349.5,25 328.5,49.5 21.5,49.5 0.5,25 z" Stretch="Fill" Margin="8,5" Fill="{DynamicResource Login_TopInner_BG}" Stroke="{DynamicResource Login_TopInner_BD}"/>
<TextBlock TextWrapping="Wrap" Text="User Login" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White" FontFamily="Arial" FontSize="24" FontWeight="Bold">
<TextBlock.Effect>
<DropShadowEffect BlurRadius="0" ShadowDepth="2"/>
</TextBlock.Effect>
</TextBlock>
</Grid>
<Grid Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="130"/>
<ColumnDefinition Width="220"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="60"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Name="labelUserName" Content="{DynamicResource GlobalLableUserName}" Foreground="{DynamicResource FG_White}" VerticalAlignment="Center" HorizontalAlignment="Right">
<Label.Effect>
<DropShadowEffect BlurRadius="0" ShadowDepth="2"/>
</Label.Effect>
</Label>
<TextBox Grid.Row="0" Grid.Column="1" Name="textBoxUserName" Style="{DynamicResource LoginTextBox}" VerticalAlignment="Center" Height="40">
<Binding Path="UserName" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<validationRules2:ValidateUserName />
</Binding.ValidationRules>
</Binding>
</TextBox>
<Label Grid.Row="1" Grid.Column="0" Name="label2" Foreground="{DynamicResource FG_White}" Content="{DynamicResource GlobalLableUserPassword}" VerticalAlignment="Center" HorizontalAlignment="Right">
<Label.Effect>
<DropShadowEffect BlurRadius="0" ShadowDepth="2"/>
</Label.Effect>
</Label>
<PasswordBox Grid.Row="1" Grid.Column="1" Name="passwordBox" Style="{DynamicResource LoginPasswordBox}" VerticalAlignment="Center" Height="40">
</PasswordBox>
<Label Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Content="" Name="LabelResult" Foreground="{DynamicResource FG_White}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<StackPanel Orientation="Horizontal" Grid.Row="3" Grid.ColumnSpan="2" HorizontalAlignment="Center" VerticalAlignment="Bottom">
<Button Style="{DynamicResource LoginButton}" Click="OnLoginClicked" FontSize="18" Height="40" Width="120" FontWeight="Bold" Content="{DynamicResource GlobalLableButtonLogin}" IsDefault="True"/>
<Button Style="{DynamicResource LoginButton}" FontSize="20" Margin="10,0,0,0" Click="OnExitClicked" Height="40" Width="120" FontWeight="Bold" Content="{DynamicResource GlobalLableButtonExit}"/>
</StackPanel>
</Grid>
</Grid>
</Border>
</Border>
</Window>

View File

@ -1,71 +0,0 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Aitex.Core.Account;
using MECF.Framework.UI.Core.Applications;
namespace MECF.Framework.UI.Core.Accounts
{
/// <summary>
/// Interaction logic for MainLogin.xaml
/// </summary>
public partial class GonaMainLogin : Window
{
LoginViewModel viewModel;
public GonaMainLogin()
{
InitializeComponent();
viewModel = new LoginViewModel();
DataContext = viewModel;
Loaded += OnLoginViewLoaded;
}
private void OnLoginViewLoaded(object sender, RoutedEventArgs e)
{
FocusManager.SetFocusedElement(this, textBoxUserName);
}
private void OnLoginClicked(object sender, RoutedEventArgs e)
{
string userName = textBoxUserName.Text;
IntPtr p = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(passwordBox.SecurePassword);
string passWord = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(p);
viewModel.UserName = userName;
viewModel.Password = passWord;
LoginResult loginResult = AccountClient.Instance.Service.Login(userName, passWord);
LabelResult.Content = loginResult.Description;
if (loginResult.ActSucc)
{
viewModel.SetLoginResult(loginResult);
this.DialogResult = true;
}
else
{
if (loginResult.Description.Contains("already login") && System.Windows.Forms.MessageBox.Show
(string.Format(loginResult.Description + "\r\n force logoff?"), UiApplication.Instance.Current.SystemName,
System.Windows.Forms.MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes)
{
AccountClient.Instance.Service.KickUserOut(userName, string.Format("account {0} login from other place", userName));
}
else
{
string message = loginResult.Description;
MessageBox.Show(message, UiApplication.Instance.Current.SystemName, MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
}
private void OnExitClicked(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
}
}

View File

@ -1,107 +0,0 @@
using System;
using Aitex.Core.Account;
using Aitex.Core.Util;
namespace MECF.Framework.UI.Core.Accounts
{
public class LoginViewModel
{
private string _userName = "";
private string _password = "";
public string UserName
{
get
{
return _userName;
}
set
{
_userName = value;
//ClearError();
//InvokePropertyChanged("UserName");
}
}
public string Password
{
get
{
return _password;
}
set
{
_password = value;
//ClearError();
//InvokePropertyChanged("Password");
}
}
public Account AccountInfo
{
get;
set;
}
public String Token
{
get;
set;
}
public string Role
{
get;
set;
}
public void SetLoginResult(LoginResult result)
{
AccountClient.Instance.CurrentUser = result.AccountInfo;
AccountInfo = result.AccountInfo;
Token = result.Token;
Role = AccountInfo.Role;
}
public LoginViewModel()
{
UserName = string.Empty;
Password = string.Empty;
AccountInfo = new Account();
//m_password1Property = new PassableProperty<string>(
// () => Password,
// (val) => { Password = val; }
// );
}
//private string m_error = "";
//public string Error
//{
// get
// {
// return m_error;
// }
// set
// {
// m_error = value;
// InvokePropertyChanged("Error");
// }
//}
//private readonly PassableProperty<string> m_password1Property;
//public PassableProperty<string> Password1Property
//{
// get { return m_password1Property; }
//}
//private void ClearError()
//{
// Error = string.Empty;
//}
}
}

View File

@ -1,175 +0,0 @@
<Window x:Class="MECF.Framework.UI.Core.Accounts.MainLogin"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:aero="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
xmlns:tb="http://www.hardcodet.net/taskbar"
xmlns:validationRules2="clr-namespace:Aitex.Core.UI.ValidationRules"
Width="400" Height="350"
WindowStyle="None"
Background="{StaticResource loginBackgroundImg}"
ShowInTaskbar="True"
WindowStartupLocation="CenterScreen"
WindowState="Normal" ResizeMode="NoResize">
<Window.Resources>
<Style TargetType="{x:Type PasswordBox}">
<Setter Property="FontSize" Value="20"></Setter>
<Setter Property="Padding" Value="1,5,0,0"></Setter>
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/输入框.png">
</ImageBrush>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Label">
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontFamily" Value="Arial,SimSun" />
<Setter Property="Foreground" Value="Black"></Setter>
</Style>
<Style TargetType="TextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBoxBase">
<aero:ListBoxChrome Background="{TemplateBinding Control.Background}" BorderBrush="{TemplateBinding Control.BorderBrush}" BorderThickness="{TemplateBinding Control.BorderThickness}" Name="Bd" RenderFocused="{TemplateBinding UIElement.IsKeyboardFocusWithin}" RenderMouseOver="{TemplateBinding UIElement.IsMouseOver}" SnapsToDevicePixels="True">
<ScrollViewer Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</aero:ListBoxChrome>
<ControlTemplate.Triggers>
<Trigger Property="UIElement.IsEnabled" Value="False">
<Setter Property="aero:ListBoxChrome.Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
<Setter Property="Control.Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="FontSize" Value="20"></Setter>
<Setter Property="Padding" Value="1,5,0,0"></Setter>
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/输入框.png">
</ImageBrush>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/输入框_选择.png">
</ImageBrush>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="Button">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/按钮.png"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/按钮_悬停.png"></ImageBrush>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsFocused" Value="true">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/按钮_点击.png"></ImageBrush>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<!--登录窗口样式-->
<Style TargetType="Button" x:Key="LoginButton">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="BorderBrush" Value="Blue" />
<Setter Property="Margin" Value="5"/>
<Setter Property="FontFamily" Value="Arial,SimSun" />
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Height" Value="40"></Setter>
<Setter Property="Width" Value="116"></Setter>
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/按钮.png" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ButtonBase">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<ContentPresenter Content="{TemplateBinding ContentControl.Content}" ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" Margin="{TemplateBinding Control.Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" >
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/按钮_悬停.png">
</ImageBrush>
</Setter.Value>
</Setter>
</Trigger>
<!-- When the mouse is pressed, apply a bevel with a narrower BevelWidth to make the button appear to get pressed. -->
<Trigger Property="IsFocused" Value="true">
<Setter Property="Background" >
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/按钮_点击.png">
</ImageBrush>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Canvas x:Name="canvas" Width="390" Height="340" >
<Label Height="40" Name="labelUserName" Content="{DynamicResource GlobalLableUserName}" Width="230" Foreground="White" Canvas.Left="90" Canvas.Top="40" RenderTransformOrigin="0.648,0.75"/>
<TextBox Height="40" Name="textBoxUserName" Width="190" FontSize="20" Canvas.Left="95" Canvas.Top="80">
<Binding Path="UserName" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<validationRules2:ValidateUserName />
</Binding.ValidationRules>
</Binding>
</TextBox>
<Label Canvas.Left="95" Canvas.Top="124" Height="40" Name="label2" Width="240" Foreground="White" Content="{DynamicResource GlobalLableUserPassword}" />
<PasswordBox Canvas.Left="95" Canvas.Top="169" Height="40" Name="passwordBox" Width="190" >
<PasswordBox.Template>
<ControlTemplate TargetType="PasswordBox" >
<aero:ListBoxChrome Background="{TemplateBinding Control.Background}" BorderBrush="{TemplateBinding Control.BorderBrush}" BorderThickness="{TemplateBinding Control.BorderThickness}" Name="Bd" RenderFocused="{TemplateBinding UIElement.IsKeyboardFocusWithin}" RenderMouseOver="{TemplateBinding UIElement.IsMouseOver}" SnapsToDevicePixels="True">
<ScrollViewer Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</aero:ListBoxChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Background" >
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/输入框_选择.png">
</ImageBrush>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</PasswordBox.Template>
</PasswordBox>
<Button Click="OnLoginClicked" FontSize="18" Style="{StaticResource LoginButton}" Canvas.Left="49" Canvas.Top="290" Height="40" Width="116" FontWeight="Bold" Content="{DynamicResource GlobalLableButtonLogin}" IsDefault="True"/>
<Button FontSize="20" Style="{StaticResource LoginButton}" Canvas.Left="233" Canvas.Top="290" Click="OnExitClicked" Height="40" Width="116" FontWeight="Bold" Content="{DynamicResource GlobalLableButtonExit}"/>
<Label Canvas.Left="135" Height="40" Width="160" Content="{DynamicResource GlobalLableUserLogin}" />
<Label Canvas.Left="28" Canvas.Top="224" Content="" Height="40" Name="LabelResult" Width="307" Foreground="White" />
</Canvas>
</Grid>
</Window>

View File

@ -1,71 +0,0 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Aitex.Core.Account;
using MECF.Framework.UI.Core.Applications;
namespace MECF.Framework.UI.Core.Accounts
{
/// <summary>
/// Interaction logic for MainLogin.xaml
/// </summary>
public partial class MainLogin : Window
{
LoginViewModel viewModel;
public MainLogin()
{
InitializeComponent();
viewModel = new LoginViewModel();
DataContext = viewModel;
Loaded += OnLoginViewLoaded;
}
private void OnLoginViewLoaded(object sender, RoutedEventArgs e)
{
FocusManager.SetFocusedElement(this, textBoxUserName);
}
private void OnLoginClicked(object sender, RoutedEventArgs e)
{
string userName = textBoxUserName.Text;
IntPtr p = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(passwordBox.SecurePassword);
string passWord = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(p);
viewModel.UserName = userName;
viewModel.Password = passWord;
LoginResult loginResult = AccountClient.Instance.Service.Login(userName, passWord);
LabelResult.Content = loginResult.Description;
if (loginResult.ActSucc)
{
viewModel.SetLoginResult(loginResult);
this.DialogResult = true;
}
else
{
if (loginResult.Description.Contains("already login") && System.Windows.Forms.MessageBox.Show
(string.Format(loginResult.Description + "\r\n force logoff?"), UiApplication.Instance.Current.SystemName,
System.Windows.Forms.MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes)
{
AccountClient.Instance.Service.KickUserOut(userName, string.Format("account {0} login from other place", userName));
}
else
{
string message = loginResult.Description;
MessageBox.Show(message, UiApplication.Instance.Current.SystemName, MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
}
private void OnExitClicked(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
}
}

View File

@ -1,167 +0,0 @@
<Window x:Class="MECF.Framework.UI.Core.Accounts.PasswordMsgBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:aero="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
xmlns:tb="http://www.hardcodet.net/taskbar"
xmlns:validationRules2="clr-namespace:Aitex.Core.UI.ValidationRules"
Width="400" Height="350"
WindowStyle="None"
Background="{StaticResource loginBackgroundImg}"
ShowInTaskbar="True"
WindowStartupLocation="CenterScreen"
WindowState="Normal" ResizeMode="NoResize">
<Window.Resources>
<Style TargetType="{x:Type PasswordBox}">
<Setter Property="FontSize" Value="20"></Setter>
<Setter Property="Padding" Value="1,5,0,0"></Setter>
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/输入框.png">
</ImageBrush>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Label">
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontFamily" Value="Arial,SimSun" />
<Setter Property="Foreground" Value="Black"></Setter>
</Style>
<Style TargetType="TextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBoxBase">
<aero:ListBoxChrome Background="{TemplateBinding Control.Background}" BorderBrush="{TemplateBinding Control.BorderBrush}" BorderThickness="{TemplateBinding Control.BorderThickness}" Name="Bd" RenderFocused="{TemplateBinding UIElement.IsKeyboardFocusWithin}" RenderMouseOver="{TemplateBinding UIElement.IsMouseOver}" SnapsToDevicePixels="True">
<ScrollViewer Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</aero:ListBoxChrome>
<ControlTemplate.Triggers>
<Trigger Property="UIElement.IsEnabled" Value="False">
<Setter Property="aero:ListBoxChrome.Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
<Setter Property="Control.Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="FontSize" Value="20"></Setter>
<Setter Property="Padding" Value="1,5,0,0"></Setter>
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/输入框.png">
</ImageBrush>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/输入框_选择.png">
</ImageBrush>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="Button">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/按钮.png"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/按钮_悬停.png"></ImageBrush>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsFocused" Value="true">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/按钮_点击.png"></ImageBrush>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<!--登录窗口样式-->
<Style TargetType="Button" x:Key="LoginButton">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="BorderBrush" Value="Blue" />
<Setter Property="Margin" Value="5"/>
<Setter Property="FontFamily" Value="Arial,SimSun" />
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Height" Value="40"></Setter>
<Setter Property="Width" Value="116"></Setter>
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/按钮.png" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ButtonBase">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<ContentPresenter Content="{TemplateBinding ContentControl.Content}" ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" Margin="{TemplateBinding Control.Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" >
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/按钮_悬停.png">
</ImageBrush>
</Setter.Value>
</Setter>
</Trigger>
<!-- When the mouse is pressed, apply a bevel with a narrower BevelWidth to make the button appear to get pressed. -->
<Trigger Property="IsFocused" Value="true">
<Setter Property="Background" >
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/按钮_点击.png">
</ImageBrush>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Canvas x:Name="canvas" Width="390" Height="340" >
<Label Canvas.Left="78" Canvas.Top="88" Height="40" Name="label2" Width="240" Foreground="White" Content="{DynamicResource GlobalLableUserPassword}" />
<PasswordBox Canvas.Left="78" Canvas.Top="144" Height="40" Name="passwordBox" Width="190" >
<PasswordBox.Template>
<ControlTemplate TargetType="PasswordBox" >
<aero:ListBoxChrome Background="{TemplateBinding Control.Background}" BorderBrush="{TemplateBinding Control.BorderBrush}" BorderThickness="{TemplateBinding Control.BorderThickness}" Name="Bd" RenderFocused="{TemplateBinding UIElement.IsKeyboardFocusWithin}" RenderMouseOver="{TemplateBinding UIElement.IsMouseOver}" SnapsToDevicePixels="True">
<ScrollViewer Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</aero:ListBoxChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Background" >
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/Login/输入框_选择.png">
</ImageBrush>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</PasswordBox.Template>
</PasswordBox>
<Button Click="OnLoginClicked" FontSize="18" Style="{StaticResource LoginButton}" Canvas.Left="49" Canvas.Top="290" Height="40" Width="116" FontWeight="Bold" Content="{DynamicResource GlobalLableButtonLogin}" IsDefault="True"/>
<Button FontSize="20" Style="{StaticResource LoginButton}" Canvas.Left="233" Canvas.Top="290" Click="OnExitClicked" Height="40" Width="116" FontWeight="Bold" Content="{DynamicResource GlobalLableButtonExit}"/>
<Label Canvas.Left="135" Height="40" Width="160" Content="{DynamicResource GlobalLableUserLogin}" />
<Label Canvas.Left="28" Canvas.Top="224" Content="" Height="40" Name="LabelResult" Width="307" Foreground="White" />
</Canvas>
</Grid>
</Window>

View File

@ -1,57 +0,0 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Aitex.Core.Account;
using MECF.Framework.UI.Core.Applications;
namespace MECF.Framework.UI.Core.Accounts
{
/// <summary>
/// Interaction logic for MainLogin.xaml
/// </summary>
public partial class PasswordMsgBox : Window
{
PasswordMsgBoxModel viewModel;
public PasswordMsgBox()
{
InitializeComponent();
viewModel = new PasswordMsgBoxModel();
DataContext = viewModel;
}
private string userName = "admin";
private void OnLoginClicked(object sender, RoutedEventArgs e)
{
IntPtr p = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(passwordBox.SecurePassword);
string passWord = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(p);
viewModel.UserName = userName;
viewModel.Password = passWord;
LoginResult loginResult = AccountClient.Instance.Service.Login(userName, passWord);
LabelResult.Content = loginResult.Description;
if (loginResult.ActSucc)
{
this.DialogResult = true;
this.Close();
}
else
{
this.passwordBox.Focus();
this.LabelResult.Content = "error in your password. Please re-enter";
}
}
private void OnExitClicked(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
this.Close();
}
}
}

View File

@ -1,76 +0,0 @@
using Aitex.Core.Account;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MECF.Framework.UI.Core.Accounts
{
public class PasswordMsgBoxModel
{
private string _userName = "";
private string _password = "";
public string UserName
{
get
{
return _userName;
}
set
{
_userName = value;
}
}
public string Password
{
get
{
return _password;
}
set
{
_password = value;
}
}
public Account AccountInfo
{
get;
set;
}
public string Token
{
get;
set;
}
public string Role
{
get;
set;
}
public void SetLoginResult(LoginResult result)
{
AccountClient.Instance.CurrentUser = result.AccountInfo;
AccountInfo = result.AccountInfo;
Token = result.Token;
Role = AccountInfo.Role;
}
public PasswordMsgBoxModel()
{
UserName = string.Empty;
Password = string.Empty;
AccountInfo = new Account();
}
}
}

View File

@ -186,7 +186,7 @@ namespace MECF.Framework.UI.Core.Applications
if (_instance.EnableAccountModule)
{
AccountClient.Instance.Service.RegisterViews(_views.GetAllViewList);
/*AccountClient.Instance.Service.RegisterViews(_views.GetAllViewList);
if (_views.SystemName == "GonaSorterUI")
{
GonaMainLogin mainLogin = new GonaMainLogin();
@ -212,7 +212,7 @@ namespace MECF.Framework.UI.Core.Applications
_views.SetViewPermission(account);
_views.MainWindow.Show();
}
}
}*/
}
}

View File

@ -114,20 +114,9 @@
<Compile Include="Accounts\CurrentLogInUsers.xaml.cs">
<DependentUpon>CurrentLogInUsers.xaml</DependentUpon>
</Compile>
<Compile Include="Accounts\GonaMainLogin.xaml.cs">
<DependentUpon>GonaMainLogin.xaml</DependentUpon>
</Compile>
<Compile Include="Accounts\LoginViewModel.cs" />
<Compile Include="Accounts\PasswordMsgBox.xaml.cs">
<DependentUpon>PasswordMsgBox.xaml</DependentUpon>
</Compile>
<Compile Include="Accounts\MainLogin.xaml.cs">
<DependentUpon>MainLogin.xaml</DependentUpon>
</Compile>
<Compile Include="Accounts\MyAccount.xaml.cs">
<DependentUpon>MyAccount.xaml</DependentUpon>
</Compile>
<Compile Include="Accounts\PasswordMsgBoxModel.cs" />
<Compile Include="Accounts\RoleEditView.xaml.cs">
<DependentUpon>RoleEditView.xaml</DependentUpon>
</Compile>
@ -487,18 +476,6 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Accounts\GonaMainLogin.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Accounts\PasswordMsgBox.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Accounts\MainLogin.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Accounts\MyAccount.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>

View File

@ -91,7 +91,6 @@ Global
{EBE55E3F-6DCE-47B9-AC61-54A8B9B3482A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBE55E3F-6DCE-47B9-AC61-54A8B9B3482A}.Release|Any CPU.Build.0 = Release|Any CPU
{F619C5AD-D0D9-4758-A85E-D747156709E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F619C5AD-D0D9-4758-A85E-D747156709E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F619C5AD-D0D9-4758-A85E-D747156709E6}.DebugWithoutCopy|Any CPU.ActiveCfg = Debug|Any CPU
{F619C5AD-D0D9-4758-A85E-D747156709E6}.DebugWithoutCopy|Any CPU.Build.0 = Debug|Any CPU
{F619C5AD-D0D9-4758-A85E-D747156709E6}.DebugWithoutCopyFiles|Any CPU.ActiveCfg = Debug|Any CPU