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

使用基于组件的对象扩展器进行应用程序开发

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2011年11月14日

CPOL

6分钟阅读

viewsIcon

25294

downloadIcon

222

讨论了如何使用基于组件的对象扩展器为应用程序添加功能。

引言

基于组件的对象扩展器 (CBO Extender) 是一个对象可扩展性框架。顾名思义,它是基于组件的,这意味着它使用接口,并且它直接扩展对象而不是扩展它们的类型。CBO Extender 可用于扩展任何接口方法,包括第三方接口、.NET Framework 接口或您自己定义的接口。它是一个通用工具,通过将行为附加到对象的接口方法来为对象添加功能。您可以在文章 Component-Based Object Extender 中找到其概述。

直接扩展对象意味着在不涉及组件设计更改的情况下,将功能添加到应用程序中。本文讨论了如何通过使用 CBO Extender 来增强应用程序。目标是在使用对象时按需为应用程序添加功能,而无需更改其组件。

背景

在 CBO Extender 中,行为由一些具有特定签名的方法表示(也称为切面方法)。“切面”借用了 AOP(面向切面编程)的概念,它表示一个横切关注点。但是,切面方法不限于横切关注点。虽然切面方法可以表示日志记录、安全检查、审计等横切关注点,但它也可以表示排序和分组等非横切关注点。在 CBO Extender 的上下文中,切面是能够实现任何逻辑的方法,包括但不限于横切关注点。

CBO Extender 由动态装饰器和 AOP 容器组成。您可以通过调用其 CreateProxy<T> 方法直接在代码中使用动态装饰器。如果您使用某种 IoC 容器,则可能需要将 AOP 容器与您的 IoC 容器一起使用。使用 AOP 容器有两种方式:通过配置或通过 Fluent 注册代码。

在本文中,我们将从开发一个简单的控制台应用程序开始,以满足最低业务需求。然后,我们将尝试通过添加日志记录和安全检查等系统增强功能以及排序等功能增强来扩展它。提出了三种不同的方法来实现这些增强:使用 CreateProxy<T> 方法,使用带有 Fluent 注册代码的 AOPContainer,以及使用配置的 AOPContainer

注意:CBO Extender 已作为 NuGet 包上传。您可以从 Visual Studio 2010 下载并将其添加到您的项目中。您还可以 此处 下载源代码、最新更新和更多示例。

要从 Visual Studio 2010 将 CBOExtender 安装到您的项目中,请单击“工具”->“库包管理器”->“管理 NuGet 程序包…”打开“管理 NuGet 程序包”对话框。键入 CBOExtender,如图所示。

您可能需要在 Visual Studio 2010 中 安装 NuGet,然后才能下载该程序包。

示例:HR 控制台

HR 控制台从数据源(XML 文件)读取部门数据和员工数据,并填充部门集合和员工集合。然后,它尝试获取第一个部门的部门 ID,更新第一个员工的部门 ID,并获取员工的详细信息。应用程序代码如下。

static void Main(string[] args)
{
    //Load employees and departments into a data set from an xml file.
    DataSet dsDB = new DataSet();
    dsDB.ReadXml("employees.xml");

    //Print original employees
    Console.WriteLine("Employees from data source");
    string str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (DataRow r in dsDB.Tables["Employee"].Rows)
    {
        str = r["EmployeeID"].ToString() + "\t" + r["FirstName"].ToString() + 
              "\t" + r["LastName"].ToString() + "\t" + 
              r["DateOfBirth"].ToString() + "\t" + r["DepartmentID"].ToString();
        Console.WriteLine(str);
    }
    Console.WriteLine();

    IRepository<IDepartment> rpDepartment = new RepositoryDepartment();
    rpDepartment.DataSource = dsDB;
    rpDepartment.GetAll();

    int? iDep = rpDepartment.RepList[0].DepartmentID;

    IRepository<IEmployee> rpEmployee = new RepositoryEmployee();
    rpEmployee.DataSource = dsDB;
    rpEmployee.GetAll();

    Console.WriteLine();

    //Print employees
    Console.WriteLine("Employees in collection");
    str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (IEmployee em in rpEmployee.RepList)
    {
        str = em.EmployeeID.ToString() + "\t" + em.FirstName + "\t" + em.LastName + "\t"
            + em.DateOfBirth.ToShortDateString() + "\t" + em.DepartmentID.ToString();
        Console.WriteLine(str);
    }
    Console.WriteLine();

    IEmployee oEm = rpEmployee.RepList[0];
    oEm.DepartmentID = 2;
    oEm.DetailsByLevel(3);
    Console.WriteLine();

    //Print employees after update
    Console.WriteLine("Employees after update");
    str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (IEmployee em in rpEmployee.RepList)
    {
        str = em.EmployeeID.ToString() + "\t" + em.FirstName + "\t" + em.LastName + "\t"
            + em.DateOfBirth.ToShortDateString() + "\t" + em.DepartmentID.ToString();
        Console.WriteLine(str);
    }
    Console.WriteLine();

    Console.ReadLine();
}

执行时,上述代码的输出如下所示。

它只是打印出数据源中的原始员工列表,从原始列表中填充的集合中的员工列表,以及更新后集合中的员工列表。

增强和需求

为了让应用程序更有趣,让我们增强它以具备日志记录和安全检查功能。我们还希望能够按姓氏或出生日期对员工列表进行排序。

作为进行这些更改的指导,我们应尽可能保持现有的业务组件(EmployeeDepartmentRepositoryEmployeeRepositoryDepartment)不变。毕竟,这些增强功能是应用程序特定的,并且是在设计了这些业务组件之后才请求的。

有三种不同的方法可用于实现这些增强和需求。它们是:使用 CreateProxy<T> 方法,使用带 Fluent 注册代码的 AOPContainer,以及使用配置的 AOPContainer。以下各节将分别讨论每种方法。

使用 CreateProxy

让我们稍微分析一下这些增强功能。日志记录是经典的横切关注点,可以由多个模块或不同的应用程序使用。因此,我们将进入/退出日志的切面方法放在一个共享类中,如下所示。

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

安全检查是一个横切关注点,并且是应用程序特定的(Windows 安全或自定义安全等)。因此,我们将安全检查的切面方法放在应用程序中的一个类中,如下所示。

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

对员工列表进行排序不是横切关注点,而是对 RepositoryEmployee 组件的扩展。它不太可能被其他组件共享。因此,我们为此切面使用匿名方法,如下所示。

(ctx, parameters) =>
{
    object target = ctx.Target;
    if (target.GetType().ToString() == "ConsoleUtil.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;
    }
}

请注意,所有上述切面方法都具有相同的签名。它们将 AspectContext 作为第一个参数,将 object[] 作为第二个参数,并返回 void。以下代码显示了如何使用这些切面来增强应用程序。

