Wednesday, December 30, 2009

Unity Application Block – Interceptor pattern


An interceptor pattern is a software design pattern that is used when software systems or frameworks want to offer a way to change, or augment, their usual processing cycle. Key aspects of the pattern are that the change is transparent and used automatically. In essence, the rest of the systems does not have to know something has been added or changed and can keep working as before. To facilitate this, a predefined interface for extension has to be implemented, some kind of dispatching mechanism is required where interceptors are registered (this may be dynamic, at runtime, or static, e.g. through configuration files) and context objects are provided, which allow access to the frameworks internal state.
The interceptor design pattern can be used for addressing issues like cross-cutting concerns, issues that cut across the entire software. Many of the cross cutting concerns happen only at either the start or the end of a method. You log when a method is called. You check your inputs for validity before processing. You handle exceptions after the method fails. This leads to a different approach to implementing cross-cutting concerns. You can put some special handling before or after method calls to handle the cross-cutting concerns, get the code out of the methods, and enhance its maintainability. Unity provides support for interception through the Interception container extension.
The interception mechanism is based around three basic concepts: matching rules, call handlers, and interceptors. Matching rules are simple but flexible objects that determine which methods should have extra handling applied. Call handlers are objects that actually implement the cross-cutting concerns. They can run before or after the method. They can modify method parameters or return values. They can even stop the method from being called at all or call it multiple times. The matching rules and call handlers are grouped together into interception policies. The interception policy uses the matching rules to define which methods get intercepted and uses the call handlers to define what processing to perform on the intercepted object.
For e:g we can implement a logic for every method to check whether the parameters passed to the method are validated against null reference. My first task is to implement a call handler for this cross cutting concern. Unity provides an Interface ICallHandler to define call handlers in your code.
public class ParameterInfoNotNullHandler : ICallHandler
{
    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        for (int i = 0; i < input.Inputs.Count; i++)
        {
            object target = input.Inputs[i];
            if (target == null)
            {
                ParameterInfo parameterInfo = input.Inputs.GetParameterInfo(i);
                ArgumentNullException ex = new ArgumentNullException(parameterInfo.Name);
                return input.CreateExceptionMethodReturn(ex);
            }
        }

        return getNext()(input, getNext);
    }

    public int Order { get; set; }
}
Now for defining an attribute to be used for injecting the logic I define a ParameterInfoNotNullAttribute
public class ParameterInfoNotNullAttribute : HandlerAttribute
{
    public override ICallHandler CreateHandler(Microsoft.Practices.Unity.IUnityContainer container)
    {
        return new ParameterInfoNotNullHandler();
    }
}
And when I define my interface with the methods
public interface IRepository
{
-    - Other methods
    [ParameterInfoNotNull(Order = 1)]
    T GetById(Id id); 
}
The class that implements the interface does not need to worry about the null validation
public Employee GetById(Guid id)
{
    return ___EmployeeCollection.First<Employee>(x => x.Id == id);
}
The only thing left is to configure the container.
IUnityContainer ___Container = new UnityContainer();
___Container.AddNewExtension<Interception>();
___Container.RegisterType<IRepository<Employee, Guid>, EmployeeRepository>();
___Container.Configure<Interception>().SetDefaultInterceptorFor<IRepository<Employee, Guid>>(new TransparentProxyInterceptor());

No comments: