As an appendix to this series on refactoring an old, ASP.NET WebForms application to an MVC app that follows SOLID principles, I would like to show how you can implement the Open/Closed principle using the Unity dependency-injection framework.
The downloadable code and instructions for compiling it are available in Part 6, but I'll try to include enough in this post that you can follow along without it.
In Part 8, we used Unity and a web.config file to instantiate a concrete DocumentSearcher for the ISearcher<Document> interface.
The web.config contained this...
<container>
<register type="ISearcher[Document]" mapTo="DocumentSearcher" />
</container>
...and our code did this.
using (var unity = new UnityContainer())
{
unity.LoadConfiguration();
WebAppControllerFactory.DocumentSearcher = unity.Resolve<ISearcher<Document>>();
}
Now suppose that we want to wrap DocumentSearcher's Search method with some code that measures how long the search takes. Using the ideas in Part 3, we could write a TimedSearcher<T> that wrapped DocumentSearcher (or any other ISearcher<T>) but wouldn't it be better if we could write something that could time any method in any class?
That's what we're going to do now. This is the MethodTimer class, which you'll find in the Solid.Diagnostics project in the downloadable solution. It inherits from IInterceptionBehavior, which is a Unity interface. The explanation is after the code.
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace Solid.Diagnostics
{
public class MethodTimer : IInterceptionBehavior
{
#region Fields
// I wish there were a way to pass the file name into the constructor.
// There *is* if the interceptor is injected with code, but I can't make it happend with the config-file version.
string _fileName = @"c:\TEMP\solid.txt";
#endregion
#region Constructors
/// <summary>
/// Default constructor.
/// </summary>
public MethodTimer()
{
}
#endregion
#region IInterceptionBehavior Members
public IEnumerable<Type> GetRequiredInterfaces()
{
return Type.EmptyTypes;
}
public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
// Before the intercepted method, start a stopwatch.
var stopwatch = new Stopwatch();
stopwatch.Start();
// Call the intercepted method (or the next interceptor in the chain).
var methodReturn = getNext().Invoke(input, getNext);
// After the intercepted method, stop the stopwatch and report the results.
stopwatch.Stop();
File.AppendAllText(_fileName, String.Format("{0}.{1} took {2} milliseconds.\r\n",
input.Target.GetType(),
input.MethodBase.Name,
stopwatch.ElapsedMilliseconds));
// Return what we got from the intercepted method.
return methodReturn;
}
/// <summary>
/// The interceptor will always execute.
/// </summary>
public bool WillExecute
{
get { return true; }
}
#endregion
}
}
The heart of the matter is the Invoke method. Unity's interface interception mechanism will call this method in lieu of whatever it would normally call in a resolved class (DocumentSearcher.Search in our case).
In Invoke, we have the chance to to whatever we want before and after calling the intercepted method. The comments in the code should make all this clear.
You can wire this up in code, but using the config file is usually preferable:
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
</configSections>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration" />
<assembly name="Solid.Core" />
<namespace name="Solid.Core" />
<assembly name="Solid.Interfaces" />
<namespace name="Solid.Interfaces" />
<assembly name="Solid.MyDatabase"/>
<namespace name="Solid.MyDatabase"/>
<assembly name="Solid.WebApp"/>
<namespace name="Solid.WebApp"/>
<assembly name="Solid.Diagnostics"/>
<namespace name="Solid.Diagnostics"/>
<container>
<extension type="Interception"/>
<register type="ISearcher[Document]" mapTo="DocumentSearcher" >
<interceptor type="InterfaceInterceptor"/>
<interceptionBehavior type="MethodTimer" />
</register>
</container>
</unity>
Interception is not a default feature of Unity, so you must add the <sectionExtension> element under <unity> and the <extension> element under <container>. Then, you may add the <interceptor> and<interceptionBehavior> child elements of the type registration.
That's all there is to it! You have now extended the DocumentSearcher in the most general way possible, and wired everything up without changing one line of existing code: the ultimate embodiment of the Open/Closed Principle.
I hope you have enjoyed this series on refactoring to SOLID!