static void Main(string[] args)
{
    //Set security policy.
    //Commenting out this line, the security check
    //aspect will throw out an exception
    Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

    //Load employees and departments into a data set from an xml file.
    DataSet dsDB = new DataSet();
    dsDB.ReadXml("employees.xml");

    //Print original employees
    Console.WriteLine("Employees");
    string str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (DataRow r in dsDB.Tables["Employee"].Rows)
    {
        str = r["EmployeeID"].ToString() + "\t" + r["FirstName"].ToString() + 
              "\t" + r["LastName"].ToString() + "\t" + 
              r["DateOfBirth"].ToString() + "\t" + r["DepartmentID"].ToString();
        Console.WriteLine(str);
    }
    Console.WriteLine();

    IRepository<IDepartment> rpDepartment = new RepositoryDepartment();
    rpDepartment = ObjectProxyFactory.CreateProxy<IRepository<IDepartment>>(
        rpDepartment,
        new string[] { "GetAll" },
        new Decoration(SharedLib.SharedConcerns.EnterLog, null),
        new Decoration(SharedLib.SharedConcerns.ExitLog, null));

    rpDepartment.DataSource = dsDB;
    //The following line populates a department list and writes enter/exit logs.
    rpDepartment.GetAll();

    //The following line gets a department id and writes enter log.
    int? iDep = ObjectProxyFactory.CreateProxy<IDepartment>(
        rpDepartment.RepList[0],
        new string[] { "get_DepartmentID" },
        new Decoration(SharedLib.SharedConcerns.EnterLog, null),
        null).DepartmentID;

    IRepository<IEmployee> rpEmployee = new RepositoryEmployee();
    rpEmployee = ObjectProxyFactory.CreateProxy<IRepository<IEmployee>>(
        rpEmployee,
        new string[] { "GetAll" },
        null,
        new Decoration((ctx, parameters) =>
        {
            object target = ctx.Target;
            if (target.GetType().ToString() == "ConsoleUtil.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;
            }
        }, null));
    rpEmployee.DataSource = dsDB;
    //The following line populates an employee list and sorts the list.
    rpEmployee.GetAll();

    Console.WriteLine();

    //Print employees sorted by employee's last name
    Console.WriteLine("Employees sorted by employee's last name");
    str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (IEmployee em in rpEmployee.RepList)
    {
        str = em.EmployeeID.ToString() + "\t" + em.FirstName + "\t" + em.LastName + "\t"
            + em.DateOfBirth.ToShortDateString() + "\t" + em.DepartmentID.ToString();
        Console.WriteLine(str);
    }
    Console.WriteLine();

    try
    {
        IEmployee oEm = rpEmployee.RepList[0];
        //Add a security check aspect to an employee before changing its property
        oEm = ObjectProxyFactory.CreateProxy<IEmployee>(oEm,
            new string[] { "set_EmployeeID","set_FirstName","set_LastName",
                           "set_DateOfBirth","set_DepartmentID" },
            new Decoration(AppConcerns.SecurityCheck, 
                           new object[] { System.Threading.Thread.CurrentPrincipal }),
            null);
        //The following line check security before setting the property
        oEm.DepartmentID = 2;

        //Chain enetr/exit log aspects to the employee's DetailsByLevel method
        oEm = ObjectProxyFactory.CreateProxy<IEmployee>(oEm,
            new string[] { "DetailsByLevel" },
            new Decoration(SharedLib.SharedConcerns.EnterLog, null),
            new Decoration(SharedLib.SharedConcerns.ExitLog, null));
        //The following line write entering log before and exiting
        //log after execution of the target's DetailsByLevel(3)
        oEm.DetailsByLevel(3);
        Console.WriteLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        Console.WriteLine();
    }

    //Print employees after sort and update
    Console.WriteLine("Employees after sort and update");
    str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (IEmployee em in rpEmployee.RepList)
    {
        str = em.EmployeeID.ToString() + "\t" + em.FirstName + "\t" + em.LastName + "\t"
            + em.DateOfBirth.ToShortDateString() + "\t" + em.DepartmentID.ToString();
        Console.WriteLine(str);
    }
    Console.WriteLine();

    Console.ReadLine();
}

请注意,在通过访问对象的任何方法来使用对象之前,就会调用 CreateProxy<T>。执行上述代码时,将显示以下输出。

您的应用程序现在具有日志记录、安全检查和排序功能,而无需更改/继承现有业务组件或创建新业务组件。您可以通过更改切面来更改应用程序行为。例如,如果要将员工排序从按姓氏排序更改为按出生日期排序,只需修改匿名方法即可。

使用带 Fluent 注册的 AOP Container

AOPContainer 为 IoC 容器提供了通用的应用程序编程接口,以获得 AOP 功能。上述应用程序已使用带 Windsor Container 和 Unity Container 的 AOPContainer 重写。Fluent 接口用于将切面注册到组件。完整代码如下。

