Unlike MVC or Web Forms, when using WPF, you don't get a pre-built security system. Thus, you need to come up with your own method of securing controls on WPF screens, such as the one shown in Figure 1. There are a few different methods to accomplish this goal. For example, you can create different properties in your View model to make controls invisible or disabled based on a user's role. The problem with this approach is that if you need to secure more controls, you need to change the code and then redistribute your WPF application to your users. In this article, I'm going to take a data-driven approach to security so you can make changes in a database table and have your WPF application's security update without having to make code changes.

Build an Employee Project

To get the most out of this article, I suggest you build the sample as you read. Create a new WPF application using Visual Studio named WPFSecuritySample. Add a new folder namedUserControls. Right mouse-click on this folder and add a user control named EmployeeControl. This control is where you're going to create the screen shown in Figure 1.

Figure 1: An Employee Information Screen
Figure 1: An Employee Information Screen

Employee User Control

Create the employee user control by typing in the code shown in Listing 1. In this screen, you're adding the Name property to some of the controls and the Tag property to others. This is to illustrate that you can use either of these properties to locate the control you wish to secure. Later in this article, you're going to use the {Binding Path} expression to locate the control to secure as well.

Listing 1: The XAML for the Employee screen

<UserControl x:Class="WPFSecuritySample.EmployeeControl" ... // NAMESPACES HERE >
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        
        <Button Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left" Content="New" Name="NewButton" />
        <Label Grid.Row="1" Grid.Column="0" Content="Employee ID" />
        <TextBox Grid.Row="1" Grid.Column="1" Name="EmployeeID" Text="{Binding Path=EmployeeID}" />
        <Label Grid.Row="2" Grid.Column="0" Content="First Name" />
        <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Path=FirstName}" />
        <Label Grid.Row="3" Grid.Column="0" Content="Last Name" />
        <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Path=LastName}" />
        <Label Grid.Row="4" Grid.Column="0" Content="Salary" />
        <TextBox Grid.Row="4" Grid.Column="1" Tag="Salary" Text="{Binding Path=Salary}" />
        <Label Grid.Row="5" Grid.Column="0" Content="SSN" />
        <TextBox Grid.Row="5" Grid.Column="1" Tag="SSN" Text="{Binding Path=SSN}" />
        <Button Grid.Row="6" Grid.Column="1" HorizontalAlignment="Left" Content="Save" Name="SaveButton" /> 
    </Grid>
</UserControl>

SecurityViewModelBase Class

When creating WPF applications, you should always use the Model-View-ViewModel (MVVM) design pattern. You're going to create an EmployeeViewModel class in the next section, but first, create a base class named SecurityViewModelBase where you write code to secure controls. The EmployeeViewModel class inherits from the SecurityViewModelBase class.

Add a new folder to the project named SecurityClasses. Right mouse-click on this folder and add a new class named SecurityViewModelBase. Add the code shown in Listing 2 to this new file. This code is just the stubbed methods you're going to write a little later in this article, but you need to create them now so you can build the employee view model. A short description of the properties and methods you see in Listing 2 are described in Table 1.

Listing 2: Create a base class for all view models that need security to inherit from

using System.Collections.Generic;
using System.Security.Principal;
using System.Threading;
using System.Windows;
using System.Windows.Controls;

public class SecurityViewModelBase
{
    public SecurityViewModelBase()  
    {
        ControlsToSecure = new List<SecurityControl>();
        ControlsInContainer = new List<XAMLControlInfo>();  
    }
    
    public List<SecurityControl> ControlsToSecure { get; set; }  
    public List<XAMLControlInfo> ControlsInContainer { get; set; }  
    public IPrincipal CurrentPrincipal { get; set; }
    public virtual void SecureControls(object element, string containerName) { }
    protected virtual void LoadControlsToSecure(string containerName) { }
    protected virtual void LoadControlsInXAMLContainer(object element) { }
}

Employee View Model

Create a new folder in your project named ViewModelClasses. Right mouse-click on that folder and add a new class named EmployeeViewModel. Add the code shown in Listing 3 to this new file. The EmployeeViewModel class inherits from the SecurityViewModelBase class so it can take advantage of the security methods.

Listing 3: Make all your view models inherit from the SecurityViewModelBase class

