Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Configurable Aspects for WPF Application Using AOP Unity Container

4.33/5 (3 votes)
18 Oct 2011CPOL12 min read 20.9K   431  
Use AOP Unity Container to add AOP capabilities to a WPF application by configuration.

Introduction

The article AOP Container introduced an AOP model that enables any IoC container to have AOP capabilities by configuration. As examples, the model was applied to MEF, Unity, and Windsor Containers. In this article, I discuss how the AOP Container together with Unity Container is used to add AOP capabilities to a WPF application.

Background

It is quite easy to add aspects to business objects using an AOP Container. You define aspect methods, configure them in the application configuration file to associate them with business components, and use one of the customized AOP Containers to instantiate your business objects. The AOP Container attaches aspects to objects automatically based on the configuration settings.

WPF (Windows Presentation Foundation) provides some very powerful features to improve UI development experience, i.e., data binding between data sources and user controls, event processing between user actions and data sources. This powerfulness comes with dependencies and limits. In this article, I discuss how the AOP Container is used to add AOP capabilities to a WPF application and how to address issues between WPF and the business object model based on the AOP Container.

Problem

In this article, we try to build a rather complicated application to demonstrate component design, aspect design, and configuration, and address issues related to WPF application.

As functional (business) requirements, this application displays employees based on the selection of a department. A user can also modify existing employees, add new employees, or delete existing employees.

In addition to the above functional requirements, we also like the application to meet non-functional (system) requirements like logging and security checking. We also like to have some flexibility to sort employees by different criteria from a business point of view, e.g., sorting by employee's last name, by employee's birth day, etc.

The application development involves common tasks like business component design, aspect design and configuration, and tasks specific to WPF application like data binding and change notifications, etc.

Common Tasks

Common tasks are independent of an application type. They include business component design, aspect design, and configuration.

Business Components

The two business components, Employee and Department, are defined as follows. They are simple classes implementing the interfaces IEmployee and IDepartment, respectively.

C#
public interface IEmployee
{
    System.Int32? EmployeeID { get; set; }
    System.String FirstName { get; set; }
    System.String LastName { get; set; }
    System.DateTime DateOfBirth { get; set; }
    System.Int32? DepartmentID { get; set; }
    System.String DetailsByLevel(int iLevel);
    //1-full name; 2-full name plus birth day;
    //3-full name plus birth day and department id.
}

public class Employee : IEmployee
{
    #region Properties

    public System.Int32? EmployeeID { get; set; }
    public System.String FirstName { get; set; }
    public System.String LastName { get; set; }
    public System.DateTime DateOfBirth { get; set; }
    public System.Int32? DepartmentID { get; set; }

    #endregion

    public Employee(
        System.Int32? employeeid
        , System.String firstname
        , System.String lastname
        , System.DateTime bDay
        , System.Int32? departmentID
    )
    {
        this.EmployeeID = employeeid;
        this.FirstName = firstname;
        this.LastName = lastname;
        this.DateOfBirth = bDay;
        this.DepartmentID = departmentID;
    }

    public Employee() { }

    public System.String DetailsByLevel(int iLevel)
    {
        System.String s = "";
            
        switch (iLevel)
        {
            case 1:
                s = "Name:" + FirstName + " " + LastName + ".";
                break;

            case 2:
                s = "Name: " + FirstName + " " + LastName + 
                    ", Birth Day: " + DateOfBirth.ToShortDateString() + ".";
                break;

            case 3:
                s = "Name: " + FirstName + " " + LastName + ", Birth Day: " + 
                    DateOfBirth.ToShortDateString() + ", Department:" + 
                    DepartmentID.ToString() + ".";
                break;

            default:
                break;
                    
        }
        return s;
    }
}
C#
public interface IDepartment
{
    System.Int32? DepartmentID { get; set; }
    System.String Name { get; set; }
}

public class Department : IDepartment
{
    #region Properties

    public System.Int32? DepartmentID { get; set; }
    public System.String Name { get; set; }

    #endregion

    public Department(
        System.Int32? departmentid
        , System.String name
    )
    {
        this.DepartmentID = departmentid;
        this.Name = name;
    }

    public Department() { }
}

For Employee, there is a corresponding RepositoryEmployee component to contain a collection of objects of Employee. For Department, there is a corresponding RepositoryDepartment component to contain a collection of objects of Department. They are defined as follows.

