Skip to main content

AOP with Enterprise Library Policy Injection Block

I used to think aspect oriented programming (AOP) is not a very useful idea because quite often it only saves few keystrokes but requires a massive configuration file. I only came to realise the true value of AOP recently when we have to convert 4+ million lines of legacy COBOL code to .NET with Microfocus’ Visual COBOL compiler. Manually adding exception handling and logging code to these existing COBOL subroutines will not only take a lot of time but also makes code merging task much harder later.

I started experimenting with the policy injection block in the Enterprise Library to see if it will make this job simpler. I tried to google for AOP and Enterprise Library tutorials but I found most tutorials on the web are either old (most were done using Enterprise Library 3) or over-complicated. Especially many of them use the Enterprise Library Logging Block to demonstrate the policy injection concept, which requires lots of configurations itself.

The policy injection in Enterprise Library is actually pretty easy to use and therefore in this tutorial I decided to demonstrate it with a simple custom call handler that requires no configuration. This is done so that you won’t be distracted by those extra settings in the App.config. Also, I’ll be using the Enterprise Library 5 configuration GUI editor, which looks quite different from the old one.

So let’s start with a basic console program with a simple interface IGreeter, which has a single operation SayHello:

public interface IGreeter
{
    void SayHello(string to);
}

and a very basic implementation, which prints a hello message to whatever name that was passed in:

public class Greeter : IGreeter
{
    public void SayHello(string to)
    {
        Console.WriteLine("hello " + to);
    }
}

Next, the Main method simply creates a greeter instance and then says hello to “bob”.

class Program
{
    static void Main(string[] args)
    {
        var greeter = new Greeter();
        greeter.SayHello("bob");
    }
}

Finally, below is a simple console logger call handler. It sits in a separate DLL (CustomCallHandlers.dll) and will be applied to the SayHello method above using the policy injection block.

[ConfigurationElementType(typeof(CustomCallHandlerData))]
public class ConsoleCallLogger : ICallHandler
{
    public ConsoleCallLogger(NameValueCollection collection) 
    { 
    }

    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        Console.WriteLine("Before " + input.MethodBase.Name);
        var returnValue = getNext()(input, getNext);
        Console.WriteLine("After " + input.MethodBase.Name);
        return returnValue;
    }

    public int Order { get; set; }
}

The implementation of the Invoke method should be pretty self-explanatory, it basically prints a before and after message around the method invocation. Check this link out if you would like to know more about how to implement the ICallHandler interface.

A couple of things to note about the ConsoleCallLogger class above. Firstly It needs to implement the ICallHandler interface and have the ConfigurationElementType class attribute. Following assembly references needs to be added to the project in order for Visual Studio to resolve these classes:

  • Microsoft.Practices.EnterpriseLibrary.Common
  • Microsoft.Practices.Unity.Interception
  • System.configuration

Secondly, a custom call handler class is required to have a constructor that accepts a NameValueCollection argument. As you’ll see when we start configuring the App.config file, you can pass name/value pair values to the custom call logger from the configuration file.

Now, to apply the ConsoleCallLogger to the SayHello method, we need to configure the policy injection block with app.config. Before we start let’s have a look what the file will look like at the end (note: I have added space in class names so it will wrap properly):

<configuration>
  <configSections>
    <section name="policyInjection" type="Microsoft.Practices.EnterpriseLibrary.PolicyInjection.Configuration. PolicyInjectionSettings, Microsoft.Practices.EnterpriseLibrary.PolicyInjection, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
  </configSections>
  <policyInjection>
    <policies>
      <add name="My Custom Logging">
        <matchingRules>
          <add type="Microsoft.Practices.EnterpriseLibrary.PolicyInjection.MatchingRules. MemberNameMatchingRule, Microsoft.Practices.EnterpriseLibrary.PolicyInjection, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
              name="Member Name Matching Rule">
            <matches>
              <add match="SayHello" />
            </matches>
          </add>
        </matchingRules>
        <handlers>
          <add type="CustomCallHandlers.ConsoleCallLogger, CustomCallHandlers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="ConsoleCallLogger" />
        </handlers>
      </add>
    </policies>
  </policyInjection>
</configuration>

Other than the ugly long class names, it's not too bad is it? You can pretty much just code this up by hand. However, since the enterprise library comes with a configuration editor, let's make use of it.

First of all we need to add a policy injection settings block in our file:

EntlibConfig1

Next, right click on the policy injection block heading (“My Custom Logging” in my example) and add a member name matching rule block.

EntlibConfig2

Enter our our target method name SayHello into the member name match field.

EntlibConfig3

After telling the policy injection block how to find the target method (i.e. our SayHello method), we need to configure the call handler (i.e. the ConsoleCallLogger class). Again, right clean on our policy injection block header and choose “Add Custom Call Handler”.

EntlibConfig4

This will popup a dialog box and if you choose “Add From File”, you can then select the location of the DLL that contains the ConsoleCallLogger class. After selecting the DLL, the ConsoleCallLogger class should show up on the tree control as shown below:

EntlibConfig5