using System.Collections.Generic;

public class EmployeeViewModel : SecurityViewModelBase
{
    public EmployeeViewModel() : base()  
    {
        EmployeeID = 1;
        FirstName = "Bruce";
        LastName = "Jones";
        Salary = 75000;
        SSN = "555-55-5555";  
    }
    
    public int EmployeeID { get; set; }  
    public string FirstName { get; set; }  
    public string LastName { get; set; }  
    public decimal Salary { get; set; }  
    public string SSN { get; set; }
    protected override void LoadControlsToSecure(string containerName) { }
}

In the EmployeeViewModel class, add the individual properties to bind to the employee screen. Override the LoadControlsToSecure() method so you can choose from where to retrieve the controls to secure. For example, you may hard-code the controls, or retrieve them from an XML file or a database table.

Main Window

Now that you have your view model classes created, drag the EmployeeControl user control onto the MainWindow. Be sure to reset all layout properties so the user control takes up the full width of your window. Your <Grid> in this window should look like the following:

<Grid>
    <UserControls:EmployeeControl />
</Grid>

Add an XML namespace to the <Window> control so you can create an instance of the EmployeeViewModel class within the resources section of the window control.

xmlns:vm="clr-namespace:WPFSecuritySample.ViewModels"

Add a <Window.Resources> section where you can create the definition for the employee view model, as shown in the code snippet below.

<Window.Resources>
    <vm:EmployeeViewModel x:Key="viewModel" />
</Window.Resources>

Assign the DataContext attribute of the <Grid> to the instance of this view model class, as identified by the key “viewModel”.

<Grid DataContext="{StaticResource viewModel}">
    <UserControls:EmployeeControl />
</Grid>

Main Window Code-Behind

Go to the code-behind for the MainWindow class and add a private variable named _viewModel. Connect this variable to the instance of the view model created by XAML using the code below.

public partial class MainWindow : Window
{  
    public MainWindow()  
    {
        InitializeComponent();
        _viewModel = (EmployeeViewModel)
        this.Resources["viewModel"];  
    }
    
    private readonly EmployeeViewModel _viewModel;
}

Set Security Principal Object

In order to secure your controls on each window, you need to add a Loaded event to your window. From this event, call the SecureControls() method on your view model class. Open the MainWindow.xaml file and add the following key/value pair in your <Window> to create the Loaded event.

Loaded="Window_Loaded"

Listing 4 shows both the Window_Loaded() event procedure and the SetSecurityPrincipal() method. The SetSecurityPrincipal() method is where you need to create, or get, an IPrincipal object and change the principal policy on the current thread of execution. You can either grab the current WindowsPrincipal object as shown here, or you can create your own GenericPrincipal object by prompting a user for their username and password.

Listing 4: Set a security principal on the thread before you attempt to secure the controls

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    // Set your Security Principal  
    SetSecurityPrincipal();
    
    // Secure controls on this WPF window  
    _viewModel.SecureControls(this, "EmployeeControl");
}

private void SetSecurityPrincipal()
{
    // Set Principal to a WindowsPrincipal  
    Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
    
    // NOTE: You can create a GenericPrincipal here with your own credentials and roles
}

Call the SetSecurityPrincipal() method from within the Window_Loaded() event. After the call to this method, call the SecureControls() method on the view model passing in the instance of this window and the name of the user control as a string.

Try It Out

Run the application and you should see a screen that looks like Figure 1. You're now ready to start building the part of the code that secures the various controls on your screen.

Goals of a Security System

Before you write the code to implement a security system, let's determine the features you need in a security system. The most basic feature is the ability to make a control appear or disappear based on a role. Of course, visibility in WPF can mean either hidden or collapsed, so you most likely want to be able to specify either. The ability to make a control become disabled based on a role should be another feature in your security system. Finally, you might also want to make input controls, such as text boxes, become read-only for certain roles.

Prepare Screen for Security

You don't want to have to hard-code the security on each individual screen or within each view model class. Instead, a data-driven approach is a better choice. The way to accomplish this is to pass in a reference to a XAML element such as a Window, a User Control, a Grid, or other container to the SecureControls() method. All of the controls contained within this container are read in and a unique identifier for each control is located. This unique identifier can be the Name or Tag properties, or maybe the property name in the {Binding Path="propertyName"} expression. The XAML shown below highlights the button and text box you're going to secure on the employee screen. Each control you want to secure must have a Name, Tag, or a {Binding} expression that's unique.

<Button Content="New" Name="NewButton" />
<TextBox Name="EmployeeID" Text="{Binding Path=EmployeeID}" />
<TextBox Tag="Salary" Text="{Binding Path=Salary}" />
<TextBox Tag="SSN" Text="{Binding Path=SSN}" />
<Button Content="Save" Name="SaveButton" />

Collection of Security Objects

Table 2 shows a sample set of security data stored in a collection of SecurityControl objects. This SecurityControl collection secures the controls shown in Figure 1. For each control, specify what Mode you wish that control to take on, such as read-only, collapsed, hidden, or disabled. The Roles property contains a string array of roles. If the user does NOT belong to one of those roles, the value in the Mode property is used to change the state of the control. For example, if the user isn't in a “Users” or “Supervisors” role, the Visibility property of the control identified as NewButton is set to “Collapsed”.

For the employee screen shown in Figure 1, you don't want to allow normal users of the system to be able to save any changes to employee data. Thus, hide the “New” button and disable the “Save” button on the screen. You also don't want normal users to see the salary of other employees, so make the salary text box invisible. An administrator in your application has the right to save employee data and to see the salary data.

To match this set of data to the elements on your WPF Window or User Control, the ElementIdentifier property can be the Name or Tag property of the control to secure, or the value in the {Binding} expressions' Path property. The ContainerName property (used later in this article) is the name of the XAML container you wish to secure.

To keep things simple, you're going to create a hard-coded collection of SecurityControl objects with element identifiers to match the controls in the EmployeeControl user control in your project, as shown in Figure 2. For now, you're also just going to use the Name or Tag properties to identify each control.

Figure 2: Mapping security controls to controls in XAML
Figure 2: Mapping security controls to controls in XAML

WPF Security Classes

You've already created the stub for the SecurityViewModelBase class, and the EmployeeViewModel class that inherits from it. There are two additional classes (Figure 3) you need to create in order to be able to secure controls on any WPF screen. The first class is named XAMLControlInfo and is used to hold information about controls within the WPF container you wish to secure. The second control is the SecurityControl class, which is the class to hold the information described in Table 2.

Both classes are added as List<T> type properties in the SecurityViewModel class. The methods LoadControlsToSecure() and LoadControlsInXAMLContainer() are responsible for loading each of these generic collection classes.

Figure 3: Classes used to secure controls on the employee screen
Figure 3: Classes used to secure controls on the employee screen

SecurityControl Class

The SecurityControl class (Listing 5) holds the data to secure a single control on a Window or User Control or other WPF container control. Each row of data shown in Table 2 is created by placing security data into a new instance of a SecurityControl class. The Roles property is a string array, but there's also a RolesAsString property that's used to express that Roles array as a comma-delimited list, if needed.

Listing 5: The SecurityControl class holds security information about a single control

public class SecurityControl
{
    public string ContainerName { get; set; }  
    public string ElementIdentifier { get; set; }  
    public string Mode { get; set; }  
    public string[] Roles { get; set; }
    private string _RolesAsString = string.Empty;  

    public string RolesAsString  
    {
        get { return _RolesAsString; }    
        set 
        {
            _RolesAsString = value;
            Roles = _RolesAsString.Split(',');
        }  
    }
}

XAMLControlInfo Class

Once you have the list of controls to secure, you need to gather a list of the controls on the WPF element you wish to secure. The WPF element can be a Window, a User Control, or a XAML container control such as a Grid or a StackPanel. The XAMLControlInfo class (Listing 6) is the one that holds the information about each control within the WPF element.

Listing 6: The XAMLControlInfo class holds information about a control within a WPF container

public class XAMLControlInfo
{
    public object TheControl { get; set; }  
    public string ControlName { get; set; }  
    public string Tag { get; set; }  
    public string ControlType { get; set; }  
    public bool HasIsReadOnlyProperty { get; set; }
    
    public bool ConsiderForSecurity()  
    {    
        return !string.IsNullOrEmpty(ControlName) || !string.IsNullOrEmpty(Tag); 
    }
}

The XAMLControlInfo class holds a reference to the control itself (TheControl), the name of the control (ControlName), the value in the Tag property, the type of control (ControlType) and a Boolean flag to identify if the control has an IsReadOnly property (HasIsReadOnlyProperty).

There's also a method contained in this class called ConsiderForSecurity(). This method returns a True value if either the ControlName or the Tag properties contain a value. For a control to be secured, one or the other of these properties must contain a value, otherwise there's nothing to match up with a value in the ControlsToSecure collection.

Security View Model Base Class

Earlier you created the stub of the SecurityViewModelBase class. It's now time to write the code for the various methods you stubbed out. Before you write these methods, look at Figure 4 for an overview of each of the methods called from SecureControls().

Figure 4: Security method overview
Figure 4: Security method overview

The SecureControls() method is called from the code-behind of the WPF element/container you want to secure. or from a command in your view model if you're using commanding. The SecureControls() method calls the LoadControlsToSecure() and the LoadControlsInXAMLContainer() methods to populate the two properties ControlsToSecure and ControlsInContainer, respectively. Override the LoadControlsToSecure() method so you can decide from which location to load the controls to secure. For example, you may hard-code the controls, retrieve them from an XML file, or load them from a database table. The LoadControlsInXAMLContainer() method loops through all controls within the XAML element you pass in and loads the ControlsInContainer collection.

Once both collections have been loaded, loop through the ControlsToSecure collection and attempt to locate the control in the ControlsInContainer collection. If you find a match, check to see if the user is a part of one of the roles identified in the security control. If they aren't a part of the role, then the state of the WPF control is modified to read-only, disabled, or made invisible.

LoadControlsToSecure Method

The LoadControlsToSecure() method builds the data shown in Table 2. This code is written in the EmployeeViewModel class because it will change based on where you wish to store the security control information. For this first example, the data is going to be hard-coded in the LoadControlsToSecure() method, as shown in Listing 7.

Listing 7: A hard-coded version of the LoadControlsToSecure() method

protected override void LoadControlsToSecure(string containerName)
{
    base.LoadControlsToSecure(containerName);
    
    ControlsToSecure = new List<SecurityControl>
    {
        new SecurityControl 
        {
            ContainerName = "EmployeeControl", 
            ElementIdentifier = "NewButton",
            Mode = "collapsed",
            RolesAsString = "Users123,Supervisor"
        },
        new SecurityControl    
        {
            ContainerName = "EmployeeControl",
            ElementIdentifier = "EmployeeID",
            Mode = "readonly",
            RolesAsString = "Admin,Supervisor"
        },
        new SecurityControl
        {
            ContainerName = "EmployeeControl",
            ElementIdentifier = "Salary",
            Mode = "hidden",
            RolesAsString = "Admin"
        },
        new SecurityControl
        {
            ContainerName = "EmployeeControl",
            ElementIdentifier = "SSN",
            Mode = "disabled",
            RolesAsString = "Supervisor"
        },
        new SecurityControl
        {
            ContainerName = "EmployeeControl",
            ElementIdentifier = "SaveButton",
            Mode = "disabled",
            RolesAsString = "Admin,Supervisor"
        }
    };
}

LoadControlsInXAMLContainer Method

In Listing 4 you passed in a reference to the current window to the SecureControls() method as shown as the first parameter in the code snippet below.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    // Set your Security Principal  
    SetSecurityPrincipal();
    
    // Secure controls on this WPF window  
    _viewModel.SecureControls(this, "EmployeeControl");
}

This reference is passed into the LoadControlsInXAMLContainer() method, shown in Listing 8 as the parameter element. If the reference is a DependencyObject, then you know that you have a control that you can possibly secure. Build a new instance of a XAMLControlInfo class and fill in the TheControl and ControlType properties. Attempt to cast the control as a FrameworkElement object. If the cast succeeds, extract the Name and Tag properties. If either of these properties are filled in, the ConsiderForSecurity() method will return a True value.

Listing 8: The LoadControlsInXAMLContainer() method

