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

Silverlight / WCF 服务应用程序的组件、方面和动态装饰器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (4投票s)

2011 年 6 月 1 日

CPOL

10分钟阅读

viewsIcon

21830

downloadIcon

236

将组件、方面和动态装饰器原则应用于 Silverlight / WCF 服务应用程序。

引言

正如《组件、方面和动态装饰器》中所讨论的,应用程序开发涉及特定于应用程序类型的任务和独立于应用程序类型的任务。这种任务分离非常重要,不仅有助于您根据与应用程序类型相关的特定技术决定处理策略、部署策略、测试策略、UI 策略等,还有助于您系统地处理与应用程序类型无关的(即通用)任务,如设计/扩展组件和解决横切关注点。本文讨论了开发这些通用任务的几项指导原则。动态装饰器用于遵循这些指导原则来解决这些任务。本文以 WinForms 应用程序为例。

在本文中,我将演示如何通过使用动态装饰器,将这些原则和指导原则应用于不同类型的应用程序——Silverlight / WCF 服务应用程序。

选择应用程序类型

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

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

处理通用任务

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

在文章《组件、方面和动态装饰器》中,给出了一组开发这些通用任务的原则。它们再次列出如下:

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

有关这些原则的详细讨论,请参阅《组件、方面和动态装饰器》。您可能还需要阅读文章《动态装饰器模式》以理解动态装饰器,并阅读文章《使用动态装饰器向对象添加方面》以理解使用动态装饰器进行方面编程。

示例

在以下章节中,将讨论一个示例应用程序,以演示如何通过使用动态装饰器将上述原则应用于 Silverlight / WCF 服务应用程序。

这里使用了《组件、方面和动态装饰器》中作为示例讨论的相同问题。这次,我们选择 Silverlight / WCF 服务应用程序作为应用程序类型,而不是 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 组件时不考虑排序,遵循了“以通用方式设计组件以满足业务需求”的原则。这样,组件是稳定的和封闭的。

HRSilverlight

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

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();

        HRServiceClient proxy = new HRServiceClient();
        proxy.DepartmentAllCompleted += 
          new EventHandler<DepartmentAllCompletedEventArgs>(proxy_DepartmentAllCompleted);
        proxy.EmployeeAllCompleted += 
          new EventHandler<EmployeeAllCompletedEventArgs>(proxy_EmployeeAllCompleted);

        proxy.DepartmentAllAsync();
        proxy.EmployeeAllAsync();
    }

    void proxy_DepartmentAllCompleted(object sender, DepartmentAllCompletedEventArgs e)
    {
        comboBox1.DataContext = e.Result;
    }

    void proxy_EmployeeAllCompleted(object sender, EmployeeAllCompletedEventArgs e)
    {
        dataGrid1.DataContext = e.Result;
    }

    private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        Department d = comboBox1.SelectedValue as Department;

        HRServiceClient proxy = new HRServiceClient();
        proxy.EmployeeByDepartmentIDAsync(d);
        proxy.EmployeeByDepartmentIDCompleted += new 
          EventHandler<EmployeeByDepartmentIDCompletedEventArgs>(
          proxy_EmployeeByDepartmentIDCompleted);
    }

    void proxy_EmployeeByDepartmentIDCompleted(object sender, 
         EmployeeByDepartmentIDCompletedEventArgs e)
    {
        dataGrid1.DataContext = e.Result;
    }
}

上述代码在客户端设备(例如浏览器)上执行。大部分业务逻辑作为 WCF 服务 HRService 封装在服务器端,Silverlight 代码通过客户端存根 HRServiceClient 使用该服务。

HRService 的代码如下所示。

[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = 
       AspNetCompatibilityRequirementsMode.Allowed)]
public class HRService
{
    private static int iStaticDep = 0;

    [OperationContract]
    public List<Department> DepartmentAll()
    {
        IRepository<IDepartment> rpDepartment = null;
        rpDepartment = new RepositoryDepartment();
        rpDepartment.GetAll();

        List<Department> depObj = new List<Department>();
        foreach (IDepartment i in rpDepartment.RepList)
        {
            depObj.Add((Department)i);
        }
        return depObj;
    }

    [OperationContract]
    public List<Employee> EmployeeAll()
    {
        IRepository<IEmployee> rpEmployee = null;
        rpEmployee = new RepositoryEmployee();
        rpEmployee.GetAll();

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

    [OperationContract]
    public List<Employee> EmployeeByDepartmentID(Department dep)
    {
        IDepartment iDep = (IDepartment)dep;
        iStaticDep = iDep.DepartmentID.Value;

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

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

需要注意的是,方法返回的是类类型列表 List<Department>List<Employee>,而不是接口类型列表 List<IDepartment>List<IEmployee>。原因是 WCF 根据契约而不是类型进行序列化,因此不知道如何序列化 IEmployeeIDepartment 等接口类型。

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

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

对员工列表进行排序

现在,假设您希望 rpEmployee 对象(RepositoryEmployee 组件的一个实例)具有按姓氏对员工进行排序的功能。您需要在 HRService 中执行以下操作。

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

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

然后,在 EmployeeAll 方法中的 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));

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

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

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

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

设计方面

假设您希望 HRSilverlight 应用程序处理进入/退出日志和安全检查的横切关注点。通过遵循“将方面设计为各自模块中的全局方法”的原则,这些方面被放在一个类 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 写入日志并返回。

您可能需要根据您的系统需求修改这些方法。您还可以通过访问上下文、目标和输入参数中的各种信息来增强它们。要了解如何使用上下文、目标和参数来增强您的方面,请参阅《使用动态装饰器向对象添加方面》。

使用方面

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

假设您希望在调用 RepositoryDepartment 组件的存储库对象 rpDepartmentGetAll 方法之前添加安全检查方面。您还希望向同一对象添加进入日志和退出日志。您在 DepartmentAll 方法中的 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 组件的 rpEmployee 对象的 GetAll 方法添加进入日志和退出日志,只需在 EmployeeAll 方法中的 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));

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

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

现在,HRSilverlight 应用程序的横切关注点已得到解决。

请注意,方面是按需添加到对象中的。组件类中没有发生任何更改。并且只有使用动态装饰器装饰的对象才具有这些方面,这独立于组件类的其他对象。此外,一个方面可以应用于不同的对象,无论是相同类型还是不同类型。例如,SysConcerns.EnterLog 用于 rpDepartmentRepositoryDepartment 的对象)、rpEmployeeRepositoryEmployee 的对象)和 dpSelDepartment 的对象)。

HRSilverlight 扩展版

由于所有新功能(排序和方面)都添加到了 WCF 服务端的服务器端,因此客户端的 Silverlight 代码没有变化。

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

[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = 
       AspNetCompatibilityRequirementsMode.Allowed)]
public class HRService
{
    internal class EmployeeAgeComparer : IComparer<IEmployee>
    {
        public int Compare(IEmployee e1, IEmployee e2)
        {
            return DateTime.Compare(e1.DateOfBirth, e2.DateOfBirth);
        }
    }

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

    private static int iStaticDep = 0;

    [OperationContract]
    public List<Department> DepartmentAll()
    {
        IRepository<IDepartment> rpDepartment = null;
        rpDepartment = new RepositoryDepartment();

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

        List<Department> depObj = new List<Department>();
        foreach (IDepartment i in rpDepartment.RepList)
        {
            depObj.Add((Department)i);
        }
        return depObj;
    }

    [OperationContract]
    public List<Employee> EmployeeAll()
    {
        IRepository<IEmployee> rpEmployee = null;
        rpEmployee = new RepositoryEmployee();

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

        //Add entering log to employee list
        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<Employee> empObj = new List<Employee>();
        foreach (IEmployee i in rpEmployee.RepList)
        {
            empObj.Add((Employee)i);
        }
        return empObj;
    }

    [OperationContract]
    public List<Employee> EmployeeByDepartmentID(Department dep)
    {
        IDepartment iDep = (IDepartment)dep;
        iDep = (IDepartment)ObjectProxyFactory.CreateProxy(
            iDep,
            new String[] { "get_DepartmentID" },
            new Decoration(new DecorationDelegate(SysConcerns.EnterLog), null),
            null);

        iStaticDep = iDep.DepartmentID.Value;

        IRepository<IEmployee> rpEmployee = null;
        rpEmployee = new RepositoryEmployee();

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

        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。这些修改仅适用于此应用程序,因为进入/退出日志方面使用了控制台。您的应用程序可能使用不同的日志机制。在这种情况下,您可能需要进行相应的更改。修改后的应用程序类如下所示。

void Application_Start(object sender, EventArgs e) 
{
    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;
}
    
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 HRSilverlight.Web.HRServiceExtended.EmployeeAll() in 
   C:\CBDDynDecoratorSilverlight\HRSilverlight.Web\HRServiceExtended.svc.cs:line 94
Entering ThirdPartyHR.RepositoryEmployee.GetAll()
Exiting ThirdPartyHR.RepositoryEmployee.GetAll()
   at HRSilverlight.Web.HRServiceExtended.DepartmentAll() in 
   C:\CBDDynDecoratorSilverlight\HRSilverlight.Web\HRServiceExtended.svc.cs:line 55
Entering ThirdPartyHR.RepositoryDepartment.GetAll()
Has right to call
Exiting ThirdPartyHR.RepositoryDepartment.GetAll()
   at HRSilverlight.Web.HRServiceExtended.EmployeeByDepartmentID(Department dep) in 
   C:\CBDDynDecoratorSilverlight\HRSilverlight.Web\HRServiceExtended.svc.cs:line 114
Entering ThirdPartyHR.Department.get_DepartmentID()
   at HRSilverlight.Web.HRServiceExtended.DepartmentAll() in 
   C:\CBDDynDecoratorSilverlight\HRSilverlight.Web\HRServiceExtended.svc.cs:line 55
Entering ThirdPartyHR.RepositoryDepartment.GetAll()
Has right to call
Exiting ThirdPartyHR.RepositoryDepartment.GetAll()
   at HRSilverlight.Web.HRServiceExtended.EmployeeAll() in 
   C:\CBDDynDecoratorSilverlight\HRSilverlight.Web\HRServiceExtended.svc.cs:line 94
Entering ThirdPartyHR.RepositoryEmployee.GetAll()
Exiting ThirdPartyHR.RepositoryEmployee.GetAll()
   at HRSilverlight.Web.HRServiceExtended.EmployeeByDepartmentID(Department dep) in 
   C:\CBDDynDecoratorSilverlight\HRSilverlight.Web\HRServiceExtended.svc.cs:line 114
Entering ThirdPartyHR.Department.get_DepartmentID()
   at HRSilverlight.Web.HRServiceExtended.EmployeeByDepartmentID(Department dep) in 
   C:\CBDDynDecoratorSilverlight\HRSilverlight.Web\HRServiceExtended.svc.cs:line 114
Entering ThirdPartyHR.Department.get_DepartmentID()

关注点

“按需向对象添加方面”和“按需扩展对象”的理念已应用于 Silverlight / WCF 服务应用程序。Silverlight / WCF 服务开发人员可能会发现以下原则对于使用动态装饰器处理常见任务很有用。

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