65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 AOP Unity 容器配置 WPF 应用程序的方面

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (3投票s)

2011 年 10 月 18 日

CPOL

12分钟阅读

viewsIcon

21204

downloadIcon

456

使用 AOP Unity 容器通过配置为 WPF 应用程序添加 AOP 功能。

引言

文章 AOP Container 介绍了一种 AOP 模型,该模型使任何 IoC 容器都可以通过配置获得 AOP 功能。例如,该模型已应用于 MEFUnityWindsor 容器。在本文中,我将讨论如何将 AOP Container 与 Unity Container 一起用于为 WPF 应用程序添加 AOP 功能。

背景

使用 AOP Container 为业务对象添加方面非常容易。您可以定义方面方法,在应用程序配置文件中进行配置以将它们与业务组件关联,并使用自定义的 AOP Container 来实例化您的业务对象。AOP Container 根据配置设置自动将方面附加到对象。

WPF (Windows Presentation Foundation) 提供了一些非常强大的功能来改善 UI 开发体验,即数据源与用户控件之间的数据绑定,以及用户操作与数据源之间的事件处理。这种强大功能伴随着依赖关系和限制。在本文中,我将讨论如何使用 AOP Container 为 WPF 应用程序添加 AOP 功能,以及如何基于 AOP Container 解决 WPF 与业务对象模型之间的问题。

问题

在本文中,我们尝试构建一个相当复杂的应用程序来演示组件设计、方面设计和配置,并解决与 WPF 应用程序相关的问题。

作为功能(业务)需求,该应用程序根据部门选择显示员工。用户还可以修改现有员工、添加新员工或删除现有员工。

除了上述功能需求外,我们还希望应用程序满足非功能(系统)需求,如日志记录和安全检查。我们还希望从业务角度来看,能够灵活地按不同标准对员工进行排序,例如按员工姓氏、按员工生日等。

应用程序开发涉及常见任务,如业务组件设计、方面设计和配置,以及 WPF 应用程序特有的任务,如数据绑定和更改通知等。

常见任务

常见任务与应用程序类型无关。它们包括业务组件设计、方面设计和配置。

业务组件

两个业务组件 EmployeeDepartment 定义如下。它们是简单的类,分别实现了 IEmployeeIDepartment 接口。

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;
    }
}
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() { }
}

对于 Employee,有一个对应的 RepositoryEmployee 组件,其中包含 Employee 对象的集合。对于 Department,有一个对应的 RepositoryDepartment 组件,其中包含 Department 对象的集合。它们定义如下。

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

请注意,数据源是通过 IRepository<T>DataSource 属性传入 RepositoryEmployeeRepositoryDepartment 的。RepositoryEmployee 使用数据集的 DataTable Employee 中的数据,而 RepositoryDepartment 使用数据集的 DataTable Department 中的数据。

您可以使用 containeraopcontainer 来创建对象。它们之间的区别在于,由 aopcontainer 创建的对象可能附加了方面,而由 container 创建的对象则没有方面。

方面设计

方面是横切关注点。对于 AOP Container,方面是一个方法,它接受一个 AspectContext 类型的对象作为第一个参数,一个 object[] 类型的对象作为第二个参数,并返回 void

您可以更通用地设计您的方面,以便在各种情况下使用它们。您也可以为某些特定情况设计您的方面。例如,您可以通用地定义进入/退出日志方面,并将它们放入 SharedConcerns 类中,如下所示。

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();
    }
}

正如您所见,这些方法以通用方式访问 TargetCallCtx,并且可以被各种对象类型共享,以编写进入/退出日志。

另一方面,某些方面可能需要访问更具体的信息。例如,您想使用 WindowsPrincipal 进行安全检查。以下代码定义了一些特定的方面。

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

在上面的代码中,SecurityCheck 方法只能检查 WindowsPrincipal 的安全性,而 SortEmployeeByLastnameSortEmployeeByBirthday 只能由 RepositoryEmployee 的目标使用。

注意:您可能已经注意到,SharedConcernsAppConcerns 类中都有一个静态构造函数。在静态类中,类中定义的每个方面方法都用于创建 Decoration 的实例,然后将其添加到 ConcernsContainer.runtimeAspects 字典中。ConcernsContainer 的定义如下。

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

