Introduction
This article aims to demonstrate how to implement UI element access control in WPF using the ICommand interface and Markup Extension.
Background
There is little information revealed in the MSDN or web on how to implement UI element authorization/access control, but we can find a lot of information about how to use the Attribute class to declaratively or imperatively implement the role based authorization in business logic functions or web service methods. For instance, the popular ASP.NET MVC AuthorizeAttribute. For UI element authorization, the application can disable or hide the UI elements based on the user access control list (ACL). For example, disable the button but hide the menu item if the current logged in user has no access right to use them. Hence, we need to have some UI authorization primitives to map the basic Yes or No returned from the authorization provider to Show/Hide or Enable/Disable the UI elements.
Understanding the Concept
The UI authorization library contains the following 3 fundamental building blocks:
- Authorization Providers - Provide the function to determine whether the user has the access right to access a resource or execute a command
- Authorization Primitives - Some programming constructs which can be applied to the UI elements declaratively or programmatically to make it access controlled
- Authorization Behaviors - Provide authorization control in response of the event triggered by UI elements
Authorization Providers
Application can use their own custom authorization providers together with the authorization primitives developed in this article. But they must adhere to the following design guidelines:
- Derive your custom authorization provider from
AuthProvider
.
- Provide a parameterless constructor or constructor that takes in one parameter of type object array.
- Override the overloaded
CheckAccess
method to provide your own authorization logic and initialize it upon user being authenticated or whenever necessary.
AuthProvider.Initialize<YourCustomAuthProviderType>();
or:
AuthProvider.Initialize<YourCustomAuthProviderTypeWithParameters>(...);
public abstract class AuthProvider
{
private static AuthProvider _instance;
public abstract bool CheckAccess(string operation);
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; }
}
}
The following code demonstrates how to create a simple authorization provider named 'DefaultAuthProvider
'.
public class DefaultAuthProvider
{
private static string[] _operations;
public DefaultAuthProvider(object[] parameters)
{
_operations = parameters.Cast<string>().ToArray();
}
public override bool CheckAccess(string operation)
{
if (String.IsNullOrEmpty(operation))
return false;
if (_operations != null && _operations.Length > 0)
{
return _operations.Any(p => p.ToUpperInvariant() ==
operation.ToUpperInvariant());
}
return false;
}
public override bool CheckAccess(object commandParameter)
{
string operation = Convert.ToString(commandParameter);
return CheckAccess(operation);
}
}
Authorization Primitives
AuthDelegateCommand
AuthDelegateCommand
is the concrete implementation of ICommand
interface. The methods defined in ICommand
have the notion of authorization concept and it perfectly matches the authorization requirement interpreted by the author here.
bool CanExecute(object parameter)
- Defines the method to determine whether the command can execute in its current state (MSDN)
- Is the user authorized to execute the command (Author interpretation)
void Execute(object parameter)
- Defines the method to be called when the command is invoked. (MSDN)
- User is authorized to execute the command if
CanExecute
returns true
(Author interpretation)
More importantly, using ICommand
in WPF abide to MVVM design pattern in which the logic of access control and business logic execution is implemented in the view model. Furthermore, we can use Attached Properties as a mechanism to access control the UI elements in response to the event triggered. I will reveal more about this technique later in this article.
The Command
property of MenuItem, ButtonBase and their derived controls are ICommand
consumer. It is expected to call CanExecute
to determine whether to enable or disable itself and its associated command invocation code. It also can determine when to invoke that method by subscribing to the CanExecuteChanged
event. The AuthDelegateCommand
shown below overloads the constructor and inject the access control function 'AuthProvider.Instance.CheckAccess(...)
' to the CanExecute
method and we only need to provide the business logic code through the executeMethod
when creating an instance of AuthDelegateCommand
.
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);
}
}
The code shown below shows how to use AuthDelegateCommand
in view and its associated view model.
<Button Command="{Binding CreateCommand}" CommandParameter="CanCreate">Create</Button>
private ICommand _createCommand;
public ICommand CreateCommand
{
get
{
return _createCommand ?? (_createCommand = new AuthDelegateCommand(
() => MessageBox.Show("You can execute the Create command.",
"Authorization"))
);
}
Markup Extensions
Markup Extension is the building block of authorization primitives where it enables us to declaratively associate the authorization function to the UI elements in XAML. In this article, I've created two authorization primitives.
AuthToEnabledExtension
- enables you to provide access control to the UI element which has the IsEnabled
property
AuthToVisiblityExtension
- enables you to provide access control to the UI element which has the Visibility
property
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;
}
}
As you can see, it is very simple to create an authorization primitive. The implementation performs the authorization logic in the ProvideValue
method by calling the AuthProvider.Instance.CheckAccess(Operation)
. You can see its usage in the code shown below:
<Image Source="Images/view.png" Visibility="{op:AuthToVisibility "CanView"}" />
<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>
Authorization Behaviors
This involves the use of attached properties to provide authorization control in response of the event triggered by UI elements. For example, prevent user from closing the window if he/she doesn't have the permission. In this article, I've written an attached behavior named 'CloseWindowBehavior
' for this purpose. It attaches to the window closing event and invokes the ClosingCommand
to determine whether the user can close the main window.
// 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")
));
}
}
Points of Interest
The demo included in this article shows that a user with full-trust can see both images as shown at the top of this article and click any buttons shown on the form. However, when the form is launched, you can see one image on the left side and can only click the Close button. The rest of the button has been disabled due to the lack of certain permissions in the access control list. You can try it out yourself by changing the permission in the Login
method to see how the access control list affects the UI elements in the form.
To run this demo on your computer, you need to download the Microsoft Expression Blend Software Development Kit (SDK) for .NET 4 or manage to get the following library file: System.Windows.Interactivity.dll.
Last, but not least, I've included a sample, available from the link at the top of this article, which demonstrates what I've described in this article.
History
- 4th November, 2011: Initial version