(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Authorization in Silverlight, part 2: manipulating the UI

(2 votes)
Kevin Dockx
>
Kevin Dockx
Joined Nov 15, 2010
Articles:   19
Comments:   8
More Articles
4 comments   /   posted on Oct 18, 2011
Categories:   Line-of-Business

Tweet This!Introduction

Authorization comes in different forms: you’ve got authorization on your services or service methods: who can access which operation? You’ve got authorized navigation: which role (if any) is required to access a certain View in your application, and what do you do when the user hasn’t got the correct credentials (or isn’t logged in at all)?

Authorized navigation is what we tackled in the previous article in this article series.

 

In this part, we’re going to look at another type of authorization: authorizing access to certain UI elements IN a View (instead of the complete View), depending on the role of the logged in user, and deciding what to do when the user hasn’t got the correct role (eg: do you want to disable the element, or do you want to hide it completely?). We’re going to write a component to achieve this as easily as possible, so it can be reused in different applications.

Next to that, we’ll quickly look into writing code to check for a certain role – which is also of importance in a lot of applications.

As said, this is the second in a 2-part series of articles. The first one can be found here.

You can download the complete source code for this article here.

 

The not-so-cool yet oh-well-it-works way

Let’s start out by having a look at what an IPrincipal looks like. If you’ve got an authorized user on your client, be it via WCF RIA Services or via another, custom approach, it should always implement IPrincipal. This interface exposes a property and a method:

[ComVisible(true)]
public interface IPrincipal
{
    IIdentity Identity { get; }
    bool IsInRole(string role);
}

First of all, we’ve got an Identity. This identifies the currently logged in User: it has the Name property (which should be the unique way to identify your user), an IsAuthenticated boolean which tells you whether or not the current user is authenticated, and an AuthenticationType property, containing the type of authentication that was used.

Second in line: the IsInRole method. This method makes it easy to check if a user has a certain role, and depending on the outcome, you can execute whichever code you want.

With this in mind, it’s quite easy to show or hide certain UI elements depending on the role of a user.  Code like this on your ViewModel:

public Visibility DummyButtonVisibility
{
    get
    {
        return (WebContext.Current.User.IsInRole("Administrator") 
            ? Visibility.Visible : Visibility.Collapsed);
    }
}

combined with XAML code in the corresponding View like this:

<Button Visibility={Binding DummyButtonVisibility} />

would already achieve what we’re looking for. However, this quickly results in a lot of recurring code on each ViewModel, just to check for a certain role for a user. And it becomes a bit more cumbersome if you want to differentiate between disabling/enabling and/or showing/hiding an element, depending on the users’ role.

So, while this approach works, it’s not the best way of doing things. Surely, there must be a cleaner approach?

 

Introducing a better approach: attachable properties.

Wouldn’t it be nice if we could write code like this, in XAML:

<Button authserv:AuthorizationService.RequiresRole="Administrator"
        Margin="0,5"
        Content="Administrator role required"
        Height="30"></Button>

This would make it a bit easier to enable or disable items in our Views depending on the users’ role – next to that, it would keep our ViewModels a lot cleaner, and leave us with less redundant code.

In the rest of the article, we’re going to write code to achieve this: an AuthorizationService, inspired by what can be found in the WCF RIA Services examples, but easier to understand.

 

The Authorization Service

This is the main class we’ll use. In this class, we’ll define two attachable properties:

  • RequiresRole: an IEnumerable<string>, in which we can input one or more roles. In this property, we’ll define which role a user must have to have access to a certain UI element.
  • TargetProperties: another IEnumerable<string>. In this property, we’ll define which properties should be targeted. For example: you might want to disable a button when a user hasn’t got the required role, in which case TargetProperties should be set to IsEnabled. In other cases, you might want to target the Text of a TextBlock, or you might even want to target multiple properties at once: all this is achieved through this TargetProperties property.

Next to these attachable properties, we’ll also need to write logic to check the role of a user. This is done in the Authorize method, which accepts an object parameter (the UI element we’re targeting, eg: a Button, a TextBlock, …).

First of all, we’ll check if a user has one of the required roles (from the RequiresRole attachable property). If he has, the target UI element is allowed for this user. If he hasn’t, we go to the next step: we need to check what we have to do when the user isn’t in one of the required roles. If no TargetProperties are defined, we default to disabling the UI element (or, in case of a TextBlock, setting it’s Opacity to 0.5). If one or more TargetProperties are defined, we call a helper method to change the defined properties (see further on in this article).

One thing remains: we need to call this Authorize method. A great place to call this is in the setters of the attachable properties.

Bringing it all together, the code looks as such for one of the 2 attachable properties, including the call to Authorize:

public static readonly DependencyProperty RequiresRoleProperty =
    DependencyProperty.RegisterAttached(
        "RequiresRole",
        typeof(IEnumerable<string>),
        typeof(AuthorizationService),
        new PropertyMetadata(new string[0], AuthorizationService.RequiresRolePropertyChanged));
private static void RequiresRolePropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    AuthorizationService.Authorize(sender);
}
[TypeConverter(typeof(StringArrayConverter))]
public static IEnumerable<string> GetRequiresRole(DependencyObject target)
{
    return (IEnumerable<string>)target.GetValue(AuthorizationService.RequiresRoleProperty);
}
public static void SetRequiresRole(DependencyObject target, IEnumerable<string> value)
{
    target.SetValue(AuthorizationService.RequiresRoleProperty, value);
}

… and the Authorize method:

public static AuthorizationResult Authorize(object target)
{
    if (target == null)
    {
        throw new ArgumentNullException("target");
    }
    if (DesignerProperties.IsInDesignTool)
    {
        return AuthorizationResult.Allowed;
    }
    // cast to dep object 
    DependencyObject dpTarget = target as DependencyObject;
    // get the required role(s) for this target
    var requiredRoleList = AuthorizationService.GetRequiresRole(dpTarget); 
    if (requiredRoleList.Count() > 0)
    {
        // check 'em
        foreach (var item in requiredRoleList)
        {
            if (WebContext.Current.User.IsInRole(item))
            {
                return AuthorizationResult.Allowed;
            }
        }
        // user hasn't got any of the required roles. Set correct target property to reflect this.
        // if no specific properties are targetted, either hide or disable this control, 
        // depending on preference.
        // find target properties
        var targetProperties = AuthorizationService.GetTargetProperties(dpTarget);
        if (targetProperties.Count() == 0)
        {
                // no target properties defined, use default
                // default: disable
                if (dpTarget is TextBlock)
                {
                    dpTarget.SetValue(AuthorizationHelperClasses
                                   .GetDependencyProperty(dpTarget, "Opacity"), 0.5);
                }
                else
                {
                    dpTarget.SetValue(AuthorizationHelperClasses
                                   .GetDependencyProperty(dpTarget, "IsEnabled"), false);
                }
        }
        else
        {
            foreach (var item in targetProperties)
            {
                AuthorizationHelperClasses.SetPropertyValueOnTarget(dpTarget, item);
            }
        }
        return new AuthorizationResult("No access");
    }
    else
    {
        return AuthorizationResult.Allowed;
    }
}

 

Helper method: setting the target properties.

I mentioned a helper method, called from the Authorize method: this is the SetPropertyValueOnTarget method (accepting a target object – the UI element – and a propertyName – the target property), and this is where we define how the UI element should react. Have a look at the code below:

public static bool SetPropertyValueOnTarget(DependencyObject target, string propertyName)
{
    var prop = GetDependencyProperty(target, propertyName);
    if (target.GetValue(prop) == null || target.GetValue(prop).GetType() == typeof(string))
    {
        target.SetValue(prop, String.Empty);
        return true;
    }
    if (target.GetValue(prop).GetType() == typeof(bool))
    {
        target.SetValue(prop, false);
        return true;
    }
    if (target.GetValue(prop).GetType() == typeof(Visibility))
    {
        target.SetValue(prop, Visibility.Collapsed);
        return true;
    }
    return false;
}

As you can see, the first thing we do is search for the corresponding property on our target (eg: we might be searching for the Visibility property on a Button). Once we’ve got that, we define what should happen to it: if the target value is null, or has a type of string, we set it to an empty string. In case of a Boolean type, we set its value to false (so: IsEnabled has a Boolean value, which will be set to false, thus disabling th Button if the required role isn’t available), and in case of a Visibility type, we set it to Visibility.Collapsed.

It’s this method that could be extended if needed (for example, you might want to check on Color or Brush types, …).

Using this in XAML

On to the final part: using this AuthorizationService. I’m using the exact same code we’ve ended up with in the first part of this article series, with one small change: if you’re logging in as a RegularUser, you now get the RegularUser role (this is done to test multiple roles in the RequiresRole attachable property).

Have a look at the following XAML code (not all code included, have a look at the accompanying source code):

<TextBlock Text="Requires RegularUser role, default target property"
           VerticalAlignment="Center"
           Margin="0,10"></TextBlock>
<Button Width="450"
        authserv:AuthorizationService.RequiresRole="RegularUser"
        Margin="0,5"
        Content="RegularUser role, default target property"
        Grid.Column="1"
        Grid.Row="0"
        Height="30"></Button>
<TextBlock Text="Requires RegularUser role, Visibility target property"
           VerticalAlignment="Center"
           Grid.Row="1"
           Margin="0,10"></TextBlock>
<Button Width="450"
        authserv:AuthorizationService.RequiresRole="RegularUser"
        authserv:AuthorizationService.TargetProperties="Visibility"
        Margin="0,5"
        Content="RegularUser role, Visibility target property"
        Grid.Column="1"
        Grid.Row="1"
        Height="30"></Button>
<TextBlock Text="Requires RegularUser role, Visibility and Text target property"
           VerticalAlignment="Center"
           Grid.Row="2"
           Margin="0,10"></TextBlock>
<TextBlock Width="450"
           authserv:AuthorizationService.RequiresRole="RegularUser"
           authserv:AuthorizationService.TargetProperties="Visibility, Text"
           Margin="0,10"
           VerticalAlignment="Center"
           Text="RegularUser role, Visibility and Content target property"
           Grid.Column="1"
           Grid.Row="2"
           ></TextBlock>

As you can see, it’s pretty simple to use: import the AuthenticationService’s Service namespace, and use it in exactly the same way as you’re used to with attachable properties. This will result in the following when you’re not logged in:

image

If you’re logged in as RegularUser (navigate to the first or second view, and enter “RegularUser”/empty password as credentials), this is how it looks:

If you’re logged in as Administrator (navigate to the first or second view, and enter “Administrator”/empty password as credentials), this is how it looks:

 

Conclusion

In the first part of this series, we’ve learned about authorized navigation. As this isn’t sufficient for all requirements, we’ve learned about changing properties on a UI element depending on the role of the logged in user in this article, which is quite easy to achieve with just a little bit of code, through attachable properties. These two techniques, combined with server-side authentication/authorization (like you get out of the box with WCF RIA Services) should make it easy and transparent for you to build authorization rules in your Silverlight application.

 

About the author

Kevin Dockx lives in Belgium and works at RealDolmen, one of Belgium's biggest ICT companies, where he is a technical specialist/project leader on .NET web applications, mainly Silverlight, and a solution manager for Rich Applications (Silverlight, Windows Phone 7 Series, WPF, Surface, HTML5). His main focus lies on all things Silverlight, but he still keeps an eye on the new developments concerning other products from the Microsoft .NET (Web) Stack. As a Silverlight enthusiast, he's a regular speaker on various national and international events, like Microsoft DevDays in The Netherlands, Microsoft Techdays in Belgium & Portugal, NDC2011, ... Next to that, he also authored a best-selling Silverlight book, Packt Publishing's Silverlight 4 Data and Services Cookbook, together with Gill Cleeren. His blog, which contains various tidbits on Silverlight, .NET, and the occasional rambling, can be found at http://blog.kevindockx.com/, and you can contact him on Twitter via @KevinDockx.


Subscribe

Comments

  • jonx

    Re: Authorization in Silverlight, part 2: manipulating the UI


    posted by jonx on Oct 20, 2011 02:38

    Very good stuff.

    Thank you very much.

  • mpimiller

    Re: Authorization in Silverlight, part 2: manipulating the UI


    posted by mpimiller on Nov 21, 2011 20:18

    Brilliant Stuff Kevin!  I was looking for best practice how to wire up the authorization to the UI and fortunately found your example at just the right time.

    I have only found one issue so far.  When I try to prevent a user from editing a grid by setting the IsReadOnly property, it actually disables it, which won't work.  this is how I'm calling the service for a DataGrid...

    AuthService:AuthorizationService.RequiresRole="guest"
    AuthService:AuthorizationService.TargetProperties="IsReadOnly"  
    My assumption being that given the code above, that a user in the guest role would be able to view the grid in Read Only mode.
    Am I calling the service correctly?  Is there other modifications that I need to make to achieve my objective?
    Thanks,
    Mark
    
    
    
    
    
    


  • KevinDockx

    Re: Authorization in Silverlight, part 2: manipulating the UI


    posted by KevinDockx on Nov 23, 2011 23:10

    Hello Mark,

     

    tnx! :-)  The code above states that a user should have the "guest" role, and if he doesn't have that role, the IsReadOnly property will be set to false.  If he does have the role, the property will be left as-is.  As the IsReadOnly property is false by default (and the authorization manager sets bools to false if the user isn't in the required role), you won't be able to achieve what you want this way. 

    You'll have to change a bit of code on the manager itself (as it will have to be intelligent enough to achieve the "inverse" of how it's written):

    - you could add an extra dependency property to bind to, "InverseValue", which would then set the targetted property to "true" instead of "false" if the role requirement isn't met

    - and you should state that a user should have the "Administrator" role (anything but guest, really).

    With those two things in place, a user that hasn't got the administrator role will result in the IsReadOnly property being set to "true", achieving the scenario you want.

    But in this case, you could use the "Not-so-cool yet oh-well-it-works way" as described above.  That method is valid and works, but requires some more code on your viewmodel, which is perfectly acceptable as well (unless you're looking to reuse this behaviour on various views or want everything concerning authorization of your UI elements in one place, of course - in that case, making the aforemented changes to the manager would be worth the effort).

    Happy coding! :-)

  • edwu

    Re: Authorization in Silverlight, part 2: manipulating the UI


    posted by edwu on Apr 12, 2012 21:34

    Hi Kevin,

    Thank you very much... this article is awesome.

    Cheers,

    Ed

Add Comment

Login to comment:
  *      *       

From this series