static void Main(string[] args)
{
    AOPContainer aopcontainer = null;
    switch (args.Length == 0 ? "" : args[0])
    {
        case "0": //Register types for Windsor Container and create AOPWindsorContainer
            {
                Console.WriteLine("Use Windsor Container");

                IWindsorContainer windsorContainer = new WindsorContainer();
                windsorContainer.Register(AllTypes
                    .FromAssembly(Assembly.LoadFrom("Employee.dll"))
                    .Where(t => t.Name.Equals("Employee"))
                    .Configure(c => c.LifeStyle.Transient)
                ).Register(AllTypes
                    .FromAssembly(Assembly.LoadFrom("Department.dll"))
                    .Where(t => t.Name.Equals("Department"))
                    .Configure(c => c.LifeStyle.Transient)
                ).Register(AllTypes
                    .FromAssembly(Assembly.GetExecutingAssembly())
                    .Where(t => (t.Name.Equals("RepositoryEmployee") || 
                           t.Name.Equals("RepositoryDepartment")))
                    .Configure(c => c.LifeStyle.Transient)
                ).Register(Castle.MicroKernel.Registration.Component.For(typeof(List<IEmployee>))
                ).Register(Castle.MicroKernel.Registration.Component.For(typeof(List<IDepartment>))
                );

                aopcontainer = new AOPWindsorContainer(windsorContainer);
            }
            break;

        case "1": //Register types for Unity Container and create AOPUnityContainer
            {
                Console.WriteLine("Use Unity Container");

                IUnityContainer unityContainer = new UnityContainer();
                unityContainer.RegisterType<IEmployee, Employee>(new InjectionConstructor()
                ).RegisterType<IDepartment, Department>(new InjectionConstructor()
                ).RegisterType<IRepository<IDepartment>, 
                  RepositoryDepartment>(new InjectionConstructor()
                ).RegisterType<IRepository<IEmployee>, 
                  RepositoryEmployee>(new InjectionConstructor()
                ).RegisterType<IList<IEmployee>, 
                  List<IEmployee>>(new InjectionConstructor()
                ).RegisterType<IList<IDepartment>, 
                  List<IDepartment>>(new InjectionConstructor()
                );

                aopcontainer = new AOPUnityContainer(unityContainer);
            }
            break;

        default:
            {
                Console.WriteLine("Usage: ConsoleUtil i");
                Console.WriteLine("where i: 0 (Windsor Container)");
                Console.WriteLine("         1 (Unity Container)");
                Console.ReadLine();
            }
            return;
    }

    Console.WriteLine();

    //Register Aspects
    aopcontainer.RegisterAspect<RepositoryDepartment, IRepository<IDepartment>>("GetAll",
        new Decoration(SharedLib.SharedConcerns.EnterLog, null),
        new Decoration(SharedLib.SharedConcerns.ExitLog, null)
    ).RegisterAspect<RepositoryEmployee, IRepository<IEmployee>>("GetAll",
        null,
        new Decoration((ctx, parameters) =>
        {
            object target = ctx.Target;
            if (target.GetType().ToString() == "ConsoleUtil.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;
            }
        }, null)
    ).RegisterAspect<Department, IDepartment>("get_DepartmentID",
        new Decoration(SharedLib.SharedConcerns.EnterLog, null),
        null);

    //Set security policy.
    //Commenting out this line, the security check aspect will throw out an exception
    Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

    //Load employees and departments into a data set from an xml file.
    DataSet dsDB = new DataSet();
    dsDB.ReadXml("employees.xml");

    //Print original employees
    Console.WriteLine("Employees");
    string str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (DataRow r in dsDB.Tables["Employee"].Rows)
    {
        str = r["EmployeeID"].ToString() + "\t" + r["FirstName"].ToString() + 
              "\t" + r["LastName"].ToString() + "\t"
              + r["DateOfBirth"].ToString() + "\t" + r["DepartmentID"].ToString();
        Console.WriteLine(str);
    }
    Console.WriteLine();

    IRepository<IDepartment> rpDepartment = 
      aopcontainer.Resolve<RepositoryDepartment, 
      IRepository<IDepartment>>("GetAll");
    rpDepartment.AopContainer = aopcontainer;
    rpDepartment.DataSource = dsDB;
    //The following line populates a department list and writes enter/exit logs.
    rpDepartment.GetAll();

    //The following line gets a department id and writes enter log.
    int? iDep = rpDepartment.RepList[0].DepartmentID;

    IRepository<IEmployee> rpEmployee = 
      aopcontainer.Resolve<RepositoryEmployee, 
      IRepository<IEmployee>>("GetAll");
    rpEmployee.AopContainer = aopcontainer;
    rpEmployee.DataSource = dsDB;
    //The following line populates an employee list and sorts the list.
    rpEmployee.GetAll();

    Console.WriteLine();

    //Print employees sorted by employee's last name
    Console.WriteLine("Employees sorted by employee's last name");
    str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (IEmployee em in rpEmployee.RepList)
    {
        str = em.EmployeeID.ToString() + "\t" + em.FirstName + "\t" + em.LastName + "\t"
            + em.DateOfBirth.ToShortDateString() + "\t" + em.DepartmentID.ToString();
        Console.WriteLine(str);
    }
    Console.WriteLine();

    try
    {
        IEmployee oEm = rpEmployee.RepList[0];
        //Add a security check aspect to an employee before changing its property
        oEm = AOPContainer.ChainAspect<IEmployee, IEmployee>(oEm,
            "set_EmployeeID,set_FirstName,set_LastName,set_DateOfBirth,set_DepartmentID",
            new Decoration(AppConcerns.SecurityCheck, 
            new object[] { System.Threading.Thread.CurrentPrincipal }),
            null);
        //The following line check security before setting the property
        oEm.DepartmentID = 2;

        //Chain enetr/exit log aspects to the employee's DetailsByLevel method
        oEm = AOPContainer.ChainAspect<IEmployee, IEmployee>(oEm,
            "DetailsByLevel",
            new Decoration(SharedLib.SharedConcerns.EnterLog, null),
            new Decoration(SharedLib.SharedConcerns.ExitLog, null));
        //The following line write entering log before and exiting
        //log after execution of the target's DetailsByLevel(3)
        oEm.DetailsByLevel(3);
        Console.WriteLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        Console.WriteLine();
    }

    //Print employees after sort and update
    Console.WriteLine("Employees after sort and update");
    str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (IEmployee em in rpEmployee.RepList)
    {
        str = em.EmployeeID.ToString() + "\t" + em.FirstName + "\t" + em.LastName + "\t"
            + em.DateOfBirth.ToShortDateString() + "\t" + em.DepartmentID.ToString();
        Console.WriteLine(str);
    }
    Console.WriteLine();

    aopcontainer.IocContainer.Dispose();

    Console.ReadLine();
}

