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

MVC/AJAX/REST 应用程序的组件、方面和动态装饰器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (3投票s)

2011年6月24日

CPOL

11分钟阅读

viewsIcon

29558

downloadIcon

304

将组件、方面和动态装饰器原则应用于 MVC/AJAX/REST 应用程序。

引言

正如在 Components, Aspects, and Dynamic Decorator 中讨论的那样,应用程序开发涉及特定于应用程序类型和独立于应用程序类型的任务。这种任务分离对于根据应用程序类型的特定技术帮助您决定处理策略、部署策略、测试策略、UI 策略等非常重要,并且还有助于您解决应用程序类型无关的(也称为通用)任务,如设计/扩展组件和系统地处理横切关注点。讨论了几项关于开发这些通用任务的指南。动态装饰器用于通过遵循这些指南来处理这些任务。本文使用了一个 WinForms 应用程序作为示例。

在本文中,我将演示如何通过使用动态装饰器将这些原则和指南应用于另一种应用程序类型——MVC/AJAX/REST 应用程序。

选择应用程序类型

在 .NET 世界中,有几种应用程序类型:WinForms、ASP.NET、ASP.NET MVC 和 Silverlight。WinForms 应用程序提供丰富的用户交互和客户端处理能力。ASP.NET 应用程序在部署和维护方面具有 Web 的优势。ASP.NET MVC 提供可测试性以及 ASP.NET 的优势。Silverlight 应用程序通过 Web 提供丰富的用户界面。不同类型的应用程序具有不同的技术特性,这些特性可能符合或不符合您的要求。您应该根据您的处理策略、部署策略、维护策略、测试策略、UI 策略等来选择应用程序类型。

此外,还可以使用 AJAX 技术来增强 Web 应用程序(ASP.NET、ASP.NET MVC 或 Silverlight),以创建更具交互性的用户体验。

不同类型的应用程序也有不同的应用程序编程模型、UI 元素、状态管理、事件处理模型等。一旦您为应用程序选择了应用程序类型,您就需要处理与应用程序类型相关的任务。

处理通用任务

除了特定于应用程序类型的任务外,应用程序开发中还有独立于特定应用程序类型的任务。例如,无论应用程序类型如何,您都会面临设计、扩展组件和处理横切关注点等任务。这些对于所有应用程序类型都是通用的。

在文章 Components, Aspects, and Dynamic Decorator 中给出了一系列用于开发这些通用任务的原则。它们在此处再次列出如下:

  • 以通用方式设计组件以满足业务需求
  • 将方面设计为各自模块中的全局方法
  • 按需为对象添加方面
  • 按需扩展对象

有关这些原则的讨论详情,请参阅 Components, Aspects, and Dynamic Decorator。您可能还需要阅读文章 Dynamic Decorator Pattern 来理解动态装饰器,并阅读文章 Add Aspects to an Object Using Dynamic Decorator 来理解使用动态装饰器进行方面编程。

示例

在以下部分中,将讨论一个示例应用程序,以演示如何通过使用动态装饰器将上述原则应用于 MVC/AJAX/REST 应用程序。

此处使用与 Components, Aspects, and Dynamic Decorator 中示例讨论的相同问题。这一次,我们选择 ASP.NET MVC/AJAX/REST 应用程序作为应用程序类型,而不是 WinForms 应用程序。为方便起见,我在此再次陈述问题。

问题

根据部门选择显示员工。

Components

假设有两个组件,EmployeeDepartment。对于 Employee,有一个相应的 RepositoryEmployee 组件,其中包含 Employee 对象的集合。对于 Department,有一个相应的 RepositoryDepartment 组件,其中包含 Department 对象的集合。这些组件的代码如下所示。

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 FullName();
    System.Single Salary();
}

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 FullName()
    {
        System.String s = FirstName + " " + LastName;
        return s;
    }

    public System.Single Salary()
    {
        System.Single i = 10000.12f;
        return i;
    }
}
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() { }
}
public interface IRepository<T>
{
    List<T> RepList { get; set; }
    void GetAll();
}

public class RepositoryEmployee : IRepository<IEmployee>
{
    private List<IEmployee> myList = null;

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

    public RepositoryEmployee()
    {
    }

    public void GetAll()
    {
        myList = new List<IEmployee> { 
            new Employee(1, "John", "Smith", new DateTime(1990, 4, 1), 1), 
            new Employee(2, "Gustavo", "Achong", new DateTime(1980, 8, 1), 1), 
            new Employee(3, "Maxwell", "Becker", new DateTime(1966, 12, 24), 2), 
            new Employee(4, "Catherine", "Johnston", new DateTime(1977, 4, 12), 2), 
            new Employee(5, "Payton", "Castellucio", new DateTime(1959, 4, 21), 3), 
            new Employee(6, "Pamela", "Lee", new DateTime(1978, 9, 16), 4) };
    }
}

public class RepositoryDepartment : IRepository<IDepartment>
{
    private List<IDepartment> myList = null;

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

    public RepositoryDepartment()
    {
    }

    public void GetAll()
    {
        myList = new List<IDepartment> { new Department(1, "Engineering"), 
            new Department(2, "Sales"), 
            new Department(3, "Marketing"), 
            new Department(4, "Executive") };
    }
}

在此应用程序中,员工和部门的数据被硬编码在两个列表中,以简化我们的讨论。在实际应用程序中,这些数据通常会持久化到关系数据库中。然后,您需要创建一个数据层来检索它们并放入列表中。

值得注意的是,员工列表是按照插入顺序填充的,没有任何排序。此时很难预测此组件需要支持哪种排序。在应用程序中,组件对象可能需要按姓氏排序。另一个可能需要按出生日期排序。第三个可能根本不需要排序。因此,最好将排序的实现推迟到组件在应用程序中使用时。通过设计 RepositoryEmployee 组件而不考虑排序,遵循了“设计组件以通用方式满足业务需求”的原则。这样,组件就是稳定且封闭的。

HRMVCAjax

通常,Web 应用程序的响应会刷新整个页面。例如,当选择一个部门时,整个页面会刷新以显示与所选部门关联的员工。更理想的情况是,当选择一个部门时,只有页面的一部分(员工表格)会被刷新。这可以通过 AJAX 实现。

AJAX 是 Web 应用程序中一组客户端技术。您可以将 AJAX 应用于不同类型的 Web 应用程序:ASP.NET、ASP.NET MVC、PHP 等。在本例中,我们选择 ASP.NET MVC 作为我们的 Web 应用程序。此外,在实现 AJAX 方面,您有几种选择:原生 JavaScript、ASP.NET AJAX 框架、jQuery 等。在我们的示例中,我们使用 jQuery。最后,您必须决定如何服务 AJAX 请求。在本例中,我们选择 WCF REST 服务来服务 AJAX 请求。

HRMVCAjax 是一个 ASP.NET MVC 应用程序,它使用上述组件根据部门选择显示员工。由于它是一个 ASP.NET MVC 应用程序,它遵循 ASP.NET MVC 的应用程序编程模型和事件模型。Controller 代码如下所示。

public class DepEmployeesController : Controller
{
    private IRepository<IEmployee> rpEmployee = null;
    private IRepository<IDepartment> rpDepartment = null;

    public DepEmployeesController()
    {
        rpEmployee = new RepositoryEmployee();
        rpDepartment = new RepositoryDepartment();
    }

    public ActionResult Index()
    {
        rpDepartment.GetAll();
        rpEmployee.GetAll();

        List<SelectListItem> depList = new List<SelectListItem>();
        SelectListItem sli = null;
        sli = new SelectListItem();
        sli.Value = "";
        sli.Text = "";
        depList.Add(sli);

        foreach (IDepartment d in rpDepartment.RepList)
        {
            sli = new SelectListItem();
            sli.Value = d.DepartmentID.Value.ToString();
            sli.Text = d.Name;
            depList.Add(sli);
        }

        ViewData["depSel"] = 
          new SelectList(depList, "Value", "Text");
        return View(rpEmployee.RepList);
    }
}

View 代码如下所示。

<%@ Page Title="" Language="C#" 
  MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% Html.BeginForm("Index", "DepEmployees"); %>
    <span>Department</span><br />
        <%= Html.DropDownList("depSel")%>

    <br />    <br />
    <span>Employees</span><br />
    <div id="grid">
    <table cellspacing="0" rules="all" border="1" 
                 id="GridView1" style="border-collapse:collapse;">
        <tr>
            <th>EmployeeID</th><th>FirstName</th>
              <th>LastName</th><th>DateOfBirth</th><th>DepartmentID</th>
        </tr>