I did run into few problems when I was adding my DLL. Here are some tips if you run into problems too:

  1. Check your build settings, if you are using 64-bits EntLib configuration editor you should build 64-bits DLL too.
  2. Check your class, is it public? did you implement the ICallHandler interface? did you remember to add the ConfigurationElementType attribute? and did you pass typeof(CustomCallHandlerData) to it?
  3. Save and restart your configuration editor. I think there might be some bugs in the configuration editor because the first time I did this I just couldn’t get the ConsoleCallLogger class to show in the tree control no matter what I did. I end up saving and restarting a couple of times and eventually it came up.

After the call handler has been added, your setting should look something similar to this:

EntlibConfig6

Now, save the app.config to the console program and add it to the project. To enable policy injection block in the console program, we first need to add following assemblies to the project:

  • Microsoft.Practices.EnterpriseLibrary.PolicyInjection
  • Microsoft.Practices.Unity.Interception
  • CustomCallHandlers (the DLL that contains the ConsoleCallLogger class).

Next we need to replace the direct object instantiation:

var greeter = new Greeter();

with PolicyInjection.Create in the Main method:

var greeter = PolicyInjection.Create<Greeter, IGreeter>();

If everything’s been configured properly, when you run the console program it’ll show the following output:

Before SayHello
hello bob
After SayHello

As you can see, the ConsoleCallLogger class has been magically applied to the SayHello method.

AOP is a very useful technique if you have to extend some legacy code without actually having to touch those code. Enterprise library already shipped with a few useful call handlers for common cross cutting concerns such as the logging and security. If none of them fits your needs you can always follow what I did in this tutorial and write your own call handlers.

Finally, you can download codes in this tutorial with the SkyDrive link below:

Comments

  1. Very nice. Thanks Oscar.

    However, I would like to see how this could be done by using Unity interception only and no configuration file. ;)

    ReplyDelete
  2. Thanks for the example!

    You say 'AOP is a very useful technique if you have to extend some legacy code without actually having to touch those code', but detail a step where the original code is edited. Inserting 'var greeter = PolicyInjection.Create();'. Is there truly a way to apply Unity without modifying the code which you are advising (like with AspectJ or PostSharp)?

    ReplyDelete
  3. Hi Anonymous,

    Yes some coding was involved and I agree this is not possible in some cases (e.g. adding logging to a live system in production environment).

    I wrote this article when I was starting in a new project to wrap legacy COBOL code in .NET web services, so I do not have this restriction.

    I haven't tried PostSharp or AspectJ and don't think it is possible to do this without PolicyInjection.Create() in Unity.

    Cheers,
    Oscar

    ReplyDelete
  4. As far as I know, PostSharp and LinFu are the only solutions if you want to do AOP in .NET without changing your code (without using a proxy).

    ReplyDelete

Post a Comment

Popular posts from this blog

Load Testing ASP.NET Sites with JMeter

Following my previous post about using JMeter to test MOSS, I tried to figure out what are the bare minimum requirements of using JMeter against a plain ASP.NET website. I wrote a very simple ASP.NET web application with just a button, a text fields and a static label. This application displays the content of a text file in the static label when it loads and write content of the text field back to the file when the button is clicked.I found all I need to do in order to script this using JMeter is to extract __VIEWSTATE and __EVENTVALIDATION fields then send them back in the update request. My JMeter test plain looks like this:

Load Testing SharePoint (MOSS) Sites with JMeter

I have used JMeter for load testing few non-ASP.NET web sites before, however I could not get it to work with ASP.NET web sites. This is mainly due to ASP.NET ViewState and event validations, which stops a recorded JMeter script from being played back.Recently I worked on a MOSS project and we were looking for tools to perform load testing on the server. Many people said the load testing tool in Microsoft Team System for Testers works well with MOSS. However, it is quite expensive so I decided to give JMeter another go. After several hours of hacking, I actually got it to work and here’s how I did it.My test page is the pretty standard MOSS edit document property screen with few extra text fields added and the goal here is to use a JMeter script to change the document properties. Once I have a working script, I can configure JMeter to fire hundreds of instances of this script simultaneously to simulate the user workload.As shown in the screenshot below, the test plan contains two HTTP…

Getting HP Mini 1000 Wireless to Work Under Ubuntu 9.10 Karmic Koala‎ Netbook Remix

I installed Ubuntu 9.10 Netbook Remix in my (actually my wife's) HP mini 1000 this afternoon. To my surprise the wireless card did not work. Also, when I looked at System -> Administration -> Hardware Drivers, the list was blank.After few hours of googling and reading through several not too helpful forum posts, I learned that this was caused by Ubuntu 9.10 shipping "b43" driver out of box, which does not work for HP mini 1000. The proprietary driver "wl" should be used instead. However, no one said exactly what I needed to do to fix this problem.Eventually, I decided to just launch Synaptic and search for "broadcom". The first result in the filtered list was bcmwl-kernel-source, which looked promising so I just went ahead and installed it.I had a look at the /etc/modprobe.d folder after the installation finished, I noticed that the package actually created a blacklist file for "b43" related modules for me already. After reboot, my wir…