protected virtual void LoadControlsInXAMLContainer(object element)
{
    XAMLControlInfo ctl;
    FrameworkElement fe;
    
    if (element is DependencyObject dep) 
    {
        ctl = new XAMLControlInfo
        {
            TheControl = element,
            ControlType = element.GetType().Name    
        };
        
        // Cast to 'FrameworkElement' so we can get the Name and Tag properties    
        fe = element as FrameworkElement;    
        if (fe != null) 
        {
            ctl.ControlName = fe.Name;
            if (fe.Tag != null) 
            {
                ctl.Tag = fe.Tag.ToString();
            }
        }
        
        if (ctl.ConsiderForSecurity()) 
        {
            // Is there a ReadOnly property?
            ctl.HasIsReadOnlyProperty = element.GetType().GetProperty("IsReadOnly") == null ? false : true;
            
            // Make sure there is not a null in ControlName or Tag
            ctl.ControlName = ctl.ControlName ?? string.Empty;
            ctl.Tag = ctl.Tag ?? string.Empty;
            
            // Add control to be considered for security
            ControlsInContainer.Add(ctl);    
        }
        
        // Look for Child objects
        foreach (object child in LogicalTreeHelper.GetChildren(dep)) 
        {
            // Make recursive call
            LoadControlsInXAMLContainer(child);    
        }
    }
}

If this control is to be added to the collection, you first determine if the control has an IsReadOnly property. Next, you make sure that the Name and Tag properties don't contain a null, that instead they have an empty string. This just avoids a little extra checking later in your code. This control is then added to the ControlsInContainer property. Any controls without a Name or Tag won't be added to the collection because there's no way to match it to one of the controls to secure.

Each control can contain other controls, so loop through any child controls and make a recursive call back to the LoadControlsInXAMLContainer() method. In this way, you walk through the entire control tree within the reference to the control you passed to the SecureControls() method.

SecureControls Method

The SecureControls() method, Listing 9, is called from the code-behind of your WPF Window or User Control (or can be hooked to a command). After calling LoadControlsToSecure() and LoadControlsInXAMLContainer(), the code now loops through the ControlsToSecure collection and for each control, it searches for the ElementIdentifier as either a ControlName or Tag within the ControlsInContainer. If the control is found, it loops through all roles to see if the user is in one of them. If one of the roles is found, that control is skipped. If none of the roles are found, the control is made invisible, read-only, or disabled, based on the Mode property.

Listing 9: The SecureControls() method

public virtual void SecureControls(object element, string containerName)
{
    XAMLControlInfo ctl = null;
    CurrentPrincipal = Thread.CurrentPrincipal;
    
    // Get Controls to Secure from Data Store  
    LoadControlsToSecure(containerName);
    
    // Build List of Controls to be Secured  
    LoadControlsInXAMLContainer(element);
    
    // Loop through controls  
    foreach (SecurityControl secCtl in ControlsToSecure) 
    {
        secCtl.ElementIdentifier = secCtl.ElementIdentifier.ToLower();
        
        // Search for Name property
        ctl = ControlsInContainer.Find(c => c.ControlName.ToLower() == secCtl.ElementIdentifier);
        
        if (ctl == null) 
        {      
            // Search for Tag property
            ctl = ControlsInContainer.Find(c => c.Tag.ToLower() == secCtl.ElementIdentifier);    
        }    
        if (ctl != null && !string.IsNullOrWhiteSpace(secCtl.Mode)) 
        {
            // Loop through roles and see if user is NOT in one of the roles      
            // If not, change the state of the control      
            foreach (string role in secCtl.Roles) 
            {
                if (CurrentPrincipal.IsInRole(role)) 
                {
                    // They are in a role, so break out of loop          
                    break;
                }
                else 
                {
                    // They are NOT in a role so change the control state
                    ChangeState(ctl, secCtl.Mode);
                    
                    // Break out of loop because we have already modified the control state          
                    break;
                }
            }
        }
    }
}

ChangeState Method

If the user isn't in one of the roles for the current control, pass in the reference to the XAMLControlInfo object and the value in the Mode property to the ChangeState() method shown in Listing 10. Extract the reference to the control from TheControl property. The switch statement decides what to do to the control to secure based on the mode parameter passed in. If the value is “disabled”, for example, then the visibility of the control is turned back on and the IsEnabled property is set to False. If the value is “readonly”, and the control has an IsReadOnly property, that property is set to True, otherwise the IsEnabled property is set to True.

Listing 10: The ChangeState() method

protected virtual void ChangeState(XAMLControlInfo control, string mode)
{
    Control ctl = (Control)control.TheControl;
    
    switch (mode.ToLower()) 
    {
        case "disabled":
            ctl.Visibility = Visibility.Visible;
            ctl.IsEnabled = false;
            break;
        case "readonly":
        case "read only":
        case "read-only":
            ctl.Visibility = Visibility.Visible;
            ctl.IsEnabled = true;
            if (control.HasIsReadOnlyProperty) 
            {
                // Turn on IsReadOnly property
                ctl.GetType().GetProperty("IsReadOnly").SetValue(control.TheControl, true, null);
            }
            else
            {
                ctl.IsEnabled = false;
            }
            break;
        case "collapsed":
        case "collapse":
            ctl.Visibility = Visibility.Collapsed;
            break;
        case "hidden":
        case "invisible":
            ctl.Visibility = Visibility.Hidden;
            break;
    }
}

Try It Out

Run the application and you should see a screen that looks like Figure 5.

Figure 5: Employee information screen after applying security
Figure 5: Employee information screen after applying security

In the LoadControlsToSecure() method in Listing 7, you have a value of “Users123”. Replace that with “Users”. If you're a member of the “Users” group, the New button shows up when you rerun the WPF application.

Secure Controls Using Database Table

Instead of hard coding the controls to secure as you did in Listing 7, create a database table where you can store the controls to secure in your WPF application.

SecurityControl Table

Below is the T-SQL script to create a SecurityControl table in SQL Server. Add a SecurityControlId column that can be a primary key value. Make this column an integer data type that increments automatically using the IDENTITY property. Open an instance of SQL Server and run this script to create this table.

CREATE TABLE SecurityControl (
    SecurityControlId int IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,  
    ContainerName nvarchar(100) NOT NULL,  
    ElementIdentifier nvarchar(100) NOT NULL,  
    Mode nvarchar(20) NOT NULL,  
    RolesAsString nvarchar(1024) NOT NULL
)

After creating the SecurityControl table, enter the data shown in Figure 6 into this table.

Figure 6: Use different container names for each window or user control to secure.
Figure 6: Use different container names for each window or user control to secure.

WpfSecurityDbContext Class

Add the Entity Framework to your project using the NuGet package manager. Open the App.config file and add a connection string, as shown in the following code snippet. Modify the Server and Database attributes in the connection string as appropriate for your environment.

<connectionStrings>
    <add name="Sandbox" 
         connectionString="Server=Localhost; Database=Sandbox; Trusted_Connection=Yes;"      
         providerName="System.Data.SqlClient" />
</connectionStrings>

Add a new class to your project named WpfSecurityDbContextm, as shown in Listing 11. Modify the constructor to use the name of the connection string you added in the App.config file.

Listing 11: The WpfSecurityDbContext class used by EF to get security data from a database table

public class WpfSecurityDbContext : DbContext
{
    public WpfSecurityDbContext() : base("name=Sandbox") { }
    public virtual DbSet<SecurityControl> ControlsToSecure { get; set; }
    
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Do NOT let EF create migrations or check database for model consistency    
        Database.SetInitializer<WpfSecurityDbContext>(null);
        base.OnModelCreating(modelBuilder);  
    }
}

Modify SecurityControl Class

When you use the Entity Framework (EF), you need to decorate the class that maps to your table with a [Table] attribute. Open the SecurityControl.cs file and add a using statement to reference the namespace where this attribute is found.

using System.ComponentModel.DataAnnotations.Schema;

Add the [Table] attribute just about the class definition as shown in the code below. You also need to add a new property, SecurityControlId, to map to the primary key you added in the SecurityControl table.

[Table("SecurityControl")]
public class SecurityControl 
{  
    public int SecurityControlId { get; set; }    
    // REST OF THE PROPERTIES ARE HERE
}

SecurityControlManager Class