这个 Dictionary 和静态构造函数的目的是根据应用程序中定义的方面方法,为所有 Decoration 对象维护一个清单,并使它们可以通过相应的方法名进行访问。这使得可以通过在应用程序配置文件中指定相应的方法名来配置方面。

方面配置

在配置文件中,您指定方面如何与业务组件关联。以下是一些示例,说明了方面的配置方式。

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

首先,您需要在配置文件中添加一个 <DynamicDecoratorAspect> 部分。您还需要在 <appSettings> 中添加一个 applicationPath 键,以指定 Web 应用程序的物理路径。对于 Web 应用程序来说,这是必需的,因为 Web 应用程序的路径与执行程序路径不同。然后,在 <DynamicDecoratorAspect><objectTemplates> 中,添加单独的元素。对于 <objectTemplates> 中的每个元素,需要指定以下属性

  • type - 目标类型
  • interface - 返回的接口
  • methods - 将附加到 predecorationpostdecoration 指定的方面目标方法的名称
  • predecoration - 预处理方面
  • postdecoration - 后处理方面

注释

  1. methods 属性值中的名称用逗号分隔。例如,"DetailsByLevel,get_EmployeeID"
  2. predecoration 属性的值包含两部分,用逗号分隔。第一部分指定方面名称,第二部分指定定义该方面的程序集名称,例如 "SharedLib.SharedConcerns.EnterLog,SharedLib"。如果未指定第二部分,则假定该方面定义在入口程序集中,例如 "WpfApplication.AppConcerns.SecurityCheck"
  3. postdecoration 属性的值包含两部分,用逗号分隔。第一部分指定方面名称,第二部分指定定义该方面的程序集名称。如果未指定第二部分,则假定该方面定义在入口程序集中。

WPF 应用程序任务

在将上述业务对象模型应用于 WPF 应用程序之前,我们需要解决几个问题。WPF 对绑定源有限制(Binding Sources Overview)。一个问题是 WPF 绑定无法与接口代理一起工作(请参阅 WPF binding to a Windsor proxied componentNHibernate, PropertyChanged event, and WPF)。因此,它无法与基于接口代理的 AOP Container 一起工作。另一个问题是,要提供更改通知,绑定源必须实现 INotifyPropertyChanged 接口。大多数业务组件类型不实现此接口。因此,上面开发的业务对象模型无法直接由 WPF 应用程序使用。

中间类

为了绕过上述限制,我们创建了中间类来包装我们的业务对象。中间类应满足 WPF 数据源的所有要求,同时将所有请求委托给被包装的业务对象。EmployeeProxyDepartmentProxy 分别是业务组件 EmployeeDepartment 的中间类,并显示如下。

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>();
    }
}

EmployeeProxyEmployee 的代理模式。它提供与原始业务对象 empOrig 相同的接口,并将所有调用委托给原始对象的相应方法。它还实现了 INotifyPropertyChanged,以便每当属性更改时,绑定的 WPF 控件都会收到通知。它还有一个公共静态实例 aopcontainer,类型为 AOPContaineraopcontainer 用于在默认构造函数中创建一个新的业务对象。默认构造函数由绑定的 WPF 用户控件在从用户界面添加新条目时调用。

PostSharp 可以用于在每个属性设置方法中注入代码,而不是在每个属性设置方法中显式放置 OnPropertyChanged 调用。我在这里不详细讨论。但是,下载的 AopContainerWpfWithPostSharp.zip 使用 PostSharp 向属性设置方法注入调用。您需要安装 PostSharp 才能使其正常工作。

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

DepartmentProxyDepartment 的代理模式,它提供与原始业务对象 depOrig 相同的接口。但是,它不实现 INotifyPropertyChanged 接口,这意味着它不会将更改通知发送到绑定的 WPF 用户控件。它没有默认构造函数,这意味着绑定的 WPF 用户控件将无法从用户界面添加新条目。

现在,我们准备开始进行 WPF 应用程序的开发。

应用程序类

需要在应用程序级别处理一些应用程序范围的设置/清理。这是 App 类的代码

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();
    }
}

App_Startup 中,首先,我们将控制台输出重定向到一个文件。这样,EnterLogExitLog 方面会将日志写入文件而不是控制台。然后,我们创建一个 UnityContainer 对象并向其注册几个类型。还会创建一个 AOPUnityContainer 对象。UnityContainer 对象和 AOPUnityContainer 都被分配给相应的公共静态成员,并在应用程序的其他地方使用。我们还设置 SecurityCheck 方面的 Parameters 属性为 WindowsPrincipal,以便在应用程序执行期间进行安全检查。