C#
public interface IRepository<T>
{
    List<T> RepList { get; set; }
    void GetAll();

    DataSet DataSource { get; set; }
    IUnityContainer IocContainer { get; set; }
    AOPContainer AopContainer { get; set; }
}

public class RepositoryEmployee : IRepository<IEmployee>
{
    public RepositoryEmployee()
    {
    }

    private List<IEmployee> myList = null;
    public List<IEmployee> RepList
    {
        get { return myList; }
        set { myList = value; }
    }

    private IUnityContainer container = null;
    public IUnityContainer IocContainer
    {
        get { return container; }
        set { container = value; }
    }

    private AOPContainer aopcontainer = null;
    public AOPContainer AopContainer
    {
        get { return aopcontainer; }
        set { aopcontainer = value; }
    }

    private DataSet ds = null;
    public DataSet DataSource
    {
        get { return ds; }
        set { ds = value; }
    }

    public void GetAll()
    {
        System.Data.DataTable dt = ds.Tables["Employee"];

        myList = container.Resolve<List<IEmployee>>();
        foreach (System.Data.DataRow r in dt.Rows)
        {
            IEmployee iEmp = aopcontainer.Resolve<Employee, IEmployee>();
            iEmp.EmployeeID = Convert.ToInt32(r["EmployeeID"]);
            iEmp.FirstName = r["FirstName"].ToString();
            iEmp.LastName = r["LastName"].ToString();
            iEmp.DateOfBirth = Convert.ToDateTime(r["DateOfBirth"]);
            iEmp.DepartmentID = Convert.ToInt32(r["DepartmentID"]);

            myList.Add(iEmp);
        }
    }
}

public class RepositoryDepartment : IRepository<IDepartment>
{
    public RepositoryDepartment()
    {
    }

    private List<IDepartment> myList = null;
    public List<IDepartment> RepList
    {
        get { return myList; }
        set { myList = value; }
    }

    private IUnityContainer container = null;
    public IUnityContainer IocContainer
    {
        get { return container; }
        set { container = value; }
    }

    private AOPContainer aopcontainer = null;
    public AOPContainer AopContainer
    {
        get { return aopcontainer; }
        set { aopcontainer = value; }
    }

    private DataSet ds = null;
    public DataSet DataSource
    {
        get { return ds; }
        set { ds = value; }
    }

    public void GetAll()
    {
        System.Data.DataTable dt = ds.Tables["Department"];

        myList = container.Resolve<List<IDepartment>>();
        foreach (System.Data.DataRow r in dt.Rows)
        {
            IDepartment dep = aopcontainer.Resolve<Department, IDepartment>();
            dep.DepartmentID = Convert.ToInt32(r["DepartmentID"]);
            dep.Name = r["Name"].ToString();

            myList.Add(dep);
        }
    }
}

Note that the data set is passed in using the DataSource property of IRepository<T> for both RepositoryEmployee and RepositoryDepartment. RepositoryEmployee uses data from DataTable Employee of the data set while RepositoryDepartment uses data from DataTable Department of the data set.

You can use either container or aopcontainer to create an object. The difference between them is that an object created by aopcontainer may have aspects attached while an object created by container has no aspects.

Aspect Design

Aspects are cross-cutting concerns. For an AOP Container, an aspect is a method that takes an AspectContext type object as its first parameter and an object[] type object as its second parameter and returns void.

You may design your aspects in a more general way to use them in various situations. You may also design your aspects for some particular situations. For example, you can define your entering/exiting log aspects in a general way and put them in the class SharedConcerns as follows.

C#
public class SharedConcerns
{
    static SharedConcerns()
    {
        ConcernsContainer.runtimeAspects.Add("SharedLib.SharedConcerns.EnterLog", 
                          new Decoration(SharedConcerns.EnterLog, null));
        ConcernsContainer.runtimeAspects.Add("SharedLib.SharedConcerns.ExitLog", 
                          new Decoration(SharedConcerns.ExitLog, null));
    }