Fluent 接口注册方法 RegisterAspect<T, V> 方便地将切面注册到组件。通过将切面注册到组件,所有由 AOPContainer 解析的实例都将附加切面。但是,在某些情况下,您不希望所有实例都附加到切面,而是希望在特定位置将切面添加到特定实例。ChainAspect<T, V> 提供了这种灵活性。如上代码所示,ChainAspect<T, V> 用于在更新特定员工实例的属性之前,将其添加到该实例。

执行时,它会生成以下输出。

使用配置的 AOP Container

除了使用 Fluent 接口注册切面外,您还可以通过应用程序配置文件配置切面。为了在配置文件中使用切面方法,需要将它们包装在一个实现标记接口 IConcerns 的类中,并提供一个静态构造函数来维护方法名和装饰对象之间的映射。以下代码展示了先前各节中定义的切面方法的这些更改。

public class SharedConcerns : IConcerns
{
    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();
    }
}

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

    static AppConcerns()
    {
        ConcernsContainer.runtimeAspects.Add("ConsoleUtil.AppConcerns.SecurityCheck", 
                          new Decoration(AppConcerns.SecurityCheck, null));
        ConcernsContainer.runtimeAspects.Add(
                "ConsoleUtil.AppConcerns.SortEmployeeByLastname", 
                new Decoration(AppConcerns.SortEmployeeByLastname, 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() == "ConsoleUtil.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;
        }
    }
}

