2. AOP in Action
To finish off our AOP overview, let’s proceed with a full example
that demonstrates how to achieve AOP benefits in .NET applications.
We’ll use Microsoft’s Policy Injection Application Block in Enterprise
Library 3.0 and higher to add aspects to our demo. For more information
on PIAB, see http://msdn.microsoft.com/en-us/library/cc511729.aspx.
The following code demonstrates a simple console application that
uses the Unity IoC container to obtain a reference to a class that
exposes a given interface—ICustomerServices:
public interface ICustomerServices
{
void Delete(string customerID);
}
static void Main(string[] args)
{
// Set up the IoC container
UnityConfigurationSection section;
section = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;
IUnityContainer container = new UnityContainer();
section.Containers.Default.Configure(container);
// Resolve a reference to ICustomerServices. The actual class returned depends
// on the content of the configuration section.
ICustomerServices obj = container.Resolve<ICustomerServices>();
// Enable policies on the object (for example, enable aspects)
ICustomerServices svc = PolicyInjection.Wrap<ICustomerServices>(obj);
// Invoke the object
svc.Delete("ALFKI");
// Wait until the user presses any key
Console.ReadLine();
}
After you have resolved the dependency on the ICustomerServices
interface, you pass the object to the PIAB layer so that it can wrap
the object in a policy-enabled proxy. What PIAB refers to here as a policy is really like what many others call, instead, an aspect.
In the end, the Wrap
static method wraps a given object in a proxy that is driven by the
content of a new section in the configuration file. The section policyInjection defines the semantics of the aspect. Let’s have a look at the configuration file.
PIAB is driven by the content of an ad hoc configuration section.
There you find listed the policies that drive the behavior of generated
proxies and that ultimately define aspects to be applied to the object
within the proxy.
<policyInjection>
<policies>
<add name="Policy">
<matchingRules>
<add type="EnterpriseLibrary.PolicyInjection.MatchingRules.TypeMatchingRule ..."
name="Type Matching Rule">
<matches>
<add match="ArchNet.Services.ICustomerServices" ignoreCase="false" />
</matches>
</add>
</matchingRules>
<handlers>
<add order="0"
type="ManagedDesign.Tools.DbLogger, mdTools"
name="Logging Aspect" />
</handlers>
</add>
</policies>
</policyInjection>
The matchingRules section expresses type-based criteria for a pointcut. It states that whenever the proxy wraps an object of type ICustomerServices it has to load and execute all listed handlers. The attribute order indicates the order in which the particular handler has to be invoked.
From this XML snippet, the result of this is that ICustomerServices is now a log-enabled type.
All that remains to be done—and it is the key step, indeed—is to take
a look at the code for a sample handler. In this case, it is the DbLogger class:
public interface ILogger
{
void LogMessage(string message);
void LogMessage(string category, string message);
}
public class DbLogger : ILogger, ICallHandler
{
// ILogger implementation
public void LogMessage(string message)
{
Console.WriteLine(message);
}
public void LogMessage(string category, string message)
{
Console.WriteLine(string.Format("{0} - {1}", category, message));
}
// ICallHandler implementation
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
// Advice that runs BEFORE
this.LogMessage("Begin ...");
// Original method invoked on ICustomerServices
IMethodReturn msg = getNext()(input, getNext);
// Advice that runs AFTER
this.LogMessage("End ...");
return msg;
}
public int Order{ get; set; }
}
The class DbLogger implements two interfaces. One is its business-specific interface ILogger; the other (ICallHandler) is a PIAB-specific interface through which advice code is injected into the class’s aspect list. The implementation of ICallHandler is fairly standard. In the Invoke method, you basically redefine the flow you want for any aspect-ed methods.
In summary, whenever a method is invoked on a type that implements ICustomerServices,
the execution is delegated to a PIAB proxy. The PIAB proxy recognizes a
few handlers and invokes them in a pipeline. Each handler does the
things it needs to do before the method executes. When done, it yields
to the next handler delegate in the pipeline. The last handler in the
chain yields to the object that executes its method. After that, the
pipeline is retraced and each registered handler has its own chance to execute its postexecution code. Figure 3 shows the overall pipeline supported by PIAB.