尽管我在 Windows 应用程序中做了类似的事情,但我在这方面的表现却很糟糕。我正在开发 WPF 应用程序(Prism、Unity、MVVM),并且刚刚完成登录视图。一旦根据 SQL Server 中的表验证了用户的凭据,我将执行以下操作:
Thread.CurrentPrincipal = user.GenericPrincipal();
用户类别定义如下:
public class ApplicationIdentity : GenericIdentity, IApplicationIdentity
{
public string UserName { get; set; }
public bool Authenticated { get; set; }
public ICollection<IModuleProperties> Modules { get; set; }
public ICollection<IViewProperties> Views { get; set; }
public ApplicationIdentity(string userName, IAuthenticatedUser authenticatedUser)
: base(userName)
{
UserName = userName;
Authenticated = authenticatedUser.Authenticated;
Modules = authenticatedUser.Modules;
Views = authenticatedUser.Views;
}
public IPrincipal GenericPrincipal()
{
return new GenericPrincipal(this, null);
}
}
我遇到的问题是,几乎在登录屏幕关闭后我立即拨打此电话:
var currentUser = (IApplicationIdentity)Thread.CurrentPrincipal.Identity;
这引发了一个异常,即 Identity 无法转换为 IApplicationIdentity 类型,并且我不确定我缺少什么。到目前为止,我读过的所有 SO/Google 文章都通过针对 AD 对用户进行身份验证来解决这个问题,但这在我的场景中不起作用。
我在这里试图解决的问题只是保留当前登录的用户以及他们应该有权访问的模块和视图。如果除了设置 CurrentPrincipal 之外还有更好的方法来实现此目的,我完全愿意接受其他解决方案。感谢您能够提供的任何帮助!
编辑(解决方案):
我只是想结束解决方案的循环。接下来是一些教程,所以它有点长,但应该对任何偶然发现它的人都有帮助。接受的答案建议我注入 Unity 容器,注册对象的实例,然后从那里使用它。我同意这可能是正确的方法,但它需要我对我的 Bootstrapper 进行一些“破解”。不过,在了解引导程序逻辑之前,我可能应该回顾一下所涉及的一些准备工作。
在我原来的方法中,我的登录视图未在我的容器中注册,因为登录视图是在我的 Bootstrapper 运行之前实例化的(App.xaml 打开了登录视图。)
我做的第一件事是让我的 View 和 ViewModel 可注入:
public Login(ILoginViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
public LoginViewModel(IUnityContainer container)
{
Container = container;
}
再次强调:使用过 Unity(或一般 IoC)的任何人都应该熟悉这一点。然后,一旦用户经过身份验证,我需要向 Unity 注册当前的用户对象:
private void Login(object obj)
{
...
if (user.Authenticated)
{
Container.RegisterInstance("CurrentUser", user);
}
...
}
任何关注过互联网上大量 Prism/Unity/MVVM 文章的人可能都熟悉以下方法:
protected override IModuleCatalog CreateModuleCatalog()
{
var catalog = new ModuleCatalog();
catalog.AddModule(typeof (CoreModule));
catalog.AddModule(typeof (CoreModule2));
catalog.AddModule(typeof (CoreModule3));
return catalog;
}
这种方法非常简单,但在现实场景中,用户有权访问的模块可能是动态的而不是静态的(或两者的组合)。CreateModuleCatalog()
在 Bootstrapper 中调用Run()
方法提前于InitializeShell()
。就我而言,我仍然有一个所有用户都可以访问的静态模块(无论授权级别如何),但是(在我看来)从这个方法实例化登录视图会感觉“反模式”(更不用说注册Unity 的类型。)因此我的CreateModuleCatalog()
became:
protected override IModuleCatalog CreateModuleCatalog()
{
var catalog = new ModuleCatalog();
catalog.AddModule(typeof(CoreModule));
return catalog;
}
我选择使用我的覆盖InitializeShell()
注册我的登录类型,显示登录视图等。我最终得到了这样的实现:
protected override void InitializeShell()
{
base.InitializeShell();
Container.RegisterType(typeof (Login), "LoginView");
Container.RegisterType<ILoginViewModel, LoginViewModel>();
Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
ShowLogOn();
Application.Current.MainWindow = (Window)Shell;
Application.Current.MainWindow.Show();
}
ShowLogOn()
如果用户最终点击登录视图上的取消按钮,将关闭应用程序,所以我可能已经将所有代码放在ShowLogOn()
在致电之前base.InitializeShell()
这样就不会运行不必要的代码。ShowLogOn()
看起来和你想象的一模一样:
private void ShowLogOn()
{
var login = Container.Resolve<Login>();
var dialogResult = login.ShowDialog();
if (!dialogResult.Value)
{
Application.Current.Shutdown(1);
}
else
{
LoadAuthorizedModules();
Application.Current.MainWindow = null;
Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
}
}
如果用户取消登录视图,对话框结果将为 false,并且应用程序将关闭。如果他们已成功通过身份验证,我们现在需要加载允许他们查看的模块。这也非常简单:
private void LoadAuthorizedModules()
{
var currentUser = Container.Resolve<IApplicationIdentity>("CurrentUser");
foreach (var module in currentUser.Modules)
{
var moduleAssembly = Assembly.Load(module.AssemblyName);
var loadingModule = moduleAssembly.GetType(module.Type);
ModuleCatalog.AddModule(new ModuleInfo
{
ModuleName = loadingModule.Name,
ModuleType = loadingModule.AssemblyQualifiedName
});
}
}
这个方法需要更多的解释。首先看一下这一行,因为它可能会令人困惑:var currentUser = Container.Resolve<IApplicationIdentity>("CurrentUser");
请注意,我没有明确注册类型IApplicationIdentity
我的代码中的任何地方!然而,当我这样做时,它被隐式注册了:Container.RegisterInstance("CurrentUser", user);
从技术上讲,我可以将前面的语句写为:Container.RegisterInstance<IApplicationIdentity>("CurrentUser", user);
但这对我来说是多余的,所以做你觉得最舒服的事情。
的模块属性IApplicationIdentity
包含对象的集合,其中包含有关当前用户有权访问的模块的信息。module.AssemblyName
是我的自定义模块类型所在的物理程序集的名称,并且module.Type
是模块的类型。
正如您所看到的:一旦通过反射的力量加载了类型,我们就需要将其添加到 Unity Bootstrappers 中ModuleCatalog
财产是直接的。假设一切顺利,执行应该返回到您的 InitializeShell 方法,并且您的 Shell 现在应该启动。
这是相当长的,但我希望有人觉得它有用。另外,我对有关如何使这段代码“更好”的任何意见感兴趣。谢谢!