    public static void EnterLog(AspectContext ctx, object[] parameters)
    {
        StackTrace st = new StackTrace(new StackFrame(4, true));
        Console.Write(st.ToString());
            
        IMethodCallMessage method = ctx.CallCtx;
        string str = "Entering " + ctx.Target.GetType().ToString() + 
                     "." + method.MethodName + "(";
        int i = 0;
        foreach (object o in method.Args)
        {
            if (i > 0)
                str = str + ", ";
            str = str + o.ToString();
        }
        str = str + ")";

        Console.WriteLine(str);
        Console.Out.Flush();

    }

    public static void ExitLog(AspectContext ctx, object[] parameters)
    {
        IMethodCallMessage method = ctx.CallCtx;
        string str = "Exiting " + ctx.Target.GetType().ToString() + 
                     "." + method.MethodName + "(";
        int i = 0;
        foreach (object o in method.Args)
        {
            if (i > 0)
                str = str + ", ";
            str = str + o.ToString();
        }
        str = str + ")";

        Console.WriteLine(str);
        Console.Out.Flush();
    }
}

As you can see, these methods access Target and CallCtx in a general way, and can be shared by various types of objects to write entering/exiting logs.

On the other hand, some aspects may need to access more specific information. For example, you want to use WindowsPrincipal for security checking. The following code defines some specific aspects.

C#
public class AppConcerns
{
    class EmployeeAgeComparer : IComparer<IEmployee>
    {
        public int Compare(IEmployee e1, IEmployee e2)
        {
            return DateTime.Compare(e1.DateOfBirth, e2.DateOfBirth);
        }
    }

    class EmployeeLastnameComparer : IComparer<IEmployee>
    {
        public int Compare(IEmployee e1, IEmployee e2)
        {
            return String.Compare(e1.LastName, e2.LastName);
        }
    }

    static AppConcerns()
    {
        ConcernsContainer.runtimeAspects.Add(
          "WpfApplication.AppConcerns.SecurityCheck", 
          new Decoration(AppConcerns.SecurityCheck, null));
        ConcernsContainer.runtimeAspects.Add(
          "WpfApplication.AppConcerns.SortEmployeeByLastname", 
          new Decoration(AppConcerns.SortEmployeeByLastname, null));
        ConcernsContainer.runtimeAspects.Add(
          "WpfApplication.AppConcerns.SortEmployeeByBirthday", 
          new Decoration(AppConcerns.SortEmployeeByBirthday, null));
    }

    public static void SecurityCheck(AspectContext ctx, object[] parameters)
    {
        Exception exInner = null;

        try
        {
            if (parameters != null && parameters[0] is WindowsPrincipal && 
               ((WindowsPrincipal)parameters[0]).IsInRole("BUILTIN\\" + 
                 "Administrators"))
            {
                return;
            }
        }
        catch (Exception ex)
        {
            exInner = ex;
        }

        throw new Exception("No right to call!", exInner);
    }

    public static void SortEmployeeByLastname(AspectContext ctx, object[] parameters)
    {
        object target = ctx.Target;
        if (target.GetType().ToString() == "WpfApplication.RepositoryEmployee")
        {
            List<IEmployee> emps = ((IRepository<IEmployee>)target).RepList;
            IEnumerable<IEmployee> query = emps.OrderByDescending(emp => emp,
                new EmployeeLastnameComparer()).ToList<IEmployee>();
            ((IRepository<IEmployee>)target).RepList = (List<IEmployee>)query;
        }
    }

    public static void SortEmployeeByBirthday(AspectContext ctx, object[] parameters)
    {
        object target = ctx.Target;
        if (target.GetType().ToString() == "WpfApplication.RepositoryEmployee")
        {
            List<IEmployee> emps = ((IRepository<IEmployee>)target).RepList;
            IEnumerable<IEmployee> query = emps.OrderByDescending(emp => emp,
                new EmployeeAgeComparer()).ToList<IEmployee>();
            ((IRepository<IEmployee>)target).RepList = (List<IEmployee>)query;
        }
    }
}

In the above code, the SecurityCheck method can only check WindowsPrincipal for security while SortEmployeeByLastname and SortEmployeeByBirthday can only be used by a target of RepositoryEmployee.

Note: You may already have noticed that there is a static constructor in each of the classes SharedConcerns and AppConcerns. Inside the static classes, each of the aspect methods defined in the classes is used to create an instance of Decoration which is then added to a dictionary ConcernsContainer.runtimeAspects. The definition of ConcernsContainer is as follows.

