Click here to Skip to main content
15,881,793 members
Articles / Programming Languages / C#

Components, Aspects, and Dynamic Decorator

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
16 Mar 2011CPOL17 min read 30.4K   214   27   8
Extend component functionality and address cross-cutting concerns using Dynamic Decorator in application development.

Introduction

Component Based Development has become the de-facto approach for software development. With benefits ranging from simplification and parallel working to pluggable maintenance and reuse, the financial gains are significant. Components are also regarded as part of the starting platform for service-oriented architecture and play a crucial role in layered architecture.

Components isolate themselves by exposing only interfaces (service contracts) to the outside world. Clients of a component consume the interfaces exposed by the component without knowing or caring about the internal implementation of the component. This is a good thing in terms of separation of concerns of business logic (functional requirements). However, there are another set of concerns which cross several components or may cross the entire application. These are cross-cutting concerns (non-functional requirements), such as authentication, authorization, logging, and instrumentation. Component based development by itself doesn't address cross-cutting concerns. It recourses other tools like AOP to address these concerns.

In this article, I discuss, from an application development point of view, how to use the Dynamic Decorator to extend component functionality and address cross-cutting concerns by following a set of principles.

What is Dynamic Decorator

Dynamic Decorator, introduced in the article Dynamic Decorator Pattern, evolved from the Decorator Pattern. It solves the same problem as the Decorator Pattern does: add additional functionality to an existing object dynamically. However, Dynamic Decorator takes a complete different approach to solve the problem. Instead of creating decoration classes, it uses .NET remoting technology to intercept method calls and applies additional functionality along the way, either before or after the execution of the target methods. One advantage of the Dynamic Decorator is that it doesn't need to design/maintain decoration classes. And it is easy to use and can be applied to any object of any components.

In the article Add Aspects to Objects Using Dynamic Decorator, various features of the Dynamic Decorator are described and different scenarios are discussed to add aspects to objects dynamically at runtime. Using the Dynamic Decorator, you are able to add aspects to your application by providing a method for each aspect. Existing objects, either of the same type or of different types, then can be decorated at runtime by these aspects.

Please refer to the articles Dynamic Decorator Pattern and Add Aspects to Objects Using Dynamic Decorator for details. I urge you to read these two articles before proceeding.

Application Development with Dynamic Decorator

In application development, you use various components to construct your applications. Some components are designed by you. Some are from the Framework. Some are from third party libraries. No matter where the components come from, you face two common issues: extend a component to have additional functionality, and address system wide cross-cutting concerns for your applications.

The Dynamic Decorator naturally fits to address these issues. As you see in the above articles, it is very easy to use the Dynamic Decorator to extend the functionality of a component or to add aspects to an object. In this article, I discuss, from the application development point of view, how the Dynamic Decorator can be used to extend components and address cross-cutting concerns systematically. Using it properly, you will have simpler and more maintainable applications.

Extend Component Functionality

Talking about extending functionality of a component, the first thing you might think of is write a new class derived from the component, then, using the objects of the new class in the application. Although it always works in terms of extending functionality of a component, it often creates other issues like class proliferation. You may end up having a lot of classes to maintain. Or, if you have the source code of the component, you can directly modify the component code to have the new functionality. These approaches are less ideal. They violate the Single-Responsibility Principle (a class should have only one reason to change) and the Open/Closed Principle (software entities should be open for extension but closed for modification). The violation of these principles may cause tremendous maintenance efforts in large software systems.

Normally, whether you need additional functionality for a component is not clear until you use an object of the component in an application, i.e., when you try to call a method of the object. For instance, assume the GetAll method of an employee repository component returns a list of Employees. Depending on situations in the application, you may or may not need to sort the list by last name, birth day, or by some other criteria.

Using the above approaches, if the component does not support your sorting requirement, you write a new class derived from the component to add the sort capability, or modify the component code if you have the source code to add the sort capability. Then you create and use an object of the new class or the modified component. Later, if you need to sort the list by a different criterion or you need to add some additional functionality to another method of the component, you will need to modify the class you created, create yet another class, or modify the component code again. In the end, you will have a lot of classes or open components.

Another approach to extend functionality of a component is to use the Decorator Pattern. The good part of the Decorator Pattern is that it extends functionality for a certain object depending on the particular situation at runtime. However, it still needs to design decoration classes and suffers the same class proliferation and maintenance issues as the above approaches.

Last, but not least, you can use the Dynamic Decorator to extend the functionality of a component. As you may already know by reading the Dynamic Decorator Pattern, adding additional functionality to an object of a component is as simple as writing a method. Take the above example. If you need to sort the list by last name for an object of the employee repository component, you write a method for sorting and call the Dynamic Decorator. Now, the object has sorting capability.

An immediate benefit is that there is no need to create classes or modify component code for extending a component. It also benefits component design during component development. You design your component to satisfy business requirements in a general way. By the time the component is used in an application, if additional functionality is needed, the Dynamic Decorator is used to extend an object of the component at runtime. This way, only the instance gets the additional functionality, independently of other instances of the same component. The end result is that you have a closed component and leaner application.

Address Cross-Cutting Concerns

Cross-cutting concerns are parts of a program which rely on or must affect many other parts of the system. These concerns often cannot be cleanly decomposed from the rest of the system in both design and implementation, and can result in either scattering (code duplication), tangling (significant dependencies), or both.

Aspect-Oriented Programming (AOP) aims to encapsulate cross-cutting concerns into aspects. However, the adoption of AOP is still very limited in component based development. The main reason, I think, is that most AOP tools try to address aspects at component design time. Since you cannot anticipate every scenario in which a component is used by its users, it is difficult to make a decision whether a component needs an aspect or not. Take the Employee component as an example. If an application has only a few instances of Employee, it is probably OK to add logging aspects to the component. But if an application has a million instances of Employee, it is probably not a good idea to add logging aspects to the component. And the worst, if an application started small with a few employees and you decided to have logging aspects for the Employee. And it grew into hundreds of thousands of employees. Now, your application is busy writing employee logs. That is how you shoot yourself in the foot.

Cross-cutting concerns are moving targets. Often, there is no sufficient information to address them at design time of a component. They should be addressed at object level when an object of a component is used in an application. By the time you use an object of a component in your application, you know exactly what aspects you would like the object to have or not to have. For example, you probably want to log an employee when he/she is going to access some sensitive data, say, other employees' salary. You probably don't want to log all employees when you generate a report of all the employees in the company. Unfortunately, most of today's AOP tools do not support object level aspects and do not have the flexibility to add aspects as needed.

As you may already know by reading the article Add Aspects to Objects Using Dynamic Decorator, the Dynamic Decorator can be used to add aspects at object level when an objet of a component is used in an application. You can use an object as is. Or, you can use the Dynamic Decorator to add aspects to it. Take the above example, if you want to log an employee when he/she is going to access some sensitive data, you call the Dynamic Decorator by passing this object with a logging aspect. Now, the object has logging capability. If you don't want to log employees when you generate a report of all employees in the company, just use them as is.

Adding aspects to an object when the object is used means a lot for component based development. First, a component is completely separated from cross-cutting concerns and should only be designed to meet business requirements in a general way. This way, you get a more generic and closed component. Second, aspects are added to an object of the component only if it is needed, independently of other instances of the component in the application. That results in a leaner and more efficient application.

Another advantage of AOP using the Dynamic Decorator is that an aspect can be shared by different objects, either of a same component type or of different component types. If designed properly, your application can have only a few aspects (logging, authentication, authorization, instrumentation, etc.), which can be applied to objects of various component types (Employee, Department, Transaction, etc.). And these objects can be in different layers (data layer, service layer, business layer, UI layers, etc.) of an application. You may also put the aspects in their own modules. This way, different applications can share the same aspects. In the end, these aspects can be applied to objects of different component types, across application layers, or across applications.

Application Development Guidelines

In general, application development involves constructing and using objects of various components following a defined work flow and event processing model. Depending on the type of your application (WinForms, ASP.NET Form, ASP.NET MVC, Silverlight, etc.), there are different ways to define work flows and process events. Regardless of the type of your application, you face common tasks like designing components, extending components, and addressing cross-cutting concerns for your application. The following guidelines provide principles to address these common tasks in application development:

  • Design components to meet business requirements in a general way
  • Design aspects as global methods in their own modules
  • Add aspects to objects as needed
  • Extend objects as needed

As you see in the following discussions and examples, the use of Dynamic Decorator makes it easy to follow these principles.

This article does not discuss how to design components. However, it emphasizes that a component should be designed to meet business requirements in a general way. The goal is to make the component more generic and stable. Take the example of the GetAll method of the employee repository component. Do you want to have five different ways to sort the list for the component? What about if a customer needs another way to sort the list later? Do all objects of the component really need the five different ways to sort the list? If you try to have all these features in the component, you will end up with a fat and open component. So, it is better to defer to decide whether to implement these specifics until the component is used in an application.

With Dynamic Decorator, an aspect is a method (named or anonymous). Since an aspect may be used by different objects, it makes sense to put aspects into their own module or modules as individual global methods. This way, you can design your organization wide aspects, and these aspects can be used by objects, either of the same component type or of different component types, within an application or across applications.

With Dynamic Decorator, adding an aspect to an object syntactically has no difference from extending an object. You just call the Dynamic Decorator for the object by passing a method. However, since an aspect is most likely shared by multiple objects, it is better to make it a global method. On the other hand, it is more convenient to pass an anonymous method to Dynamic Decorator while extending an object since it is mostly an one time thing.

An example is the best way to help you understand the above discussions. Therefore, in these following sections, I present a complete application developed by following the above guidelines.

An Example

In the following sections, a small application HRForms is developed as an example by following the application development guidelines. This application displays employees based on selection of a department and is implemented as a WinForms application.

Components

Assume there are two components, Employee and Department. For Employee, there is a corresponding RepositoryEmployee component to contain a collection of objects of Employee. For Department, there is a corresponding RepositoryDepartment component to contain a collection of objects of Department. The code for these components is listed here:

C#
public interface IEmployee
{
    System.Int32? EmployeeID { get; set; }
    System.String FirstName { get; set; }
    System.String LastName { get; set; }
    System.DateTime DateOfBirth { get; set; }
    System.Int32? DepartmentID { get; set; }
    System.String 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;
    }
}
C#
public interface IDepartment
{
    System.Int32? DepartmentID { get; set; }
    System.String Name { get; set; }
}

public class Department : IDepartment
{
    #region Properties

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

    #endregion

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

    public Department() { }
}
C#
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") };
    }
}

In this application, the data for employees and departments are hard-coded in two lists to simplify our discussion. In a real world application, these data are normally persisted in a relational database. Then, you will need to create a data layer to retrieve them and put them in the lists.

It is also worth noting that the employee list is populated by the inserting order without any kind of sorting in place. It is difficult to anticipate what kinds of sorting to support for this component at this time. In applications, an object of the component may need to be sorted by last name. Another may need to be sorted by birth day. A third may not need sorting at all. So, it is better to defer the implementation of sorting until the component is used in an application.

HRForms

HRForms is a WinForms application which uses the above components to display employees based on selection of a department. Since it is a WinForms application, it follows the WinForms work flow and event model. The code is listed here:

C#
public partial class DepEmpForm : Form
{
    private IRepository<IEmployee> rpEmployee = null;
    private IRepository<IDepartment> rpDepartment = null;

    private static int iStaticDep = 0;
    private bool bLoaded = false;

    public DepEmpForm()
    {
        InitializeComponent();

        rpEmployee = new RepositoryEmployee();
        rpDepartment = new RepositoryDepartment();
    }

    private void DepEmpForm_Load(object sender, EventArgs e)
    {
        bLoaded = false;

        try
        {
            rpDepartment.GetAll();
            comboBox1.DataSource = rpDepartment.RepList;
            comboBox1.ValueMember = "DepartmentID";
            comboBox1.DisplayMember = "Name";
            comboBox1.SelectedIndex = -1;

            rpEmployee.GetAll();
            dataGridView1.DataSource = rpEmployee.RepList;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }

        bLoaded = true;
    }

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

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

            dataGridView1.DataSource = empSel;
        }
    }
}

When it runs, all employees are displayed as follows:

Image 1

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

Image 2

Extend Employee List for Sorting

Now, let's say you want to sort the employees by last name. Since RepositoryEmployee has an unordered employee list, you will need to extend this component to have its list sorted by last name. Here is what you need to do.

First, you create a comparer class for the sorting as follows:

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

Then, call the Dynamic Decorator just before rpEmployee.GetAll() in the event handler DepEmpForm_Load, as follows:

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

That's it. Now, HRForms displays employees sorted by their last names. Build it and run it. You will see that the employees are displayed and ordered by their last names as follows:

Image 3

When you select a department, the employees associated with this department are displayed and ordered by their last names.

Image 4

Note that a Lambda expression is used to provide an anonymous method for this employee repository object to add sorting capability. Of course, you can use a normal method for the sorting logic. However, since this sorting logic is particularly for the employee repository object rpEmployee and not shared by other objects, it is more concise to keep it in an anonymous method.

Design Aspects

Say, you want to address cross-cutting concerns of entering/exiting logging and security checking for your HRForms application. These aspects are put in one class SysConcerns as individual public methods and packed in their own module. The following is the code for these concerns:

C#
public class SysConcerns
{
    public static void EnterLog(AspectContext ctx, object[] parameters)
    {
        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);
    }

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

    public static void AdminCheck(AspectContext ctx, object[] parameters)
    {
        if (parameters != null && 
            ((WindowsPrincipal)parameters[0]).IsInRole(
              "BUILTIN\\" + "Administrators"))
        {
            Console.WriteLine("Has right to call");
            return;
        }

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

EnterLog writes entering logs while ExitLog writes exiting logs. AdminCheck verifies if a user of the application is an administrator.

Of course, you can modify these methods to do whatever you want based on your system requirements. They can be enhanced by accessing various information in the context, the target, and the input parameters.

Use Aspects

Once you define your aspects, you can add them to objects as needed.

Say you want to add the security checking aspect before calling the GetAll method of the repository object rpDepartment of the component RepositoryDepartment. You also want to add the entering log and exiting log to the same object. You add the following code right before rpDepartment.GetAll() in the form loading event handler DepEmpForm_Load.

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

Then, assume you want to add entering log and exiting log to the GetAll method of the object rpEmployee of the RepositoryEmployee component; just insert the following code before rpEmployee.GetAll() in the form loading event handler DepEmpForm_Load.

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

Finally, assume you want to track which department is accessed; you can add the following code in the department selection event handler comboBox1_SelectedIndexChanged just before using the department ID property of the selected object dpSel of the Department component iStaticDep = dpSel.DepartmentID.Value.

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

That completes extending the component and addressing cross-cutting concerns for the HRForms application.

For your convenience, the HRForms code after extending the component and adding the aspects is listed here:

C#
public partial class DepEmpForm : Form
{
    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;

    private static int iStaticDep = 0;
    private bool bLoaded = false;

    public DepEmpForm()
    {
        InitializeComponent();

        rpEmployee = new RepositoryEmployee();
        rpDepartment = new RepositoryDepartment();
    }

    private void DepEmpForm_Load(object sender, EventArgs e)
    {
        bLoaded = false;

        try
        {
            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();
            comboBox1.DataSource = rpDepartment.RepList;
            comboBox1.ValueMember = "DepartmentID";
            comboBox1.DisplayMember = "Name";
            comboBox1.SelectedIndex = -1;

            //Add sorting logic to employee list
            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();
            dataGridView1.DataSource = rpEmployee.RepList;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }

        bLoaded = true;
    }

    private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
    {
        if (comboBox1.SelectedValue != null)
        {
            IDepartment dpSel = (IDepartment)comboBox1.SelectedItem;
            if (bLoaded == true)
            {
                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; });
            }

            dataGridView1.DataSource = empSel;
        }
    }
}

One thing to notice is that the object returned by ObjectProxyFactory.CreateProxy is assigned back to the variable originally pointed to the target. For example, rpEmployee originally was assigned an object of RepositoryEmployee - the target. After calling ObjectProxyFactory.CreateProxy, it is assigned the returned object, which is a proxy of the target. It is subtle but important. The returned object of ObjectProxyFactory.CreateProxy is a proxy of the target. By using the same variable for both the target and its proxy, the original code is intact. That means that the target and its proxy are interchangeable. If the variable is pointed to the target, the target is used as is. If the variable is pointed to the proxy of the target, additional functionality is executed before or after the target is used. Actually, if you remove all the code calling ObjectProxyFactory.CreateProxy, you get the original code before you extended the component and added the aspects.

