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 Employee
s. 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:
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") };
}
}
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:
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:
When a department is selected, the employees for that department are displayed.
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:
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:
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:
When you select a department, the employees associated with this department are displayed and ordered by their last names.
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:
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
.
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
.
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
.
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:
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;
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();
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.
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.
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