C#
public class ConcernsContainer
{
    static public Dictionary<string, Decoration> runtimeAspects = 
           new Dictionary<string, Decoration>();
}

The purpose of this Dictionary and the static constructors is to keep an inventory for all Decoration objects based on the aspect methods defined in the application and make them accessible by the corresponding method names. It makes possible to configure aspects in the application configuration file by specifying the corresponding method names.

Aspect Configuration

In the configuration file, you specify how aspects are associated with business components. Here are some examples to demonstrate how the aspects are configured.

XML
<configuration>
    <configSections>
        <section name="DynamicDecoratorAspect" 
          type="DynamicDecoratorAOP.Configuration.DynamicDecoratorSection,
                DynamicDecoratorAOP.Configuration" />
    </configSections>

    <DynamicDecoratorAspect>
        <objectTemplates>
            <add name="1"
                 type="WpfApplication.RepositoryDepartment"
                 interface="WpfApplication.IRepository`1[ThirdPartyHR.IDepartment]"
                 methods="GetAll"
                 predecoration="SharedLib.SharedConcerns.EnterLog,SharedLib"
                 postdecoration="SharedLib.SharedConcerns.ExitLog,SharedLib" />
            <add name="2"
                 type="WpfApplication.RepositoryEmployee"
                 interface="WpfApplication.IRepository`1[ThirdPartyHR.IEmployee]"
                 methods="GetAll"
                 predecoration=""
                 postdecoration="WpfApplication.AppConcerns.SortEmployeeByLastname" />
            <add name="3"
                 type="ThirdPartyHR.Department"
                 interface="ThirdPartyHR.IDepartment"
                 methods="DetailsByLevel,get_DepartmentID"
                 predecoration="SharedLib.SharedConcerns.EnterLog,SharedLib"
                 postdecoration="" />
            <!--<add name="4"
                 type="ThirdPartyHR.Employee"
                 interface="ThirdPartyHR.IEmployee"
                 methods="set_EmployeeID,set_FirstName,
                          set_LastName,set_DateOfBirth,set_DepartmentID"
                 predecoration="WpfApplication.AppConcerns.SecurityCheck"
                 postdecoration="" />-->
        </objectTemplates>
    </DynamicDecoratorAspect>
</configuration>

First of all, you need to add a section <DynamicDecoratorAspect> in your configuration file. You also need to add an applicationPath key to the <appSettings> to specify the physical path of the Web application. It is required for a Web application since a Web application path is different from the execution program path. Then, inside <objectTemplates> of <DynamicDecoratorAspect>, you add individual elements. For each element inside <objectTemplates>, the following attributes need to be specified:

  • type - target type
  • interface - interface returned
  • methods - names of target methods which will be attached to the aspects specified by predecoration and postdecoration
  • predecoration - preprocessing aspect
  • postdecoration - postprocessing aspect

Notes:

  1. The names in the value of the methods attribute are comma separated. For example, "DetailsByLevel,get_EmployeeID".
  2. The value of the predecoration attribute has two parts and is separated by a comma. The first part specifies the aspect name while the second part specifies the assembly name in which the aspect is defined, for example, "SharedLib.SharedConcerns.EnterLog,SharedLib". If the second part is not specified, it is assumed that the aspect is defined in the entry assembly, for example, "WpfApplication.AppConcerns.SecurityCheck".
  3. The value of the postdecoration attribute has two parts and is separated by a comma. The first part specifies the aspect name while the second part specifies the assembly name in which the aspect is defined. If the second part is not specified, it is assumed that the aspect is defined in the entry assembly.

WPF Application Tasks

Before we apply the above business object model to a WPF application, we need to address several issues. WPF has limits on the binding source (Binding Sources Overview). One issue is that WPF binding fails to work with interface proxies (see WPF binding to a Windsor proxied component and NHibernate, PropertyChanged event, and WPF). So, it fails to work with the AOP Container which is based on interface proxies. Another issue is that to provide change notifications, a binding source must implement the INotifyPropertyChanged interface. Most types of business components do not implement this interface. Therefore, the business object model developed above can not be used by a WPF application directly.

Intermediate Classes

To get around the above limits, intermediate classes are created to wrap our business objects. The intermediate classes should meet all the requirements of a WPF data source while delegating all requests to the wrapped business objects. EmployeeProxy and DepartmentProxy are the intermediate classes for business components Employee and Department, respectively, and are displayed as follows.

C#
public class EmployeeProxy : IEmployee, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    private IEmployee emp;
    static public AOPContainer aopcontainer = null;

    #region Properties
    public System.Int32? EmployeeID 
    {
        get { return emp.EmployeeID; }
        set { emp.EmployeeID = value; OnPropertyChanged("EmployeeID"); }
    }

    public System.String FirstName
    {
        get { return emp.FirstName; }
        set { emp.FirstName = value; OnPropertyChanged("FirstName"); }
    }

    public System.String LastName 
    {
        get { return emp.LastName; }
        set { emp.LastName = value; OnPropertyChanged("LastName"); }
    }

    public System.DateTime DateOfBirth 
    {
        get { return emp.DateOfBirth; }
        set { emp.DateOfBirth = value; OnPropertyChanged("DateOfBirth"); }
    }

    public System.Int32? DepartmentID
    {
        get { return emp.DepartmentID; }
        set { emp.DepartmentID = value; OnPropertyChanged("DepartmentID"); }
    }

    #endregion

    public System.String DetailsByLevel(int iLevel)
    {
        return emp.DetailsByLevel(iLevel);
    }

    public EmployeeProxy(IEmployee empOrig)
    {
        this.emp = empOrig;
    }

    public EmployeeProxy() 
    {
        emp = aopcontainer.Resolve<Employee, IEmployee>();
    }
}

EmployeeProxy is a Proxy Pattern for Employee. It provides the same interface as the original business object empOrig and delegates all calls to the corresponding methods of the original object. It also implements INotifyPropertyChanged so that whenever a property changes, the bound WPF control will be notified. It also has a static public instance aopcontainer of type AOPContainer. The aopcontainer is used to create a new business object in the default constructor. The default constructor is called by a bound WPF user control when adding a new entry from the user interface.

Instead of explicitly putting an OnPropertyChanged call in each of the property set methods, PostSharp can be used to inject the code in each of the property set methods. I am not going to discuss it in detail here. However, the download AopContainerWpfWithPostSharp.zip uses PostSharp to inject the call to the property set methods. You will need to install PostSharp to get it to work.

C#
public class DepartmentProxy : IDepartment
{
    #region Properties

    public System.Int32? DepartmentID
    {
        get { return dep.DepartmentID; }
        set { dep.DepartmentID = value; }
    }

    public System.String Name
    {
        get { return dep.Name; }
        set { dep.Name = value; }
    }

    #endregion

    private IDepartment dep;
    public DepartmentProxy(IDepartment depOrig)
    {
        this.dep = depOrig;
    }
}

DepartmentProxy is a Proxy Pattern for Department, which provides the same interface as the original business object depOrig. However, it doesn't implement the INotifyPropertyChanged interface, which means it will not send change notifications to the bound WPF user control. It doesn't have a default constructor, which means the bound WPF user control will not be able to add a new entry from the user interface.

Now, we are ready to work on the WPF application.

Application Class

A few application wide setups/cleanups need to be addressed at the application level. Here is the code of the App class:

C#
public partial class App : Application
{
    private FileStream fileStream = null;
    private TextWriter tmp = null;
    private StreamWriter sw1 = null;

    static public IUnityContainer iocContainer = null;
    static public AOPUnityContainer aopContainer = null;

    void App_Startup(object sender, StartupEventArgs e)
    {
        string path = ".";
        if (!File.Exists(path + "\\hrlog.txt"))
        {
            fileStream = new FileStream(path + "\\hrlog.txt", FileMode.Create);
        }
        else
            fileStream = new FileStream(path + "\\hrlog.txt", FileMode.Truncate);

        tmp = Console.Out;
        sw1 = new StreamWriter(fileStream);
        Console.SetOut(sw1);

        App.iocContainer = new UnityContainer();
        App.iocContainer.RegisterType<IEmployee, Employee>(new InjectionConstructor());
        App.iocContainer.RegisterType<IDepartment, 
            Department>(new InjectionConstructor());
        App.iocContainer.RegisterType<IRepository<IDepartment>, 
            RepositoryDepartment>(new InjectionConstructor());
        App.iocContainer.RegisterType<IRepository<IEmployee>, 
            RepositoryEmployee>(new InjectionConstructor());

        App.iocContainer.RegisterType<IList<IEmployee>, 
            List<IEmployee>>(new InjectionConstructor());
        App.iocContainer.RegisterType<IList<IDepartment>, 
             List<IDepartment>>(new InjectionConstructor());

        App.aopContainer = new AOPUnityContainer(App.iocContainer);

        //Commenting out this line will throw out an exception
        Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
        Decoration dec = 
          ConcernsContainer.runtimeAspects["WpfApplication.AppConcerns.SecurityCheck"];
        dec.Parameters = new object[] { Thread.CurrentPrincipal };
    }

    void App_Exit(object sender, ExitEventArgs e)
    {
        Console.SetOut(tmp);
        sw1.Close();
        fileStream.Close();

        IDisposable container = App.iocContainer;
        container.Dispose();
    }
}

In App_Startup, first, we redirect the console output to a file. This way, the aspects EnterLog and ExitLog will write logs to the file instead of the console. Then, we create a UnityContainer object and register a few types to it. An AOPUnityContainer object is also created. Both the UnityContainer object and the AOPUnityContainer are assigned to the corresponding public static members, respectively, and will be used in other places of the application. We also set the Parameters property of the SecurityCheck aspect to WindowsPrincipal so that we can check security during the execution of the application.

The code in App_Exit does some cleanups.

Main Window

The Main Window is the GUI for the application. Its XAML code is listed as follows.

XML
<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="402*" />
            <ColumnDefinition Width="101*" />
        </Grid.ColumnDefinitions>
        <ComboBox Height="23" 
            HorizontalAlignment="Left" Margin="34,33,0,0" 
            Name="comboBox1" VerticalAlignment="Top" Width="120"
            SelectionChanged="comboBox1_SelectionChanged"/>
        <DataGrid AutoGenerateColumns="False" Height="200" 
                 HorizontalAlignment="Left" Margin="34,82,0,0" 
                 Name="dataGrid1" VerticalAlignment="Top" 
                 Width="435" Grid.ColumnSpan="2" 
                 CanUserAddRows="True">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Employee ID" 
                     Binding="{Binding EmployeeID}" />
                <DataGridTextColumn Header="First Name" 
                     Binding="{Binding FirstName}" />
                <DataGridTextColumn Header="Last Name" 
                     Binding="{Binding LastName, Mode=TwoWay}" />
                <DataGridTextColumn Header="Birth Date" 
                     Binding="{Binding DateOfBirth}" />
                <DataGridTextColumn Header="DepartmentID" 
                     Binding="{Binding DepartmentID}" />
            </DataGrid.Columns>
        </DataGrid>
        <Label Content="Departments" Height="28" 
           HorizontalAlignment="Left" Margin="34,12,0,0" 
           Name="label1" VerticalAlignment="Top" 
           Width="84" IsEnabled="True" />
        <Button Content="PropertyChanged" Height="23" 
           HorizontalAlignment="Left" Margin="188,33,0,0" 
           Name="button1" VerticalAlignment="Top" 
           Width="106" Click="button1_Click" />
        <Button Content="CollectionChanged" Grid.ColumnSpan="2" 
           Height="23" HorizontalAlignment="Left" 
           Margin="330,35,0,0" Name="button2" 
           VerticalAlignment="Top" Width="139" 
           Click="button2_Click" />
        <Button Content="Save" Height="23" 
           HorizontalAlignment="Left" Margin="204,288,0,0" 
           Name="button3" VerticalAlignment="Top" 
           Width="75" Click="button3_Click" />
        <Label Content="Employees" Height="28" 
           HorizontalAlignment="Left" Margin="34,60,0,0" 
           Name="label2" VerticalAlignment="Top" Width="84" />
    </Grid>
</Window>

The MainWindow has a ComboBox to list the departments and a DataGrid to list employees. It has also three Buttons for testing property change, collection change, and saving changes, respectively.

The C# code for the MainWindow is listed as follows.

C#
public partial class MainWindow : Window
{
    private ObservableCollection<EmployeeProxy> lstEmp = null;