Instead of writing EF code to access the SecurityControl table in your view model class, add a new class named SecurityControlManager to your project. In this class, create a method named GetSecurityControls(), shown in Listing 12, to which you pass in the container name. The container name is used if you have two or more Window or User Control objects in your WPF application that you wish to secure. Add a unique container name to each control in your SecurityControl table, as shown in Figure 6.

Listing 12: The SecurityControlManager class gets the controls to secure from a table

public class SecurityControlManager
{
    public List<SecurityControl> GetSecurityControls(string containerName)
    {
        List<SecurityControl> ret = new List<SecurityControl>();
        
        using (WpfSecurityDbContext db = new WpfSecurityDbContext())
        {
            ret.AddRange(db.ControlsToSecure.Where(s => s.ContainerName == containerName).ToList());
        }
        
        return ret;  
    }
}

LoadControlsToSecure Method

Open the EmployeeViewModel class and modify the LoadControlsToSecure() method to create an instance of the SecurityControlManager class. Call the GetSecurityControls() method passing in the container name passed in to the SecureControls() method from the Window_Loaded() event procedure in the main window.

protected override void LoadControlsToSecure(string containerName)
{
    SecurityControlManager mgr = new SecurityControlManager();
    ControlsToSecure = mgr.GetSecurityControls(containerName);
}

Try It Out

Press F5 to run the WPF application and you should see the same controls turned off as before, when they were hard-coded.

Check for Binding Path

Normally, you don't have to name controls in WPF because you don't reference them from code-behind. The TextBox controls you want to affect with security have the Text property with a {Binding} expression in them. You can query the binding class attached to a control and retrieve the value of the Path property. This property can be used as the value in the ElementIdentifier property in your SecurityControl class. Consider the following TextBox control with a binding expression.

<TextBox Grid.Row="5" Grid.Column="1" Tag="SSN" Text="{Binding Path=SSN}" />

Both the Tag and Text properties have the value “SSN” that you can use to secure this control. Other controls in the EmployeeControl.xaml file also have a binding and either a Name or Tag property set. Write a method to retrieve the Path value so you can remove Name and Tag properties from the XAML where appropriate. Open the EmployeeControl.xaml file and remove the Name property from the “EmployeeID” TextBox control. Remove the Tag property from the “Salary” TextBox control. And remove the Tag property from the “SSN” TextBox control.

Add GetBindingName Method

Open the SecurityViewModelBase.cs file and add a new method to retrieve the value of the Path property from the Binding expression used on controls. The method named GetBindingName() shown in Listing 13 uses a switch statement to determine the type of control is passed to this method. For a TextBox, you want to get the binding expression from the Text property, so in the case statement for a text box you set the variable prop to the Dependency property TextBox.TextProperty. Each control uses a different Dependency property based on which property you bind to most often.

Listing 13: The GetBindingName() method

protected virtual string GetBindingName(Control ctl)
{
    string ret = string.Empty;
    BindingExpression exp;
    DependencyProperty prop;
    
    if (ctl != null) 
    {
        // Get the unique Dependency Property for each control that can be used to get a {Binding Path=xxx} expression    
        switch (ctl.GetType().Name.ToLower()) 
        {
            case "textbox":
                prop = TextBox.TextProperty;
                break;
            case "label":
                prop = Label.ContentProperty;
                break;
            case "menuitem":
                prop = MenuItem.HeaderProperty;
                break;
            ...  // MORE CONTROLS GO HERE
            default:
                // Add your own custom/third-party controls
                prop = GetBindingNameCustom(ctl);
                break;    
        }
        
        if (prop != null) 
        {
            // Get Binding Expression
            exp = ctl.GetBindingExpression(prop);
            
            // If we have a valid binding attempt to get the binding path
            if (exp != null && exp.ParentBinding != null && exp.ParentBinding.Path != null) 
            {
                if (!string.IsNullOrEmpty(exp.ParentBinding.Path.Path)) 
                {
                    ret = exp.ParentBinding.Path.Path;
                }
            }
        }  
    }
    
    if (!string.IsNullOrEmpty(ret)) 
    {    
        if (ret.Contains(".")) 
        {
            ret = ret.Substring(ret.LastIndexOf(".") + 1);    
        }  
    }
    
    return ret;
}

