Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / architecture

Cross Cutting Concerns in .NET Applications

5.00/5 (16 votes)
27 Nov 2015CPOL10 min read 50.7K   498  
How to use Microsoft Unity Interception as a solution for cross cutting concerns in a .NET application

Cross

Introduction

Cross cutting concerns are common methods provided by a specific service used by all layers in an application; “cutting through the whole application”.
Examples are:

  • Logging
  • Caching
  • Authorisation
  • Error handling
  • Auditing

This article describes different examples how to implement cross cutting concerns in a .NET application.
With every example, the pros and cons are listed.
The conclusion is made that using Microsoft Unity for implementing cross cutting concerns is probably the most suitable manner regarding flexibility, maintenance, amount of work and applying to SOLID software design principles.

Examples

Adding Code to Consuming Classes

When cross cutting concerns are implemented by simply adding code in consuming classes, the code will be polluted with dependencies that will be a burden when the code is under test or parallel development is done by the development team.

An example is:

C#
public class ProductRepository    
{
 public void Update(string productName)
 {
  Update(productName);
  new EventLog("MyLog").WriteEntry(string.Format("{0} is updated in the database"));
 }
}

The above code works fine, but there are some disadvantages.
It violates the single responsibility principle with both an update and a logging functionality.
The update method can’t be tested in an isolated manner because the update method can’t be called without triggering the logging service.
The logger (EventLog class) can’t be substituted with a stub or fake logger in an easy way.
The ProductRepository is dragging along a dependency of the logger DLL which means that whenever there is something wrong with the logger codebase, there is something wrong with the ProductRepository.

Using Dependency Injection

Using dependency injection is a better solution but still it needs to be injected in every class that wants to consume the concern and can therefore be quite repetitive.

Also, the interface presents now a nonspecific behaviour of the class, e.g., the logging is a non-intrinsic behaviour of the ProductRepository but it is (too) obtrusive present in the interface.

C++
public class ProductRepository
{
        ILogger _logger;
 
        public ProductRepository(ILogger logger)
        {
            _logger = logger;
        }
 
        public void Update(string productName)
        {
            Update(productName);
            logger.WriteEntry(string.Format("{0} is updated in the database"));
        }
}

Using Ambient Context

Ambient Context is represented by a defined set of properties or methods that can be consumed as if they are application variables (or global variables).

One way of realizing this is to make use of static properties or static methods with a writable singleton class.
The ambient context is best used when the public methods in the context return a value that is used by the consumer.
When the public methods return void, e.g., the methods are writing to a log, cache, etc., has no use in “polluting” the original code when no return value is used by the original class.
Using interception is a better solution in that case because no return value has to be passed to the consumer.
The next example shows the abstract class TimeProvider.

C#
public abstract class TimeProvider
    {
        private static TimeProvider current;
 
        static TimeProvider()
        {
            TimeProvider.current = new DefaultTimeProvider();
        }
 
        public static TimeProvider Current
        {
            get { return TimeProvider.current; }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                TimeProvider.current = value;
            }
        }
 
        public abstract DateTime UtcNow { get; }
 
        public static void Reset()
        {
            TimeProvider.current = new DefaultTimeProvider();
        }
    } 
 
    public class DefaultTimeProvider : TimeProvider
    {
        public override DateTime UtcNow
        {
            get { return DateTime.UtcNow; }
        }
    }

Good Usage of the Ambient Context

  • Make sure it has an intrinsic default, it should always work without assigning a custom context.
  • The context can’t be nullable, a consumer should be able to always query the ambient context.
  • This can be arranged with a guard clause that checks if the setter value is not null.

Advantages of AmbIent Context

  • Always available
  • The timeprovider class can be used everywhere in the application, it is always available without expanding the interfaces/APIs (obeying the interface segregation principle)

Mockable

The timeprovider class is mockable with the use of a mocking framework (NSubstitute in the following example):

C#
var timeProviderStub = Substitute.For<TimeProvider>();
timeProviderStub.UtcNow.Returns(DateTime.UtcNow.AddYears(-1));
Assert.AreEqual(DateTime.UtcNow.AddYears(-1).Year, timeProviderStub.UtcNow.Year);

Disadvantages of Ambient Context

Black Boxing
  • The consumer of a TimeProvider is (more) Black boxed.
  • It is not clear at first sight (via its interface) that a consumer is making use of the TimeProvider to get the time.
  • If the TimeProvider is changed in behaviour, all consumers will change with that and the direct source of change can be hard to find.
Tricky
  • As with every application wide scope instance/variable, it is possible when not implemented correctly that the value or reference can be changed due for example to different parallel threat complications.

Decorator

The decorator pattern is one of the most obvious ways of complying with SOLID programming principles. A good implementation will demonstrate all principles.

  • A decorator acts as a wrapper around a class by means of consuming that class and simultaneously implementing the same interface of that class.
  • This makes it possible to substitute the original class and to override the methods in that class.
  • The overrides can delegate the call to the injected class plus add extra functionality of its own like for example, logging or caching.

Code Example

C#
public interface IProductRepository
{
   void Update(string productname);
}

public class ProductRepository : IProductRepository
{
  public void Update(string productname)
  {
    //write to database
  }
}

public class ProductRepositoryWithLog : IProductRepository
    {
        private IProductRepository _productRepository;
 
        public ProductRepositoryWithLog(IProductRepository productRepository)
        {
            _productRepository = productRepository;
        }
 
        public void Update(string productName)
        {
            //Delegate to original update method
            _productRepository.Update(productName);
            //Decoration with logging feature
            new EventLog("MyLog").WriteEntry(string.Format("{0} is updated in the database"));
        }
    }

Advantage Of Using a Decorator

  • Applying to SOLID principles with all the conveyed advantages: more readable, testable, extensible, maintainable and loosely coupled code.

Disadvantage

  • For larger projects, it can be verbose and repetitive which violates the DRY principle.

Aspect Oriented Programming

AOP is a mechanism to automatically wire up decorators to the original classes without the need for writing the actual decorators. The cross cutting concerns functionality presented as aspects are attached to the original class with the means of custom attributes.

PostSharp is such an AOP framework that uses code interweaving at compile time to intercept and decorates the original class with the cross cutting concerns aspects during the compile process.
There are pros and cons to use an AOP framework like PostSharp and it all depends on the project and situation.

One of the disadvantages is that most AOP mechanisms use attributes to declare aspects.
Aspects are the interleaved pieces of code that an AOP framework adds in the post compilation steps. This can lead to debugging problems and the so-called Vendor Lock-In anti-pattern. Also, using attributes will make your code less loosely coupled by creating a hard dependency between your code and the AOP framework in use.

Interception with Unity

  • Interception is a design pattern used to alter or amend code in a transparent, non-obtrusive way.
  • With (Microsoft) Unity, it is possible to use (dynamic) interception during run-time without altering the compiled code.
  • Unity provides a way to define a decorator that can be used by an arbitrary class without the requirement to implement this decorator as an interface by the class.
  • There are in short two types of interceptions possible with Unity: Instance interception and Type interception.

Interception Type: Instance

  • Unity creates a proxy object based on the interface the target object implements.
  • To implement instance interception with Unity, two interceptor types are provided by Microsoft:
    • InterfaceInterceptor: An instance interceptor that works by generating a proxy class on the fly for a single interface
    • TransparentProxyInterceptor: An instance interceptor that uses remoting proxies to do the interception.
      The advantage of this interceptor is that it can intercept multiple interfaces but the slower speed of this interceptor is the disadvantage.

Implementation

  • Create a class that implements: IinterceptionBehavior. This class will act as the decorator.
  • The Invoke method is called when a member is intercepted. In the Invoke method, define what cross cutting concern you need, e.g., logging:
C#
public class LoggingInterceptionBehavior : IInterceptionBehavior
    {
        public IMethodReturn Invoke(IMethodInvocation input,
        GetNextInterceptionBehaviorDelegate getNext)
        {
            // Before invoking the method on the original target.
            WriteToLog(String.Format(
            "Invoking method {0} at {1}",
            input.MethodBase, DateTime.Now.ToLongTimeString()));
            // Invoke the next behavior in the chain.
            var result = getNext()(input, getNext);
            // After invoking the method on the original target.
 
            if (result.Exception != null)
            {
                WriteToLog(String.Format(
                "Method {0} threw exception {1} at {2}",
                input.MethodBase, result.Exception.Message,
                DateTime.Now.ToLongTimeString()));
            }
 
            else
            {
                WriteToLog(String.Format(
                "Method {0} returned {1} at {2}",
                input.MethodBase, result.ReturnValue,
                DateTime.Now.ToLongTimeString()));
            }
            return result;
        }
 
        public IEnumerable<Type> GetRequiredInterfaces()
        {
            return Type.EmptyTypes;
        }
 
        public bool WillExecute
        {
            get { return true; }
        }
 
        private void WriteToLog(string message)
        {
            EventLog eventLog = new EventLog("");
            eventLog.Source = "Interception";
            eventLog.WriteEntry(string.Format("{0}", message, EventLogEntryType.Information));
        }
    }

Next, define with the container the class and interface that you want for interception (the class you normally would write a decorator for) and with that statement, you define the type of interception and which decorator (e.g., LoggingInterceptionBehavior):

C#
container.AddNewExtension<Interception>();
//class to decorate
container.RegisterType<IProductRepository, ProductRepository>(
//interceptor type
new Interceptor<InterfaceInterceptor>(),
//decorator
new InterceptionBehavior<LoggingInterceptionBehavior>());

Interception Type: Type

Unity creates an object based on the type of the target object, it is actually a subtype of the target object. The interceptor used is called a VirtualMethodInterceptor.

VirtualMethodInterceptor

  • Intercept all methods including internals
  • Can only use when creating an object, not possible on existing instances
  • For example, you cannot use type interception on a WCF service proxy object that is created for you by a channel factory
  • Disadvantage of these interception types is there is no granularity in what you intercept of the class.

Implementation

Create a class that implements: IinterceptionBehavior

Next, register the container the same as with the interface interception with the difference that you use the VirtualMethodInterceptor:

C#
container.AddNewExtension<Interception>();
container.RegisterType<ProductRepository>(
new Interceptor<VirtualMethodInterceptor>(),
new InterceptionBehavior<LoggingInterceptionBehavior>());

Overview

The following figure shows the advantages and disadvantages of the two types of interception:

Interception Type

Advantages

Disadvantages

(instance)

 

Transparent Proxy Interceptor

 

Can intercept all methods of the target object (virtual, non-virtual, or interface).

 

The object must either implement an interface or inherit from System.MarshalByRefObject. If the marshal by reference object is not a base class, you can only proxy interface methods. The Transparent Proxy process is much slower than a regular method call.

 

(instance)

 

Interface Interceptor

 

Allows interception on any object that implements the target interface. It is much faster than the TransparentProxyInterceptor.

 

It only intercepts methods on a single interface. It cannot cast a proxy back to the target object's class or to other interfaces on the target object.

 

(type)

 

Virtual Method Interceptor

 

Calls are much faster than the Transparent Proxy Interceptor.

 

Interception only happens on virtual methods. You must set up interception at object creation time and cannot intercept an existing object.

 

Unity Configuration

There are different ways to configure Unity. You can use policy and attributes.

Policy Injection

Policy injection is an interceptor that captures calls to objects you resolve through the (unity) container, and applies a policy that uses call handlers and matching rules inherited from Unity to define its policy injection behaviour on a per-method basis.

The call handler is the actual decorator and behaviour rules defined in the application configuration file describe how and when the decorator is used.
Policy injection gives you more control over what types of classes or methods (members) are interception by means of behaviour rules. Policy injection is not bounded to a specific class or interface, the behaviour rules describe in a declarative way what members are intercepted.

Matching examples:

Matching rule Description
Assembly Matching Rule

Selects classes in a specified assembly

Custom Attribute Matching Rule

Selects classes or members that have an arbitrary attribute applied

Member Name Matching Rule

Selects class members based on the member name

Method Signature Matching Rule

Selects class methods that have a specific signature

Namespace Matching Rule

Selects classes based on the namespace name

Parameter Type Matching Rule

Selects class members based on the type name of a parameter for a member of the target object

Property Matching Rule

Selects class properties by name and accessor type

Return Type Matching Rule

Selects class members that return an object of the specified type

Tag Attribute Matching Rule

Selects class members that carry a Tag attribute with the specified name

Type Matching Rule

Selects classes that are of a specified type

Implementation

Create a class that implements IcallHandler

  • This class will act as the decorator.
  • A call handler can either be written very specifically for a particular method, or very generally, to handle any member.
  • The Invoke method is called when a member is intercepted.
  • In the Invoke method, define what cross cutting concern you need, e.g., logging:
C#
public class LoggingCallHandler : ICallHandler
    {
        public IMethodReturn Invoke(IMethodInvocation input, 
    GetNextHandlerDelegate getNext)
        {
            System.Diagnostics.Debug.WriteLine("Begin");
 
            string message =
                string.Format(
               "Assembly : {0}.{1}Location : {2}.{3}Calling Class : {4}",
                input.MethodBase.DeclaringType.Assembly.FullName,
                Environment.NewLine,
                input.MethodBase.DeclaringType.Assembly.Location,
                Environment.NewLine,
                input.MethodBase.DeclaringType.FullName
                );
 
            WriteToLog(string.Format("{0} is called with parameters '{1}'", 
                input.MethodBase.Name,
                input.Inputs[0]));
 
            WriteToLog(message);
            
            IMethodReturn result = getNext().Invoke(input, getNext);
            WriteToLog(string.Format("{0} is ended", input.MethodBase.Name));
            System.Diagnostics.Debug.WriteLine("End");
            return result;
        }
 
        // order in which handler will be invoked
        public int Order { get; set; }
 
        private void WriteToLog(string message)
        {
            EventLog eventLog = new EventLog("");
            eventLog.Source = "Interception";
            eventLog.WriteEntry(string.Format("{0}", 
        message,EventLogEntryType.Information));
        }
    }

Next, you only need to describe the policy in the application configuration file and load this behaviour in the container.

Example:

XML
<container name="Demo">
      <extension type="Interception"/>
      <register type="Logger.CallHandler.LoggingCallHandler, Logger"/>
      <register type="DynamicInterception.IProductRepository, DynamicInterception" 
       mapTo="DynamicInterception.ProductRepository, DynamicInterception"/>
      <register type="DynamicInterception.ProductRepository, DynamicInterception">
        <interceptor type="VirtualMethodInterceptor"/>
        <interceptionBehavior type="PolicyInjectionBehavior"/>
      </register>
      <interception>
        <policy name="LogPolicy">
 
       <matchingRule type="NamespaceMatchingRule" name=" NameSpaceRule">
            <constructor>
              <param name="namespaceName" value="DynamicInterception"/>
            </constructor>
          </matchingRule>
         
          
       <matchingRule type="MemberNameMatchingRule" name="MemberMatch">
            <constructor>
              <param name="nameToMatch" value="Update" />
            </constructor>
          </matchingRule>
          
          <callHandler type="Logger.CallHandler.LoggingCallHandler, 
          Logger" name="logging handler"/>
        </policy>
      </interception>
   </container>

Load these rules in the container:

C#
container.LoadConfiguration("Demo");

The result will be that every method with the name “update” in the namespace DynamicInterception will be intercepted and thus have a logging feature.

Policy Injection and Attributes

With using attributes, there is no need to configure any policies, but you do need to define your attributes. The attributes declare if a member must be intercepted.
It is important that when using attributes, SOLID rules are disobeyed, attributes force you to make a reference to the DLL that services the cross cutting concern.

If you apply attributes to methods you wish to intercept, you’re effectively creating a hard dependency on the aspect you’ve applied. Let’s say that you want to handle security on a certain set of methods.
The moment you declare a security-attribute on your methods, you’re creating a hard dependency on that security implementation.

Implementation

First, register the policy injection with attributes in the container:

C#
container.AddNewExtension<Interception>();
container.RegisterType<IProductRepository, ProductRepository>(
new Interceptor<InterfaceInterceptor>(),
new InterceptionBehavior<PolicyInjectionBehavior>());

Next, create an attribute that implements the HandlerAttribute.
The attribute AttributeUsage specifies the language elements to which the attribute can be applied.

Example:

C#
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method)]
    public class LogCallBeginEndAttribute : HandlerAttribute
    {
        public override ICallHandler CreateHandler(IUnityContainer container)
        {
            return container.Resolve<LoggingCallHandler>();
        }
    }

Use the attribute on members you want to intercept :
[LogCallBeginEndAttribute()]
public virtual void Delete(string productname)
{
   //delete in database
}

Points of Interest

Required Tooling

In order to use Interception with Unity, the following requirements must be met:

  • Operating system: Microsoft Windows® 7 Professional, Enterprise or Ultimate; Windows Server 2003 R2; Windows Server 2008 with Service Pack 2; Windows Server 2008 R2; Windows Vista with Service Pack 2; or Windows XP with Service Pack 3.
  • Microsoft .NET Framework 3.5 with Service Pack 1 or Microsoft .NET Framework 4.0. or higher
  • Unity Application Block for .NET (available at nuget.org)
  • Unity Interception Extension for .NET (available at nuget.org)

Dependencies

  • There are no dependencies except the use of the required tooling.

Guidelines

  • Best practice is to use Policy Injection to avoid the use of filtering in classes that implement the IinterceptionBehavior interface.
  • When using Policy Injection, start with narrow rules to avoid that all members in the project are intercepted.

History

  • First release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)