    private static int iStaticDep = 0;
    DataSet dsDB = new DataSet();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        try
        {
            dsDB.ReadXml("employees.xml");

            IRepository<IDepartment> rpDepartment = 
              App.aopContainer.Resolve<RepositoryDepartment, IRepository<IDepartment>>();
            rpDepartment.IocContainer = App.iocContainer;
            rpDepartment.AopContainer = App.aopContainer;
            rpDepartment.DataSource = dsDB;

            rpDepartment.GetAll();
            List<DepartmentProxy> lstDep = new List<DepartmentProxy>();
            foreach (IDepartment d in rpDepartment.RepList)
            {
                lstDep.Add(new DepartmentProxy(d));
            }
            comboBox1.ItemsSource = lstDep;
            comboBox1.DisplayMemberPath = "Name";
            comboBox1.SelectedValuePath = "DepartmentID";

            IRepository<IEmployee> rpEmployee = 
              App.aopContainer.Resolve<RepositoryEmployee, IRepository<IEmployee>>();
            rpEmployee.IocContainer = App.iocContainer;
            rpEmployee.AopContainer = App.aopContainer;
            rpEmployee.DataSource = dsDB;

            rpEmployee.GetAll();
            EmployeeProxy.aopcontainer = App.aopContainer;
            lstEmp = new ObservableCollection<EmployeeProxy>();
            foreach (IEmployee em in rpEmployee.RepList)
            {
                lstEmp.Add(new EmployeeProxy(em));
            }
            dataGrid1.ItemsSource = lstEmp;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (comboBox1.SelectedValue != null)
        {
            IDepartment dpSel = (IDepartment)comboBox1.SelectedItem;

            iStaticDep = dpSel.DepartmentID.Value;
            List<EmployeeProxy> empSel = null;
            if (lstEmp != null)
            {
                empSel = lstEmp.ToList<EmployeeProxy>().FindAll(
                    (EmployeeProxy emp) => { return emp.DepartmentID.Value == iStaticDep; });
            }

            dataGrid1.ItemsSource = new ObservableCollection<EmployeeProxy>(empSel);
        }
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            IEmployee oEm = lstEmp[0];
            oEm = ObjectProxyFactory.CreateProxy<IEmployee>(
                oEm,
                new String[] { "set_EmployeeID" },
                ConcernsContainer.runtimeAspects["WpfApplication.AppConcerns.SecurityCheck"],
                null);

            if (lstEmp[0].EmployeeID == 100)
                oEm.EmployeeID = 1;
            else
                oEm.EmployeeID = 100;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    private void button2_Click(object sender, RoutedEventArgs e)
    {
        EmployeeProxy empCollectionChanged = null;

        empCollectionChanged = lstEmp.ToList<EmployeeProxy>().Find(
             (EmployeeProxy em) => { return em.EmployeeID.Value == 1001; });
        if (empCollectionChanged != null)
            lstEmp.Remove(empCollectionChanged);
        else
        {
            empCollectionChanged = new EmployeeProxy();
            empCollectionChanged.EmployeeID = 1001;
            empCollectionChanged.FirstName = "John";
            empCollectionChanged.LastName = "Doe";
            empCollectionChanged.DateOfBirth = new DateTime();
            empCollectionChanged.DepartmentID = 1;

            lstEmp.Add(empCollectionChanged);
        }
    }

    private void button3_Click(object sender, RoutedEventArgs e)
    {
        System.Data.DataSet ds = dsDB;
        System.Data.DataTable dt = ds.Tables["Employee"];
        dt.Clear();

        foreach (EmployeeProxy emp in lstEmp)
        {
            System.Data.DataRow r = dt.NewRow();
            r["EmployeeID"] = emp.EmployeeID == null ? -1 : emp.EmployeeID;
            r["FirstName"] = emp.FirstName == null ? "" : emp.FirstName;
            r["LastName"] = emp.LastName == null ? "" : emp.LastName;
            r["DateOfBirth"] = emp.DateOfBirth;
            r["DepartmentID"] = emp.DepartmentID == null ? -1 : emp.DepartmentID;
            dt.Rows.Add(r);
        }
        dsDB.WriteXml("employees.xml");
    }
}

In the event handler Window_Loaded, an XML file employees.xml is loaded into the DataSet dsDB. This XML file persists data for departments and employees and plays a database role for the application. Of course, you can use a real database and its tables to persist data and load them into the DataSet.

Note that rpDepartment.RepList is a list of interface proxies, we need to convert it to a list of DepartmentProxy objects and bind the list of DepartmentProxy to comboBox1. For the same reason, we have to convert rpEmployee.RepList to a list of EmployeeProxy objects and bind the list of EmployeeProxy to dataGrid1. Note also that a ObservableCollection<EmployeeProxy> is assigned to dataGrid1, which means notifications will be generated when items get added to or removed from the collection.

The event handler comboBox1_SelectionChanged is executed when a department is selected. Then, the employees associated with the department are displayed.

The event handler button1_Click is executed when pressing the PropertyChanged button. The code toggles the first employee's EmployeeID between 1 and 100. Note that ObjectProxyFactory.CreateProxy is called to add the security check before changing the EmployeeID. The important point here is that you can chain aspects to object in the code no matter whether it is configured in the configuration file or not. The best practice for this scenario is that since your application may have a lot of employees and an employee may have a lot of properties, it is better to add aspects to an employee object using code as needed instead of configuring aspects to the employee component in the configuration file.

The event handler button2_Click is executed when pressing the CollectionChanged button. The code simply adds or removes an employee from the employee collection with EmployeeID 1001.

The event handler button3_Click is executed when pressing the Save button. The code persists changes of employees to the "employees.xml" file.

Run the application, the MainWindow looks as follows:

Image 1

When a department is selected, the employees for that department are displayed.

Image 2

Close the application and open the file hrlog.txt. You will see the following logs:

   at WpfApplication.MainWindow.Window_Loaded(Object sender, RoutedEventArgs e) 
           in C:\AopContainerWpf\WpfApplication\MainWindow.xaml.cs:line 146
Entering WpfApplication.RepositoryDepartment.GetAll()
Exiting WpfApplication.RepositoryDepartment.GetAll()
   at WpfApplication.DepartmentProxy.get_DepartmentID() in 
           C:\AopContainerWpf\WpfApplication\Model\DepartmentProxy.cs:line 14
Entering ThirdPartyHR.Department.get_DepartmentID()
   at WpfApplication.DepartmentProxy.get_DepartmentID() in 
          C:\AopContainerWpf\WpfApplication\Model\DepartmentProxy.cs:line 14
Entering ThirdPartyHR.Department.get_DepartmentID()

Run it again. Press the PropertyChanged button, the EmployeeID of the first record is set to 100. The MainWindow is updated to show the change as follows.

Image 3

Press the CollectionChanged button, a new employee record is added with EmployeeID 1001. The MainWindow is updated to show the change as follows.

Image 4

Close the application and open the file hrlog.txt. You will see the following logs.

   at WpfApplication.MainWindow.Window_Loaded(Object sender, RoutedEventArgs e) 
             in C:\AopContainerWpf\WpfApplication\MainWindow.xaml.cs:line 146
Entering WpfApplication.RepositoryDepartment.GetAll()
Exiting WpfApplication.RepositoryDepartment.GetAll()

Now, comment out the line Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); in App_Startup of the App class. Build and run the application. When the MainWindow is up, click the PropertyChanged button, and you will see the following message:

Image 5

You were not allowed to change the EmployeeID since you failed the security check.

As you may have noticed, the above application sorts the employee by the employee's last name. What if you want to sort employees by their birthday? You change the configuration entry for WpfApplication.RepositoryEmployee by replacing WpfApplication.AppConcerns.SortEmployeeByLastname with WpfApplication.AppConcerns.SortEmployeeByBirthday. That's it. Now, your application sorts employees by their birthday.

Points of Interest

AOP Container makes extending functionality of business components or adding AOP capabilities to them very easy. You define your aspect methods, and configure them in the configuration file to associate aspects to business components.

AOP Container makes component design, aspect design, and application design completely decoupled. Configuration settings and global settings wire all the pieces together. Changing/Extending functionality may be as simple as changing configuration settings.

You can also mix adding aspects using configuration and adding aspects using code. This way, you can use aspect configuration for general purpose while using code to chain more aspects to meet your specific requirements as needed.

The Proxy Pattern is a neat way to apply the power of WPF to business objects based on interface interception technology.

Although this article discusses AOP container with Unity IoC container, you should be able to easily combine an AOP Container with other IoC containers like MEF and Windsor.

License

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