<% foreach (var itm in Model) { %>        
        <tr>
            <td><%= itm.EmployeeID %></td><td><%= itm.FirstName%></td>
              <td><%= itm.LastName%></td><td><%= itm.DateOfBirth%></td>
              <td><%= itm.DepartmentID%></td>
        </tr>
<% } %>
    </table>
    </div>
<% Html.EndForm(); %>

<script type='text/javascript'>
    $(document).ready(function () {
        $("select").change(function () {
            $.get("HRRestService?dep=" + $(this).val(), function (data) {
                $("#grid").html(
        '<table cellspacing="0" rules="all" border="1" id="emp" 
                    style="border-collapse:collapse;">' +
        '<tr>' +
            '<th>EmployeeID</th><th>FirstName</th><th>LastName</th>
              <th>DateOfBirth</th><th>DepartmentID</th>' +
        '</tr>');

                $(data).find("Employee").each(function() {
                    $("#emp").append(
        '<tr>' +
            '<td>' + $(this).find("EmployeeID").text() + '</td><td>' + 
                $(this).find("FirstName").text() + '</td><td>' + 
                $(this).find("LastName").text() + '</td><td>' + 
                $(this).find("DateOfBirth").text() + '</td><td>' + 
                $(this).find("DepartmentID").text() + '</td>' +
        '</tr>'
                    );
                });
                $("#grid").append('</table>');
            });
        });
    })
</script>
</asp:Content>

请注意上面的 jQuery JavaScript 代码。当选择一个部门时,会向 HRRestService 服务发送一个 AJAX 请求,并将选定的部门 ID 作为查询字符串。该服务返回一个包含与选定部门关联的员工列表的 XML 数据文件。回调函数解析 XML 数据并使用它来更新页面中的员工表格。

HRRestService 服务的代码如下所示。

