Click here to Skip to main content
Click here to Skip to main content

Implement UI Element Authorization in WPF

, 4 Nov 2011
Rate this:
Please Sign up or sign in to vote.
How to implement UI element authorization/access control in WPF using the ICommand interface and Markup Extension
Without Authorization

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:

  1. Authorization Providers - Provide the function to determine whether the user has the access right to access a resource or execute a command
  2. Authorization Primitives - Some programming constructs which can be applied to the UI elements declaratively or programmatically to make it access controlled
  3. 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:

  1. Derive your custom authorization provider from AuthProvider.
  2. Provide a parameterless constructor or constructor that takes in one parameter of type object array.
  3. 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;

    /// <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; }
    }
}

The following code demonstrates how to create a simple authorization provider named '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);
    }
}

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.

// 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"))
    ); 
}

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:

// 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>

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

With Authorization

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Elvin Cheng

Singapore Singapore
Elvin Cheng is currently living in Woodlands, Singapore. He has been developing applications with the .NET Framework, using C# and ASP.NET since October 2002. Elvin specializes in building Real-time monitoring and tracking information system for Semi-conductor manufacturing industry. During his spare time, he enjoys reading books, watching movie and gym.
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 PinmemberFilip D'haene5-Nov-11 2:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 4 Nov 2011
Article Copyright 2011 by Elvin Cheng
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid