在 WPF 中实现 UI 元素授权





5.00/5 (18投票s)
如何使用 ICommand 接口和标记扩展在 WPF 中实现 UI 元素授权/访问控制

引言
本文旨在演示如何使用 ICommand 接口和 标记扩展在 WPF 中实现 UI 元素访问控制。
背景
MSDN 或网上关于如何实现 UI 元素授权/访问控制的信息很少,但我们可以找到大量关于如何使用 Attribute 类来声明式或命令式地在业务逻辑函数或 Web 服务方法中实现基于角色的授权的信息。例如,流行的 ASP.NET MVC AuthorizeAttribute。对于 UI 元素授权,应用程序可以根据用户访问控制列表 (ACL) 来禁用或隐藏 UI 元素。例如,如果当前登录用户没有使用菜单项的访问权限,则禁用按钮但隐藏菜单项。因此,我们需要一些 UI 授权原语来将授权提供者返回的基本的“是”或“否”映射到“显示/隐藏”或“启用/禁用” UI 元素。
概念理解
UI 授权库包含以下 3 个基本构建块:
- 授权提供者 - 提供函数以确定用户是否具有访问资源的权限或执行命令的权限
- 授权原语 - 可以声明式或以编程方式应用于 UI 元素的某些编程构造,以使其可以进行访问控制
- 授权行为 - 响应 UI 元素触发的事件提供授权控制
授权提供者
应用程序可以使用自己的自定义授权提供者以及本文开发的授权原语。但它们必须遵守以下设计指南:
- 从
AuthProvider
派生您的自定义授权提供者。 - 提供一个无参数构造函数或一个接受一个对象数组参数的构造函数。
- 重写重载的
CheckAccess
方法以提供您自己的授权逻辑,并在用户通过身份验证时或在必要时进行初始化。
AuthProvider.Initialize<YourCustomAuthProviderType>();
或
AuthProvider.Initialize<YourCustomAuthProviderTypeWithParameters>(...);
public abstract class AuthProvider
{
private static AuthProvider _instance;
/// <summary>
/// This method determines whether the user is authorize to perform
/// the requested operation
/// </summary>
public abstract bool CheckAccess(string operation);
/// <summary>
/// This method determines whether the user is authorize to perform
/// the requested operation
/// </summary>
public abstract bool CheckAccess(object commandParameter);
public static void Initialize<TProvider>() where TProvider : AuthProvider, new()
{
_instance = new TProvider();
}
public static void Initialize<TProvider>(object[] parameters)
{
_instance = (AuthProvider)typeof(TProvider).GetConstructor(new Type[]
{ typeof(object[]) }).Invoke(new object[] { parameters });
}
public static AuthProvider Instance
{
get { return _instance; }
}
}
以下代码演示了如何创建一个名为 'DefaultAuthProvider
' 的简单授权提供者。
public class DefaultAuthProvider
{
private static string[] _operations;
/// <summary>
/// Load the operation Access Control List (ACL)
/// </summary>
public DefaultAuthProvider(object[] parameters)
{
_operations = parameters.Cast<string>().ToArray();
}
/// <summary>
/// This method determines whether the user is authorize to perform
/// the requested operation
/// </summary>
public override bool CheckAccess(string operation)
{
if (String.IsNullOrEmpty(operation))
return false;
if (_operations != null && _operations.Length > 0)
{
// Match the requested operation with the ACL
return _operations.Any(p => p.ToUpperInvariant() ==
operation.ToUpperInvariant());
}
return false;
}
/// <summary>
/// This method determines whether the user is authorize to perform
/// the requested operation
/// </summary>
public override bool CheckAccess(object commandParameter)
{
string operation = Convert.ToString(commandParameter);
return CheckAccess(operation);
}
}
授权原语
AuthDelegateCommand
AuthDelegateCommand
是 ICommand
接口的具体实现。ICommand
中定义的方法具有授权概念,完美契合了作者在此解释的授权需求。
bool CanExecute(object parameter)
- 定义了确定命令在其当前状态下是否可以执行的方法 (MSDN)
- 用户是否有权执行该命令 (作者解释)
void Execute(object parameter)
- 定义了调用命令时要调用的方法 (MSDN)
- 如果
CanExecute
返回true
,则用户有权执行命令 (作者解释)
更重要的是,在 WPF 中使用 ICommand
遵循 MVVM 设计模式,其中访问控制和业务逻辑的执行实现在 ViewModel 中。此外,我们可以使用 附加属性作为一种机制,以响应触发的事件来控制 UI 元素。我将在本文后面详细介绍这种技术。
MenuItem
、ButtonBase 及其派生控件的 Command
属性是 ICommand
的消费者。它会调用 CanExecute
来确定是启用还是禁用自身及其相关的命令调用代码。它还可以通过订阅 CanExecuteChanged
事件来确定何时调用该方法。下面的 AuthDelegateCommand
重载了构造函数,并将访问控制函数 'AuthProvider.Instance.CheckAccess(...)
' 注入到 CanExecute
方法中,我们只需要在创建 AuthDelegateCommand
实例时通过 executeMethod
提供业务逻辑代码。
public class AuthDelegateCommand : DelegateCommandBase
{
public AuthDelegateCommand(Action executeMethod)
: base((op) => executeMethod(), (op) => AuthProvider.Instance.CheckAccess(op))
{
if (executeMethod == null)
throw new ArgumentNullException("executeMethod");
}
public void Execute()
{
base.Execute(null);
}
}
下面的代码显示了如何在视图及其关联的 ViewModel 中使用 AuthDelegateCommand
。
// MainWindow.xaml
// The CommandParameter value is the access controlled operation name
<Button Command="{Binding CreateCommand}" CommandParameter="CanCreate">Create</Button>
// MainWindowVM.cs
private ICommand _createCommand;
public ICommand CreateCommand
{
get
{
return _createCommand ?? (_createCommand = new AuthDelegateCommand(
// executeMethod
() => MessageBox.Show("You can execute the Create command.",
"Authorization"))
);
}
标记扩展
标记扩展是授权原语的构建块,它使我们能够以声明式的方式在 XAML 中将授权功能与 UI 元素关联起来。在本文中,我创建了两个授权原语。
AuthToEnabledExtension
- 使您能够为具有IsEnabled
属性的 UI 元素提供访问控制AuthToVisiblityExtension
- 使您能够为具有Visibility
属性的 UI 元素提供访问控制
AuthToEnabledExtension
[MarkupExtensionReturnType(typeof(bool))]
public class AuthToEnabledExtension : MarkupExtension
{
public string Operation { get; set; }
public AuthToEnabledExtension()
{
Operation = String.Empty;
}
public AuthToEnabledExtension(string operation)
{
Operation = operation;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (String.IsNullOrEmpty(Operation))
return false;
return AuthProvider.Instance.CheckAccess(Operation);
}
}
AuthToVisibilityExtension
[MarkupExtensionReturnType(typeof(Visibility))]
public class AuthToVisibilityExtension : MarkupExtension
{
public string Operation { get; set; }
public AuthToVisibilityExtension()
{
Operation = String.Empty;
}
public AuthToVisibilityExtension(string operation)
{
Operation = operation;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (String.IsNullOrEmpty(Operation))
return Visibility.Collapsed;
if (AuthProvider.Instance.CheckAccess(Operation))
return Visibility.Visible;
return Visibility.Collapsed;
}
}
如您所见,创建授权原语非常简单。该实现通过调用 AuthProvider.Instance.CheckAccess(Operation)
在 ProvideValue
方法中执行授权逻辑。您可以在下面的代码中看到它的用法
// AuthToVisibility
<Image Source="Images/view.png" Visibility="{op:AuthToVisibility "CanView"}" />
// AutoToEnabled
<MenuItem Header="_Exit" IsEnabled="{op:AuthToEnabled "CanClose"}"></MenuItem>
// You can also combine and use both of them together
<Button IsEnabled="{op:AuthToEnabled "CanCreate"}"
Visibility="{op:AuthToVisibility "CanView"}">Create Record</Button>
授权行为
这涉及到使用附加属性来响应 UI 元素触发的事件提供授权控制。例如,如果用户没有权限,则阻止用户关闭窗口。在本文中,我为此目的编写了一个名为 'CloseWindowBehavior
' 的附加行为。它附加到窗口关闭事件,并调用 ClosingCommand
来确定用户是否可以关闭主窗口。
// MainWindow.xaml
<i:Interaction.Behaviors>
<b:CloseWindowBehavior ClosingCommand="{Binding ClosingCommand}" />
</i:Interaction.Behaviors>
// MainWindowVM.cs
private ICommand _closingCommand;
public ICommand ClosingCommand
{
get
{
return _closingCommand ?? (_closingCommand = new DelegateCommandBase(
(_) => { },
(_) => AuthProvider.Instance.CheckAccess("CanClose")
));
}
}
关注点

本文包含的演示显示,完全信任的用户可以看到顶部的两张图片,并可以点击表单上的任何按钮。但是,当表单启动时,您可以看到左侧有一张图片,并且只能点击“关闭”按钮。由于访问控制列表中缺少某些权限,因此其余按钮已被禁用。您可以尝试通过更改 Login
方法中的权限来查看访问控制列表如何影响表单中的 UI 元素。
要在您的计算机上运行此演示,您需要下载 Microsoft Expression Blend 软件开发工具包 (SDK) for .NET 4 或获取以下库文件:System.Windows.Interactivity.dll。
最后但同样重要的是,我包含了一个示例,可从本文顶部的链接获取,该示例演示了我在这篇文章中所描述的内容。
历史
- 2011 年 11 月 4 日:初版