在对切面方法进行了这些更改之后,我们就可以配置应用程序的切面了。配置设置如下。

<configSections>
    <section name="DynamicDecoratorAspect" 
          type="CBOExtender.Configuration.DynamicDecoratorSection,CBObjectExtender"/>
</configSections>

<DynamicDecoratorAspect>
    <objectTemplates>
        <add name="1"
                type="ConsoleUtil.RepositoryDepartment"
                interface="ConsoleUtil.IRepository`1[ThirdPartyHR.IDepartment]"
                methods="GetAll"
                predecoration="SharedLib.SharedConcerns.EnterLog,SharedLib"
                postdecoration="SharedLib.SharedConcerns.ExitLog,SharedLib" />
        <add name="2"
                type="ConsoleUtil.RepositoryEmployee"
                interface="ConsoleUtil.IRepository`1[ThirdPartyHR.IEmployee]"
                methods="GetAll"
                predecoration=""
                postdecoration="ConsoleUtil.AppConcerns.SortEmployeeByLastname"/>
        <add name="3"
                type="ThirdPartyHR.Department"
                interface="ThirdPartyHR.IDepartment"
                methods="get_DepartmentID"
                predecoration="SharedLib.SharedConcerns.EnterLog,SharedLib"
                postdecoration=""/>
    </objectTemplates>
</DynamicDecoratorAspect>

应用程序中使用了三个 IoC 容器:Windsor、Unity 和 MEF。应用程序代码如下。

static void Main(string[] args)
{
    AOPContainer aopcontainer = null;

    switch (args.Length == 0 ? "" : args[0])
    {
        case "0": //Register types for Windsor Container and create AOPWindsorContainer
            {
                Console.WriteLine("Using AOPWindsorContainer.");

                IWindsorContainer container = new WindsorContainer();
                container.Register(AllTypes
                    .FromAssembly(Assembly.LoadFrom("Employee.dll"))
                    .Where(t => t.Name.Equals("Employee"))
                    .Configure(c => c.LifeStyle.Transient)
                ).Register(AllTypes
                    .FromAssembly(Assembly.LoadFrom("Department.dll"))
                    .Where(t => t.Name.Equals("Department"))
                    .Configure(c => c.LifeStyle.Transient)
                ).Register(AllTypes
                    .FromAssembly(Assembly.GetExecutingAssembly())
                    .Where(t => (t.Name.Equals("RepositoryEmployee") || 
                           t.Name.Equals("RepositoryDepartment")))
                    .Configure(c => c.LifeStyle.Transient)
                );

                aopcontainer = new AOPWindsorContainer((IWindsorContainer)container);
            }
            break;

        case "1": //Register types for Unity Container and create AOPUnityContainer
            {
                Console.WriteLine("Using AOPUnityContainer.");

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

                aopcontainer = new AOPUnityContainer((IUnityContainer)container);
            }
            break;

        case "2": //Register catalogs for MEF and create AOPMefContainer
            {
                Console.WriteLine("Using AOPMefContainer.");

                AggregateCatalog catalog = new AggregateCatalog();
                catalog.Catalogs.Add(new AssemblyCatalog(Assembly.LoadFrom("Employee.dll")));
                catalog.Catalogs.Add(new AssemblyCatalog(Assembly.LoadFrom("Department.dll")));
                catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetEntryAssembly()));

                CompositionContainer container = new CompositionContainer(catalog);

                aopcontainer = new AOPMefContainer(container);
            }
            break;

        default:
            {
                Console.WriteLine("Usage: ConsoleUtil i");
                Console.WriteLine("where i: 0 (Windsor Container)");
                Console.WriteLine("         1 (Unity Container)");
                Console.WriteLine("         2 (MEF)");
                Console.ReadLine();
            }
            return;
    }

    Console.WriteLine();

    //Set security policy.
    //Commenting out this line, the security check aspect will throw out an exception
    Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

    //Load employees and departments into a data set from an xml file.
    DataSet dsDB = new DataSet();
    dsDB.ReadXml("employees.xml");

    //Print original employees
    Console.WriteLine("Employees");
    string str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (DataRow r in dsDB.Tables["Employee"].Rows)
    {
        str = r["EmployeeID"].ToString() + "\t" + r["FirstName"].ToString() + 
              "\t" + r["LastName"].ToString() + "\t" + 
              r["DateOfBirth"].ToString() + "\t" + r["DepartmentID"].ToString();
        Console.WriteLine(str);
    }
    Console.WriteLine();

    IRepository<IDepartment> rpDepartment = 
      aopcontainer.Resolve<RepositoryDepartment, IRepository<IDepartment>>("GetAll");
    rpDepartment.AopContainer = aopcontainer;
    rpDepartment.DataSource = dsDB;
    //The following line populates a department list and writes enter/exit logs.
    rpDepartment.GetAll();

    //The following line gets a department id and writes enter log.
    int? iDep = rpDepartment.RepList[0].DepartmentID;

    IRepository<IEmployee> rpEmployee = 
      aopcontainer.Resolve<RepositoryEmployee, IRepository<IEmployee>>("GetAll");
    rpEmployee.AopContainer = aopcontainer;
    rpEmployee.DataSource = dsDB;
    //The following line populates an employee list and sorts the list.
    rpEmployee.GetAll();

    Console.WriteLine();

    //Print employees sorted by employee's last name
    Console.WriteLine("Employees sorted by employee's last name");
    str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (IEmployee em in rpEmployee.RepList)
    {
        str = em.EmployeeID.ToString() + "\t" + em.FirstName + "\t" + em.LastName + "\t"
            + em.DateOfBirth.ToShortDateString() + "\t" + em.DepartmentID.ToString();
        Console.WriteLine(str);
    }
    Console.WriteLine();

    try
    {
        IEmployee oEm = rpEmployee.RepList[0];
        //Add a security check aspect to an employee before changing its property
        oEm = AOPContainer.ChainAspect<IEmployee, IEmployee>(oEm,
            "set_EmployeeID,set_FirstName,set_LastName,set_DateOfBirth,set_DepartmentID",
            new Decoration(AppConcerns.SecurityCheck, 
            new object[] { System.Threading.Thread.CurrentPrincipal }),
            null);
        //The following line check security before setting the property
        oEm.DepartmentID = 2;

        //Chain enetr/exit log aspects to the employee's DetailsByLevel method
        oEm = AOPContainer.ChainAspect<IEmployee, IEmployee>(oEm,
            "DetailsByLevel",
            new Decoration(SharedLib.SharedConcerns.EnterLog, null),
            new Decoration(SharedLib.SharedConcerns.ExitLog, null));
        //The following line write entering log before and exiting
        //log after execution of the target's DetailsByLevel(3)
        oEm.DetailsByLevel(3);
        Console.WriteLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        Console.WriteLine();
    }

    //Print employees after sort and update
    Console.WriteLine("Employees after sort and update");
    str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (IEmployee em in rpEmployee.RepList)
    {
        str = em.EmployeeID.ToString() + "\t" + em.FirstName + "\t" + em.LastName + "\t"
            + em.DateOfBirth.ToShortDateString() + "\t" + em.DepartmentID.ToString();
        Console.WriteLine(str);
    }
    Console.WriteLine();

    aopcontainer.IocContainer.Dispose();

    Console.ReadLine();
}

可以看到,在将类型注册到特定 IoC 容器后,无需显式注册切面。AOP 容器将自动解析配置文件,并根据配置设置将切面注册到组件。执行时,它会生成以下输出。

关注点

  • 通过在消耗对象时对其进行扩展,可以轻松地增强应用程序。
  • 可以通过使用 CreateProxy、带 Fluent 注册的 AOP 容器或带配置的 AOP 容器来扩展对象。
  • 软件修改仅限于组件的消费端,这使得组件稳定而应用程序敏捷。
  • 切面方法简单、轻量级,并且通过访问目标、调用上下文和传递参数可以非常强大。
© . All rights reserved.