[ServiceContract]
[AspNetCompatibilityRequirements(
  RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class HRRestService
{
    private static int iStaticDep = 0;

    [WebGet(UriTemplate = "?dep={dep}")]
    public List<Employee> EmployeesByDepartment(string dep)
    {
        IRepository<IEmployee> rpEmployee = null;
        IRepository<IDepartment> rpDepartment = null;
        rpEmployee = new RepositoryEmployee();
        rpDepartment = new RepositoryDepartment();

        rpDepartment.GetAll();
        rpEmployee.GetAll();

        IDepartment dpSel = rpDepartment.RepList[Convert.ToInt16(dep) - 1];
        iStaticDep = dpSel.DepartmentID.Value;

        List<IEmployee> empSel = null;
        if (rpEmployee.RepList != null)
        {
            empSel = rpEmployee.RepList.FindAll(
                (IEmployee emp) => { return emp.DepartmentID.Value == iStaticDep; });
        }

        List<Employee> empObj = new List<Employee>();
        foreach (IEmployee i in empSel)
        {
            empObj.Add((Employee)i);
        }

        return empObj;
    }
}

这是一个 WCF REST 服务。它接受部门 ID 并向客户端返回员工列表。

当浏览器第一次访问应用程序时,MVC Controller 会创建部门列表和员工表格,并将响应发送回客户端。然后,当选择一个部门时,jQuery 代码会使用选定的部门 ID 向 REST 服务发出 AJAX 请求。一旦服务响应,回调函数就会刷新员工表格以显示与选定部门关联的员工。

运行时,所有员工显示如下

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

请注意,只有员工表格被刷新,而部门下拉控件则未刷新,因此仍然高亮显示。

对员工列表进行排序

现在,假设您希望对象 rpEmployeeRepositoryEmployee 组件的实例)具有按姓氏排序员工的功能。以下是您需要做的事情。

首先,您创建一个用于排序的比较器类,如下所示。

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

然后,在 DepEmployeesControllerIndex 操作中,在调用 rpEmployee.GetAll() 之前,调用动态装饰器,如下所示。

rpEmployee = (IRepository<IEmployee>)ObjectProxyFactory.CreateProxy(
    rpEmployee,
    new String[] { "GetAll" },
    null,
    new Decoration((x, y) =>
    {
        object target = x.Target;
        if (target.GetType().ToString() == "ThirdPartyHR.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));

上面的代码需要应用到 HRRestServiceEmployeesByDepartment 方法中,在 rpEmployee.GetAll() 之前。

就是这样。现在,您的 HRMVCAjax 将按姓氏排序显示员工。构建并运行它。您将看到员工按姓氏排序显示,如下所示。

当您选择一个部门时,将显示与该部门关联的员工,并按姓氏排序。

请注意,这里使用了 Lambda 表达式为该员工存储库对象提供一个匿名方法,以添加排序功能。当然,您也可以使用普通方法来实现排序逻辑。但是,由于此排序逻辑特别针对员工存储库对象 rpEmployee 且不被其他对象共享,因此将其保留在匿名方法中更简洁。

这里有几点值得注意。首先,遵循了“根据需要扩展对象”的原则。当我们设计 RepositoryEmployee 组件时,排序需求尚不明确。在我们应用程序中使用 rpEmployee 对象时,很明显需要按员工姓氏对员工列表进行排序。因此,我们将此对象扩展为按员工姓氏对员工列表进行排序。其次,排序功能被附加到 rpEmployee 对象,而无需修改其组件或从中派生。第三,对象 rpEmployee 是具有排序功能的 RepositoryEmployee 组件的唯一实例,独立于 RepositoryEmployee 创建的其他实例。

设计方面

假设您希望您的 HRMVCAjax 应用程序处理进入/退出日志记录和安全检查的横切关注点。通过遵循“将方面设计为各自模块中的全局方法”的原则,这些方面被放置在 SysConcerns 类中作为单独的公共方法,并打包在自己的模块中。以下是这些方面的代码。

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

    public static void AdminCheck(AspectContext ctx, object[] parameters)
    {
        Console.WriteLine("Has right to call");
        return;
    }
}

EnterLog 写入进入日志,而 ExitLog 写入退出日志。AdminCheck 写入日志并返回。

您可能需要根据您的系统要求修改这些方法。您还可以通过访问上下文、目标和输入参数中的各种信息来增强它们。要了解上下文、目标和输入参数如何用于增强您的方面,请参阅 Add Aspects to Object Using Dynamic Decorator

使用方面

定义了方面之后,您就可以在应用程序中根据需要将它们添加到对象中。

假设您想在调用 RepositoryDepartment 组件的对象 rpDepartmentGetAll 方法之前添加安全检查方面。您还想将进入日志和退出日志添加到同一对象。在 DepEmployeesControllerIndex 方法中,在调用 rpDepartment.GetAll() 之前插入以下代码。

rpDepartment = (IRepository<IDepartment>)ObjectProxyFactory.CreateProxy(
    rpDepartment,
    new String[] { "GetAll" },
    new Decoration(new DecorationDelegate(SysConcerns.AdminCheck), 
                   new object[] { Thread.CurrentPrincipal }),
    null);

rpDepartment = (IRepository<IDepartment>)ObjectProxyFactory.CreateProxy(
    rpDepartment,
    new String[] { "GetAll" },
    new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
    new Decoration(new DecorationDelegate(SysConcerns.ExitLog), null));

然后,假设您想将进入日志和退出日志添加到 RepositoryEmployee 组件的对象 rpEmployeeGetAll 方法中;只需在 DepEmployeesControllerIndex 方法中,在调用 rpEmployee.GetAll() 之前插入以下代码。

rpEmployee = (IRepository<IEmployee>)ObjectProxyFactory.CreateProxy(
    rpEmployee,
    new String[] { "GetAll" },
    new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
    new Decoration(new DecorationDelegate(SysConcerns.ExitLog), null));

最后,假设您想跟踪访问了哪个部门,可以在 HRRestServiceEmployeesByDepartment 方法中使用选定 Department 组件对象 dpSel 的部门 ID 属性 iStaticDep = dpSel.DepartmentID.Value 之前添加以下代码。

dpSel = (IDepartment)ObjectProxyFactory.CreateProxy(
    dpSel,
    new String[] { "get_DepartmentID" },
    new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
    null);

现在,HRMVCAjax 应用程序的横切关注点得到了处理。

请注意,方面是在需要时添加到的对象上的。组件类没有改变。只有通过动态装饰器装饰的对象才具有方面,独立于组件类的其他对象。此外,一个方面可以应用于不同的对象,可以是相同类型或不同类型的。例如,SysConcerns.EnterLog 用于 rpDepartmentRepositoryDepartment 的对象)、rpEmployeeRepositoryEmployee 的对象)和 dpSelDepartment 的对象)。

HRMVCAjax 扩展

由于所有新功能(排序和方面)都已添加到 MVC Controller 或 WCF REST 服务端的服务器端,因此客户端 jQuery 代码没有任何变化。

为方便起见,扩展后的 DepEmployeesController 的代码如下所示。

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

    private IRepository<IEmployee> rpEmployee = null;
    private IRepository<IDepartment> rpDepartment = null;

    public DepEmployeesController()
    {
        rpEmployee = new RepositoryEmployee();
        rpDepartment = new RepositoryDepartment();
    }

    public ActionResult Index()
    {
        rpDepartment = (IRepository<IDepartment>)ObjectProxyFactory.CreateProxy(
            rpDepartment,
            new String[] { "GetAll" },
            new Decoration(new DecorationDelegate(SysConcerns.AdminCheck), 
                           new object[] { Thread.CurrentPrincipal }),
            null);

        rpDepartment = (IRepository<IDepartment>)ObjectProxyFactory.CreateProxy(
            rpDepartment,
            new String[] { "GetAll" },
            new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
            new Decoration(new DecorationDelegate(SysConcerns.ExitLog), null));

        rpDepartment.GetAll();

        rpEmployee = (IRepository<IEmployee>)ObjectProxyFactory.CreateProxy(
            rpEmployee,
            new String[] { "GetAll" },
            null,
            new Decoration((x, y) =>
            {
                object target = x.Target;
                if (target.GetType().ToString() == "ThirdPartyHR.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 = (IRepository<IEmployee>)ObjectProxyFactory.CreateProxy(
            rpEmployee,
            new String[] { "GetAll" },
            new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
            new Decoration(new DecorationDelegate(SysConcerns.ExitLog), null));

        rpEmployee.GetAll();

        List<SelectListItem> depList = new List<SelectListItem>();
        SelectListItem sli = null;
        sli = new SelectListItem();
        sli.Value = "";
        sli.Text = "";
        depList.Add(sli);

        foreach (IDepartment d in rpDepartment.RepList)
        {
            sli = new SelectListItem();
            sli.Value = d.DepartmentID.Value.ToString();
            sli.Text = d.Name;
            depList.Add(sli);
        }

        ViewData["depSel"] = new SelectList(depList, "Value", "Text");
        return View(rpEmployee.RepList);
    }
}

扩展后的 HRRestService 的代码如下所示。

[ServiceContract]
[AspNetCompatibilityRequirements(
    RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class HRRestService
{
    internal class EmployeeLastnameComparer : IComparer<IEmployee>
    {
        public int Compare(IEmployee e1, IEmployee e2)
        {
            return String.Compare(e1.LastName, e2.LastName);
        }
    }
    private static int iStaticDep = 0;

    [WebGet(UriTemplate = "?dep={dep}")]
    public List<Employee> EmployeesByDepartment(string dep)
    {
        IRepository<IEmployee> rpEmployee = null;
        IRepository<IDepartment> rpDepartment = null;
        rpEmployee = new RepositoryEmployee();
        rpDepartment = new RepositoryDepartment();

        rpDepartment.GetAll();

        rpEmployee = (IRepository<IEmployee>)ObjectProxyFactory.CreateProxy(
            rpEmployee,
            new String[] { "GetAll" },
            null,
            new Decoration((x, y) =>
            {
                object target = x.Target;
                if (target.GetType().ToString() == "ThirdPartyHR.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.GetAll();

        IDepartment dpSel = rpDepartment.RepList[Convert.ToInt16(dep) - 1];
        dpSel = (IDepartment)ObjectProxyFactory.CreateProxy(
            dpSel,
            new String[] { "get_DepartmentID" },
            new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
            null);

        iStaticDep = dpSel.DepartmentID.Value;

        List<IEmployee> empSel = null;
        if (rpEmployee.RepList != null)
        {
            empSel = rpEmployee.RepList.FindAll(
                (IEmployee emp) => { return emp.DepartmentID.Value == iStaticDep; });
        }

        List<Employee> empObj = new List<Employee>();
        foreach (IEmployee i in empSel)
        {
            empObj.Add((Employee)i);
        }

        return empObj;
    }
}

需要注意的一点是,由 ObjectProxyFactory.CreateProxy 返回的对象被重新赋值给最初指向目标变量。例如,rpEmployee 最初被赋值为 RepositoryEmployee 对象(目标)。调用 ObjectProxyFactory.CreateProxy 后,它被赋值为返回的对象,这是一个目标的代理。这很微妙但很重要。ObjectProxyFactory.CreateProxy 返回的对象是目标的代理。通过使用相同的变量来表示目标及其代理,原始代码保持不变。这意味着目标及其代理是可互换的。如果变量指向目标,则目标按原样使用。如果变量指向目标的代理,则在目标使用之前或之后会执行额外的功能。实际上,如果您删除所有调用 ObjectProxyFactory.CreateProxy 的代码,您将获得在扩展对象并向对象添加方面之前的原始代码。

最后,在运行应用程序之前,您需要修改 Global.asax 方法,将控制台输出重定向到文件 hrlog.txt。这些更改仅用于此应用程序,因为进入/退出日志记录方面使用控制台。您的应用程序可能会使用不同的日志记录机制。在这种情况下,您可能需要进行相应的更改。修改后的应用程序类如下所示。

public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.Add(new ServiceRoute("HRRestService", 
                   new WebServiceHostFactory(), typeof(Services.HRRestService)));

        routes.MapRoute(
            "Default", // Route name
            "{controller}/{action}/{id}", // URL with parameters
            new { controller = "DepEmployees", action = "Index", id = UrlParameter.Optional }
                  // Parameter defaults
        );

    }

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        RegisterRoutes(RouteTable.Routes);

        FileStream fileStream = null;

        string path = Path.GetDirectoryName(Server.MapPath("~"));
        if (!File.Exists(path + "\\hrlog.txt"))
        {
            fileStream = new FileStream(path + "\\hrlog.txt", FileMode.Create);
        }
        else
            fileStream = new FileStream(path + "\\hrlog.txt", FileMode.Truncate);

        TextWriter tmp = Console.Out;
        Application["origOut"] = tmp;

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

        Application["logStream"] = sw1;
    }

    protected void Application_End(object sender, EventArgs e)
    {
        TextWriter origStrm = (TextWriter)Application["origOut"];
        Console.SetOut(origStrm);

        StreamWriter tmp = (StreamWriter)Application["logStream"];
        Stream fileStream = tmp.BaseStream;

        tmp.Close();
        fileStream.Close();
    }
}

应用程序运行时,您将在文件 hrlog.txt 中看到以下输出。

   at HRMVCAjax.Controllers.DepEmployeesController.Index() 
   in C:\CBDDynDecoratorAJAX\HRMVCAjax\Controllers\DepEmployeesController.cs:line 47
Entering ThirdPartyHR.RepositoryDepartment.GetAll()
Has right to call
Exiting ThirdPartyHR.RepositoryDepartment.GetAll()
   at HRMVCAjax.Controllers.DepEmployeesController.Index() 
     in C:\CBDDynDecoratorAJAX\HRMVCAjax\Controllers\DepEmployeesController.cs:line 72
Entering ThirdPartyHR.RepositoryEmployee.GetAll()
Exiting ThirdPartyHR.RepositoryEmployee.GetAll()
   at HRMVCAjax.Services.HRRestService.EmployeesByDepartment(String dep) 
   in C:\CBDDynDecoratorAJAX\HRMVCAjax\Services\HRRestService.cs:line 73
Entering ThirdPartyHR.Department.get_DepartmentID()
   at HRMVCAjax.Services.HRRestService.EmployeesByDepartment(String dep) 
   in C:\CBDDynDecoratorAJAX\HRMVCAjax\Services\HRRestService.cs:line 73
Entering ThirdPartyHR.Department.get_DepartmentID()

关注点

“根据需要添加方面到对象”和“根据需要扩展对象”的理念被应用于 MVC/AJAX/REST 应用程序。MVC/AJAX/REST 开发人员可能会发现以下原则对于使用动态装饰器处理通用任务很有用。

  • 以通用方式设计组件以满足业务需求
  • 将方面设计为各自模块中的全局方法
  • 按需为对象添加方面
  • 按需扩展对象
© . All rights reserved.