Last, before running the application, you need to modify the Main method to redirect console output to a file hrlog.txt and set the security policy. These modifications are for this application only since the entering/exiting logging aspects use the console and the security checking aspect uses the Window security policy. Your application may use a different logging mechanism and security policy. In that case, you will need to make the corresponding changes.

C#
static void Main()
{
    string path = Path.GetDirectoryName(Application.ExecutablePath);
    FileStream fileStream = null;
    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;
    StreamWriter sw1 = new StreamWriter(fileStream);
    Console.SetOut(sw1);

    Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new DepEmpForm());

    Console.SetOut(tmp);
    sw1.Close();
    fileStream.Close();
}

When the application runs, you will see the following output in the file hrlog.txt.

C#
Entering ThirdPartyHR.RepositoryDepartment.GetAll()
Has right to call
Exiting ThirdPartyHR.RepositoryDepartment.GetAll()
Entering ThirdPartyHR.RepositoryEmployee.GetAll()
Exiting ThirdPartyHR.RepositoryEmployee.GetAll()
Entering ThirdPartyHR.Department.get_DepartmentID()

Notes: In the source code, the project HRForms contains the initial code prior to extending the component and adding aspects. The project HRFormsExtended contains the code after extending the component and adding the aspects.

Points of Interest

Application development involves common tasks like designing components, extending components, designing aspects, and using aspects. Dynamic Decorator helps in addressing these issues systematically. In particular, Dynamic Decorator makes it easy to:

  • Design components to meet business requirements in a general way
  • Design aspects as global methods in their own modules
  • Add aspects to objects as needed
  • Extend objects as needed

License

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


Written By
United States United States
Object-oriented (OO) is about "classes" not "objects". But I truly believe that "objects" deserve more our attentions. If you agree, read more on... Dynamic Object Programming (DOP), Component-Based Object Extender (CBO Extender), AOP Container and Dynamic Decorator Pattern.

Mobile development is not just another type of front end. The real challenge is actually in the back end: How to present meaningful information in time to mobile users with exponentially increased data flooding around? Here is my first mobile solution: SmartBars - Barcode Reader, Price Comparison and Coupons.

Gary lives in southeast Michigan. My first programming language is FORTRAN. For the last a few years, I have primarily focused on .NET technologies with Mobile Development as my newest interest.

Comments and Discussions

 
QuestionVery Interesting, especially given that the C# tends to handle the decorator pattern on through compiled code Pin
chaoskcw12-Apr-11 22:31
chaoskcw12-Apr-11 22:31 
AnswerRe: Very Interesting, especially given that the C# tends to handle the decorator pattern on through compiled code [modified] Pin
Gary H Guo14-Apr-11 3:03
Gary H Guo14-Apr-11 3:03 
GeneralRe: Very Interesting, especially given that the C# tends to handle the decorator pattern on through compiled code Pin
chaoskcw18-Apr-11 21:56
chaoskcw18-Apr-11 21:56 
GeneralRe: Very Interesting, especially given that the C# tends to handle the decorator pattern on through compiled code Pin
Gary H Guo20-Apr-11 17:37
Gary H Guo20-Apr-11 17:37 
GeneralVery interesting Pin
vbfengshui24-Feb-11 4:56
vbfengshui24-Feb-11 4:56 
GeneralRe: Very interesting Pin
vbfengshui24-Feb-11 4:58
vbfengshui24-Feb-11 4:58 
GeneralRe: Very interesting Pin
Gary H Guo24-Feb-11 5:42
Gary H Guo24-Feb-11 5:42 
Hi vbfengshui,

Thanks for your interesting in my articles.

The Dynamic Decorator uses the proxy feature, which is part of .NET remoting package, for interception of method calls. There is no crossing app domains, or contexts. So, it should be very efficient.

Please let me know if you have further questions.

Regards,

Gary
GeneralRe: Very interesting Pin
Gary H Guo15-Apr-11 9:32
Gary H Guo15-Apr-11 9:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.