Call the GetBindingExpression() method on the control, passing in the Dependency property. If you get a value back from this method, you check to see if the ParentBinding.Path.Path property has a value in it. If it does, that's the name of the Path property you passed to the Binding expression.

When you're loading the controls from the XAML container, you set the BindingPath property by calling the GetBindingName() method. Add the code shown in the snippet below.

protected virtual void LoadControlsInXAMLContainer(object element)
{
    // REST OF THE CODE HERE
    
    // See if there are any data bindings    
    ctl.BindingPath = GetBindingName(element as Control);
    
    // REST OF THE CODE HERE  
}

GetBindingNameCustom() Method

If you're using any third-party controls, you need a method where you can enter those. In the “default” for the switch statement, call a GetBindingNameCustom() method shown below. In this method is where you can add your own custom controls and grab the appropriate Dependency property for that control.

protected virtual DependencyProperty GetBindingNameCustom(Control control) 
{
    DependencyProperty prop = null;
    
    // Add custom control binding expressions here  
    switch (control.GetType().Name.ToLower()) 
    {
        case "my_custom_control":
            // prop = ControlType.BindingProperty;
            break;  
    }
    
    return prop;
}

Modify the SecureControls Method

Because the BindingPath property can now be filled into the XAMLControlInfo class, you need to check that property to see if it matches the ElementIdentifier property in the ControlsToSecure collection. Open the SecurityViewModelBase class and locate the SecureControls() method. Within the loop where you search for a control by the name or tag, insert the code shown in bold in Listing 14.

Listing 14: Add code to look for a binding path that matches a control to secure

public virtual void SecureControls(object element, string containerName)
{
    // REST OF THE CODE HERE
    
    // Loop through controls  
    foreach (SecurityControl secCtl in ControlsToSecure) 
    {
        secCtl.ElementIdentifier = secCtl.ElementIdentifier.ToLower();
        
        // Search for Name property
        ctl = ControlsInContainer.Find(c => c.ControlName.ToLower() == secCtl.ElementIdentifier);
        if (ctl == null) 
        {
            // Search for BindingPath
            ctl = ControlsInContainer.Find(c => c.BindingPath.ToLower() == secCtl.ElementIdentifier);
        }
        
        // REST OF THE CODE HERE  
    }
}

Modify ConsiderForSecurity() Method

One last change to the code you need to make to support binding expressions is in the XAMLControlInfo class. Open the XAMLControlInfo.cs file and locate the ConsiderForSecurity() method. Add the line shown in bold to check if the BindingPath property has a value or not.

public bool ConsiderForSecurity()
{
    return !string.IsNullOrEmpty(BindingPath) || 
           !string.IsNullOrEmpty(ControlName) || 
           !string.IsNullOrEmpty(Tag);
}

Try It Out

Run the WPF application and if you've done everything correctly, you should see the same controls turned off just as they were before, even though you removed the Name and Tag properties.

Summary

In this article, you put together a data-driven security system for WPF. Using a data-driven approach allows you to change which controls to secure, generally without having to change any code. The code in this article does use reflection and thus can run a little slower; however, if you're loading a screen with data, most users won't even notice the extra half-second it might take to load the security information. You can also add some caching to this code to ensure that you only retrieve the security data one time. I've used code like this for years in my WPF applications and it's always worked well. I hope you can employ this code in your WPF applications when you need a security system.

Table 1: The list of properties/methods in the security view model base class

Property/MethodDescription
ControlsToSecureA list of controls you wish to secure within a WPF container
ControlsInContainerThe list of controls within a WPF container
CurrentPrincipalThe current IPrincipal object for the logged-in user
SecureControls()Call this method to secure all controls within a WPF container.
LoadControlsToSecure()This method loads the list of controls to secure.
LoadControlsInXAMLContainer()This method loads all controls within a WPF container.

Table 2: Set of sample security data stored in SecurityControl objects

ContainerNameElementIdentifierModeRoles
EmployeeControlNewButtonCollapsedUsers,Supervisor
EmployeeControlEmployeeIDReadOnlyAdmin,Supervisor
EmployeeControlSalaryHiddenAdmin
EmployeeControlSSNDisabledSupervisor
EmployeeControlSaveButtonDisabledAdmin,Supervisor