Introduction
The article AOP Container introduced an AOP model that enables any IoC container to have AOP capabilities by configuration.
As examples, the model was applied to MEF, Unity, and
Windsor Containers.
Since its publishing, the AOP Container has been enhanced with new features such as registering aspects using code and adding aspects to an object after its creation.
These features are discussed in this article.
Background
The reason for me to create an AOP Container is to make it easy to add AOP capabilities to IoC Containers. Therefore, it makes sense for
the AOP Container to provide methods to register aspects using code just like most IoC Containers provide methods to register types using code.
There are also situations you may not want all objects of a component to have an aspect, but would like to add or chain aspects to a specific object as needed
after its creation. The AOP Container offers this flexibility by providing a method for you to use after the creation of an object.
In the following sections, I discuss how to register aspects to a component using code and how to chain aspects to an object after its creation. The Windsor Container
and Unity Container are used as examples of IoC Containers.
Using the Code
The following code demonstrates the use of the AOP Container together with the Windsor Container and Unity Container. Basically, the AOP Container provides a common
application programming interface for aspect registration and object resolving.
static void Main(string[] args)
{
AOPContainer aopcontainer = null;
switch (args[0])
{
case "0":
{
IWindsorContainer windsorContainer = new WindsorContainer();
windsorContainer.Register(AllTypes
.FromAssembly(Assembly.LoadFrom("Employee.dll"))
.Where(t => t.Name.Equals("Employee"))
.Configure(c => c.LifeStyle.Transient)
).Register(AllTypes
.FromAssembly(Assembly.LoadFrom("Department.dll"))
.Where(t => t.Name.Equals("Department"))
.Configure(c => c.LifeStyle.Transient)
).Register(AllTypes
.FromAssembly(Assembly.GetExecutingAssembly())
.Where(t => (t.Name.Equals("RepositoryEmployee") ||
t.Name.Equals("RepositoryDepartment")))
.Configure(c => c.LifeStyle.Transient)
).Register(Castle.MicroKernel.Registration.Component.For(typeof(List<IEmployee>))
).Register(Castle.MicroKernel.Registration.Component.For(typeof(List<IDepartment>))
);
aopcontainer = new AOPWindsorContainer(windsorContainer);
}
break;
case "1":
{
IUnityContainer unityContainer = new UnityContainer();
unityContainer.RegisterType<IEmployee, Employee>(new InjectionConstructor()
).RegisterType<IDepartment, Department>(new InjectionConstructor()
).RegisterType<IRepository<IDepartment>, RepositoryDepartment>(new InjectionConstructor()
).RegisterType<IRepository<IEmployee>, RepositoryEmployee>(new InjectionConstructor()
).RegisterType<IList<IEmployee>, List<IEmployee>>(new InjectionConstructor()
).RegisterType<IList<IDepartment>, List<IDepartment>>(new InjectionConstructor()
);
aopcontainer = new AOPUnityContainer(unityContainer);
}
break;
default:
{
Console.WriteLine("Invalid number for an AOP container.");
Console.ReadLine();
}
return;
}
aopcontainer.RegisterAspect<RepositoryDepartment,
IRepository<IDepartment>>("GetAll",
new Decoration(SharedLib.SharedConcerns.EnterLog, null),
new Decoration(SharedLib.SharedConcerns.ExitLog, null)
).RegisterAspect<RepositoryEmployee, IRepository<IEmployee>>("GetAll",
null,
new Decoration((ctx, parameters) =>
{
object target = ctx.Target;
if (target.GetType().ToString() == "ConsoleUtil.RepositoryEmployee")
{
List<IEmployee> emps = ((IRepository<IEmployee>)target).RepList;
IEnumerable<IEmployee> query = emps.OrderByDescending(emp => emp,
new EmployeeLastnameComparer()).ToList<IEmployee>();
((IRepository<IEmployee>)target).RepList = (List<IEmployee>)query;
}
}, null)
).RegisterAspect<Department, IDepartment>("get_DepartmentID",
new Decoration(SharedLib.SharedConcerns.EnterLog, null),
null);
Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
DataSet dsDB = new DataSet();
dsDB.ReadXml("employees.xml");
Console.WriteLine("Employees");
string str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
Console.WriteLine(str);
Console.WriteLine("====================================================");
foreach (DataRow r in dsDB.Tables["Employee"].Rows)
{
str = r["EmployeeID"].ToString() + "\t" +
r["FirstName"].ToString() + "\t" +
r["LastName"].ToString() + "\t" +
r["DateOfBirth"].ToString() + "\t" +
r["DepartmentID"].ToString();
Console.WriteLine(str);
}
Console.WriteLine();
IRepository<IDepartment> rpDepartment =
aopcontainer.Resolve<RepositoryDepartment,
IRepository<IDepartment>>("GetAll");
rpDepartment.AopContainer = aopcontainer;
rpDepartment.DataSource = dsDB;
rpDepartment.GetAll();
int? iDep = rpDepartment.RepList[0].DepartmentID;
IRepository<IEmployee> rpEmployee =
aopcontainer.Resolve<RepositoryEmployee,
IRepository<IEmployee>>("GetAll");
rpEmployee.AopContainer = aopcontainer;
rpEmployee.DataSource = dsDB;
rpEmployee.GetAll();
Console.WriteLine();
Console.WriteLine("Employees sorted by employee's last name");
str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
Console.WriteLine(str);
Console.WriteLine("====================================================");
foreach (IEmployee em in rpEmployee.RepList)
{
str = em.EmployeeID.ToString() + "\t" + em.FirstName +
"\t" + em.LastName + "\t" +
em.DateOfBirth.ToShortDateString() + "\t" +
em.DepartmentID.ToString();
Console.WriteLine(str);
}
Console.WriteLine();
try
{
IEmployee oEm = rpEmployee.RepList[0];
oEm = AOPContainer.ChainAspect<IEmployee, IEmployee>(oEm,
"set_EmployeeID,set_FirstName,set_LastName,set_DateOfBirth,set_DepartmentID",
new Decoration(AppConcerns.SecurityCheck,
new object[] { System.Threading.Thread.CurrentPrincipal }),
null);
oEm.DepartmentID = 2;
oEm = AOPContainer.ChainAspect<IEmployee, IEmployee>(oEm,
"DetailsByLevel",
new Decoration(SharedLib.SharedConcerns.EnterLog, null),
new Decoration(SharedLib.SharedConcerns.ExitLog, null));
oEm.DetailsByLevel(3);
Console.WriteLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine();
}
Console.WriteLine("Employees after sort and update");
str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
Console.WriteLine(str);
Console.WriteLine("====================================================");
foreach (IEmployee em in rpEmployee.RepList)
{
str = em.EmployeeID.ToString() + "\t" + em.FirstName + "\t" +
em.LastName + "\t" + em.DateOfBirth.ToShortDateString() +
"\t" + em.DepartmentID.ToString();
Console.WriteLine(str);
}
Console.WriteLine();
aopcontainer.IocContainer.Dispose();
Console.ReadLine();
}
The first part of the code (code between the switch ... case
clause) is specific to IoC Containers. That is, depending on which IoC Container is used,
you register your components to the IoC Container and create the corresponding AOP Container. The code for case "0" is specific to Windsor Container while
the code for case "1" is specific to Unity Container. The code basically registers types for the IoC Containers so that later you can use the IoC Containers
to resolve an object of the registered type. Depending on which IoC Container is used, you also create a corresponding AOP Container. For Windsor Container,
an AOPWindsorContainer
is created by passing the instance of the Windsor Container. For Unity Container, an AOPUnityContainer
is created
by passing the instance of the Unity Container.
The rest of the code is independent of IoC Containers. Before we continue, let me explain a little more detail of the RegisterAspect
method
of the AOPContainer
. The declaration of RegisterAspect
is as follows.
public AOPContainer RegisterAspect<T, V>(string methods,
Decoration preDeco, Decoration postDeco) where T : V
T
- target type (target is the original object to which aspects are attached)V
- interface implemented by the target type and returned when using the AOPContainer
to resolve an object of T
methods
- names (separated by comma if more than one) of target's methods which will be attached the aspects specified by preDeco
and postDeco
preDeco
- preprocessing Decoration
postDeco
- postprocessing Decoration
The Decoration
is an aggregation of DecorationDelegate
and object[]
. Its constructor is as follows:
public Decoration(DecorationDelegate aspectHandler, object[] parameters)
aspectHandler
- a delegate with AspectContext
as first parameter, object[]
as second parameter, and void
as returnparameters
- array of objects passed in the aspectHandler
as its second parameter when invoking
DecorationDelegate
is defined as follows:
public delegate void DecorationDelegate(AspectContext ctx, object[] parameters)
Essentially, an aspect of AOP Container is a method that takes the signature of DecorationDelegate
.
Hope that gives you enough background to understand the following aspect registration code.
The code:
aopcontainer.RegisterAspect<RepositoryDepartment, IRepository<IDepartment>>("GetAll",
new Decoration(SharedLib.SharedConcerns.EnterLog, null),
new Decoration(SharedLib.SharedConcerns.ExitLog, null)
)
registers the preprocessing aspect SharedLib.SharedConcerns.EnterLog
and the postprocessing aspect SharedLib.SharedConcerns.ExitLog
to the GetAll
method of RepositoryDepartment
which implements IRepository<IDepartment>
.
SharedLib.SharedConcerns.EnterLog
and SharedLib.SharedConcerns.ExitLog
are aspect methods to write entering/exiting logs, respectively.
The code:
RegisterAspect<RepositoryEmployee, IRepository<IEmployee>>("GetAll",
null,
new Decoration((ctx, parameters) =>
{
object target = ctx.Target;
if (target.GetType().ToString() == "ConsoleUtil.RepositoryEmployee")
{
List<IEmployee> emps = ((IRepository<IEmployee>)target).RepList;
IEnumerable<IEmployee> query = emps.OrderByDescending(emp => emp,
new EmployeeLastnameComparer()).ToList<IEmployee>();
((IRepository<IEmployee>)target).RepList = (List<IEmployee>)query;
}
}, null)
)
registers a null preprocessing aspect and an anonymous method as postprocessing aspect to the GetAll
method of RepositoryEmployee
which implements IRepository<IEmployee>
. Note that the anonymous method provides sorting logic to the RepositoryEmployee
.
The code:
RegisterAspect<Department, IDepartment>("get_DepartmentID",
new Decoration(SharedLib.SharedConcerns.EnterLog, null),
null)
registers the preprocessing aspect SharedLib.SharedConcerns.EnterLog
and the null postprocessing aspect to the get_DepartmentID
method
of Department
which implements IDepartment
.
We're done with the type registration and aspect registration. Before we use the AOP Container to do something useful, we set the security policy to WindowsPrincipal,
load the departments and employees into a dataset, and print out the employees.
Now, we are ready to use the AOP Container to do something.
First, we use the aopcontainer
to resolve a RepositoryDepartment
object. It returns an interface of IRepository<IDepartment>
to the variable rpDepartment
. When using rpDepartment
to call the GetAll
method, you will see the entering log before and exiting log after
the method's execution of the target.
Next, rpDepartment.RepList[0].DepartmentID
will write the entering log before returning the department ID. The reason it works like this is that
the aopcontainer
is used to resolve individual departments inside the GetAll
of the RepositoryDepartment
.
Similarly, the aopcontainer
is used to resolve a RepositoryEmployee
object which returns an interface of IRepository<IEmployee>
to the variable rpEmployee
. When using rpEmployee
to call the GetAll
method, it will sort the employee list and save it in the target.
Registering aspects to a component using the RegisterAspect<T, V>
method means all objects of the component resolved by Resolve<T, V>
will have the aspects attached. However, there are situations in which you don't want all objects of a component to have an aspect attached, but would like to add an aspect
to an object after its creation. There are also situations where you would like to chain more aspects to an object after its creation. The ChainAspect<T, V>
method of AOPContainer
provides a solution for these situations and can be used to add or chain aspects to an object as needed.
In the try
block of the above code, ChainAspect<IEmployee, IEmployee>
adds the security check aspect to the first employee
so that only the built-in administrators can modify the employee's properties. If the application is executed by a non-administrator, an exception will throw and the property is not modified.
Run the application, and the output looks as follows:
Points of Interest
- The AOP Container provides a mechanism and a common application programming interface for adding aspects to IoC Containers.
- The AOP Container provides a Fluent interface to register aspects to components. Objects resolved by the AOP Container have aspects attached.
- The AOP Container provides a method to add/chain aspects to an object as needed after its creation.
- Although this article discusses the AOP Container with Windsor and Unity IoC Containers, you should be able to easily combine the AOP Container with other IoC Containers.