App_Exit 中的代码进行了一些清理。

主窗口

主窗口是应用程序的 GUI。其 XAML 代码如下。

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

MainWindow 有一个 ComboBox 用于列出部门,一个 DataGrid 用于列出员工。它还有三个 Button 分别用于测试属性更改、集合更改和保存更改。

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

在事件处理程序 Window_Loaded 中,将 XML 文件 employees.xml 加载到 DataSet dsDB 中。此 XML 文件持久化部门和员工数据,并充当应用程序的数据库角色。当然,您可以使用真实数据库及其表来持久化数据并将其加载到 DataSet 中。

请注意,rpDepartment.RepList 是接口代理的列表,我们需要将其转换为 DepartmentProxy 对象列表,并将 DepartmentProxy 列表绑定到 comboBox1。出于同样的原因,我们必须将 rpEmployee.RepList 转换为 EmployeeProxy 对象列表,并将 EmployeeProxy 列表绑定到 dataGrid1。另请注意,ObservableCollection<EmployeeProxy> 被分配给 dataGrid1,这意味着当项添加到集合或从集合中移除时将生成通知。

事件处理程序 comboBox1_SelectionChanged 在选择部门时执行。然后,显示与该部门关联的员工。

事件处理程序 button1_Click 在按下 PropertyChanged 按钮时执行。代码将第一个员工的 EmployeeID 在 1 和 100 之间切换。请注意,调用了 ObjectProxyFactory.CreateProxy 来在更改 EmployeeID 之前添加安全检查。这里重要的是,您可以将方面链接到代码中的对象,无论它是在配置文件中配置的还是未配置的。对于这种情况,最佳做法是,由于您的应用程序可能有许多员工,并且一个员工可能有许多属性,因此最好在需要时使用代码将方面添加到员工对象,而不是在配置文件中将方面配置到员工组件。

事件处理程序 button2_Click 在按下 CollectionChanged 按钮时执行。代码只是将一个员工添加到 EmployeeID 为 1001 的员工集合中,或者从该集合中删除。

事件处理程序 button3_Click 在按下“保存”按钮时执行。代码将员工的更改持久化到“employees.xml”文件中。

运行应用程序,MainWindow 如下所示

选择部门后,将显示该部门的员工。

关闭应用程序并打开 hrlog.txt 文件。您将看到以下日志

   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()

再次运行它。按 PropertyChanged 按钮,第一个记录的 EmployeeID 被设置为 100。MainWindow 更新以显示更改,如下所示。

按 CollectionChanged 按钮,将添加一个 EmployeeID 为 1001 的新员工记录。MainWindow 更新以显示更改,如下所示。

关闭应用程序并打开 hrlog.txt 文件。您将看到以下日志。

   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()

现在,在 App 类的 App_Startup 中注释掉 Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); 这一行。构建并运行应用程序。当 MainWindow 出现时,单击 PropertyChanged 按钮,您将看到以下消息

您因安全检查失败而无法更改 EmployeeID。

您可能已经注意到,上述应用程序按员工姓氏对员工进行排序。如果您想按生日对员工进行排序怎么办?您可以通过将 WpfApplication.AppConcerns.SortEmployeeByLastname 替换为 WpfApplication.AppConcerns.SortEmployeeByBirthday 来更改 WpfApplication.RepositoryEmployee 的配置项。就是这样。现在,您的应用程序按生日对员工进行排序。

关注点

AOP Container 可以非常轻松地扩展业务组件的功能或为其添加 AOP 功能。您可以定义方面方法,并在配置文件中对其进行配置,将方面与业务组件关联。

AOP Container 使组件设计、方面设计和应用程序设计完全解耦。配置设置和全局设置将所有部件连接在一起。更改/扩展功能可能就像更改配置设置一样简单。

您还可以混合使用通过配置添加方面和通过代码添加方面。这样,您就可以为通用目的使用方面配置,同时使用代码链接更多方面以满足您的特定需求。

代理模式是一种巧妙的方法,可以通过接口拦截技术将 WPF 的强大功能应用于业务对象。

虽然本文讨论了 AOP Container 与 Unity IoC Container 的结合,但您应该能够轻松地将 AOP Container 与其他 IoC Container(如 MEF 和 Windsor)结合使用。

© . All rights reserved.