AOP with ASP.NET Web Api

This blog demonstrates the usage of filters and unity interface interception with an ASP.NET Web Api application.

AOP = Aspect oriented programming. This programming paradigm can be used to add functionality to existing methods. In the following example, we wrap the controller method with diagnosis using filters and then we wrap the business manager with diagnosis using unity interception (interface).

To add AOP to the controllers:

Create a EventSource class. These will be used to log the events.

using System.Diagnostics.Tracing;
 
namespace MvcApplication1.Logging
{
    [EventSource(Name = "RequestResponseEvents")]
    public class RequestResponseEvents : EventSource
    {
        public static readonly RequestResponseEvents Log = new RequestResponseEvents();
 
        private const int methodEnterEventId = 5;
        private const int methodLeaveEventId = 6;
        private const int logVerboseMessageEventId = 7;
        private const int logInfoMessageEventId = 8;
         
        [Event(methodEnterEventId, Message = "Request: {0}", Level = EventLevel.Verbose)]
        public void MethodEnter(string message)
        {
            if (IsEnabled()) WriteEvent(methodEnterEventId, message);
        }
 
        [Event(methodLeaveEventId, Message = "Response: {0}", Level = EventLevel.Verbose)]
        public void MethodLeave(string message)
        {
            if (IsEnabled()) WriteEvent(methodLeaveEventId, message);
        }
 
        [Event(logVerboseMessageEventId, Message = "{0}", Level = EventLevel.Verbose)]
        public void LogVerboseMessage(string message)
        {
            if (IsEnabled()) WriteEvent(logVerboseMessageEventId, message);
        }
 
        [Event(logInfoMessageEventId, Message = "{0}", Level = EventLevel.Informational)]
        public void LogInfoMessage(string message)
        {
            if (IsEnabled()) WriteEvent(logInfoMessageEventId, message);
        }
    }
}

Add a filter class

using System;
using System.Net.Http;
using System.Web.Http.Filters;
using System.Web.Http.Controllers;

namespace MvcApplication1
{

    public class LogActionWebApiFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            // pre-processing
            Logging.RequestResponseEvents.Log.LogInfoMessage("OnActionExecuted Request " + actionContext.Request.RequestUri.ToString());
        }

        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            var objectContent = actionExecutedContext.Response.Content as ObjectContent;
            if (objectContent != null)
            {
                var type = objectContent.ObjectType; //type of the returned object
                var value = objectContent.Value; //holding the returned value
            }

            Logging.RequestResponseEvents.Log.LogInfoMessage("OnActionExecuted Response " +  actionExecutedContext.Response.StatusCode.ToString());
        }
    }
}

And use the filter class with a controller:

   [LogActionWebApiFilter]
    public class ValuesController : ApiController

NOTE: The ActionFilterAttribute is not the same for MVC and Web Api! If used for all controllers, it must be added in the filter list. The class has also different methods.
( System.Web.Http.Filters.ActionFilterAttribute , System.Web.Http.Mvc.ActionFilterAttribute )

Here’s a List of possible filters for Web Api: (System.Web.Http.Filters)

  1. ActionFilterAttribute
  2. AuthorizationFilterAttribute
  3. ExceptionFilterAttribute

Here’s a List of possible filters for MVC: (System.Web.Http.Mvc.Filters)

  1. System.Web.Mvc.ActionFilterAttribute
  2. System.Web.Mvc.AuthorizeAttribute
  3. System.Web.Mvc.ChildActionOnlyAttribute
  4. System.Web.Mvc.HandleErrorAttribute
  5. System.Web.Mvc.RequireHttpsAttribute
  6. System.Web.Mvc.ValidateAntiForgeryTokenAttribute
  7. System.Web.Mvc.ValidateInputAttribute

You can see that the 2 namespaces are different. When using Web Api, care must be taken to use the right one. You don’t have to use these, but then you must implement all the filter logic yourself.

If it is required to add this filter to all controllers, add it in the global.asax file:

 FilterConfig.RegisterHttpFilters(GlobalConfiguration.Configuration.Filters);

And the method:

public static void RegisterHttpFilters(HttpFilterCollection filters)
{
    filters.Add(new LogActionWebApiFilter());
}

Now lets add AOP for the backend. We use unity in this example.

Using nuget, add the following packages:
nugetUnityWebApi

Then create a EventSource class

using System.Diagnostics.Tracing;
 
namespace MvcApplication1.Logging
{
    [EventSource(Name = "DiagnosisEvents")]
    public class DiagnosisEvents : EventSource
    {
        public static readonly DiagnosisEvents Log = new DiagnosisEvents();
 
        private const int methodEnterEventId = 1;
        private const int methodLeaveEventId = 2;
        private const int logVerboseMessageEventId = 3;
        private const int logInfoMessageEventId = 4;
         
        [Event(methodEnterEventId, Message = "Method Enter: {0}", Level = EventLevel.Verbose)]
        public void MethodEnter(string message)
        {
            if (IsEnabled()) WriteEvent(methodEnterEventId, message);
        }
 
        [Event(methodLeaveEventId, Message = "Method Leave: {0}", Level = EventLevel.Verbose)]
        public void MethodLeave(string message)
        {
            if (IsEnabled()) WriteEvent(methodLeaveEventId, message);
        }
 
        [Event(logVerboseMessageEventId, Message = "{0}", Level = EventLevel.Verbose)]
        public void LogVerboseMessage(string message)
        {
            if (IsEnabled()) WriteEvent(logVerboseMessageEventId, message);
        }
 
        [Event(logInfoMessageEventId, Message = "{0}", Level = EventLevel.Informational)]
        public void LogInfoMessage(string message)
        {
            if (IsEnabled()) WriteEvent(logInfoMessageEventId, message);
        }
    }
}

Now a Behaviour is required


using System;
using System.Collections.Generic;
using Microsoft.Practices.Unity.InterceptionExtension;
 
namespace MvcApplication1.Logging
{
    public class RequestResponseBehaviour : IInterceptionBehavior
    {
        public IEnumerable<Type> GetRequiredInterfaces()
        {
            return Type.EmptyTypes;
        }
 
        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            DiagnosisEvents.Log.MethodEnter(String.Format("[{0}:{1}]", this.GetType().Name, "Invoke"));
            DiagnosisEvents.Log.LogVerboseMessage(String.Format("{0} {1}", input.MethodBase.ToString(), input.Target.ToString()));
 
            var methodReturn = getNext().Invoke(input, getNext);
 
            if (methodReturn.Exception == null)
            {
                 DiagnosisEvents.Log.MethodLeave(String.Format("Successfully finished {0} {1}", input.MethodBase.ToString(), input.Target.ToString()));
            }
            else
            {
                DiagnosisEvents.Log.MethodLeave(String.Format("Finished {0} with exception {1}: {2}", input.MethodBase.ToString(), methodReturn.Exception.GetType().Name, methodReturn.Exception.Message));
            }
 
            return methodReturn;
        }
 
        public bool WillExecute
        {
            get { return true; }
        }
    }
}

Now we can add a scan assembly reflection method:

using System;
using System.Collections.Generic;
using System.Reflection;

namespace MvcApplication1.Logging
{
    public static class RegisterTypesScan
    {
        public static IEnumerable<Type> GetTypesWithCustomAttribute<T>(Assembly[] assemblies)
        {
            foreach (var assembly in assemblies)
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.GetCustomAttributes(typeof(T), true).Length > 0)
                    {
                        yield return type;
                    }
                }
            }
        }
    }
}

And this can be used in the unity configuration method

using System;
using System.Linq;

using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using MvcApplication1.Logging;
using Microsoft.Practices.Unity.InterceptionExtension;

using ServiceLayer;

namespace MvcApplication1.App_Start
{
    /// <summary>
    /// Specifies the Unity configuration for the main container.
    /// </summary>
    public class UnityConfig
    {
        	

        #region Unity Container
        private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
        {
            var container = new UnityContainer();
            
            RegisterTypes(container);
            return container;
        });

        /// <summary>
        /// Gets the configured Unity container.
        /// </summary>
        public static IUnityContainer GetConfiguredContainer()
        {
            return container.Value;
        }
        #endregion

        /// <summary>Registers the type mappings with the Unity container.</summary>
        /// <param name="container">The unity container to configure.</param>
        /// <remarks>There is no need to register concrete types such as controllers or API controllers (unless you want to 
        /// change the defaults), as Unity allows resolving a concrete type even if it was not previously registered.</remarks>
        public static void RegisterTypes(IUnityContainer container)
        {
            var myAssemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName.StartsWith("ServiceLayer")).ToArray();

            container.AddNewExtension<Interception>();
            container.RegisterTypes(RegisterTypesScan.GetTypesWithCustomAttribute<RequestReponseAttribute>(myAssemblies),
                                    WithMappings.FromMatchingInterface,
                                    WithName.Default,
                                    WithLifetime.ContainerControlled,
                                    getInjectionMembers: t => new InjectionMember[]
                                    {
                                        new Interceptor<InterfaceInterceptor>(),
                                        new InterceptionBehavior<MvcApplication1.Logging.RequestResponseBehaviour>()
                                    })
                     .RegisterTypes(RegisterTypesScan.GetTypesWithCustomAttribute<DataAccessAttribute>(myAssemblies),
                                    WithMappings.FromMatchingInterface,
                                    WithName.Default,
                                    WithLifetime.Transient
                                );
        }

    }
}

Now any class marked with the RequestReponseAttribute will be logged before and after.

getInjectionMembers: t => new InjectionMember[]
                                    {
                                        new Interceptor<InterfaceInterceptor>(),
                                        new InterceptionBehavior<MvcApplication1.Logging.RequestResponseBehaviour>()
                                    })

A request produces the following logs:
AOPLogs

Code: https://github.com/damienbod/AOPwithWebApi

Links:

http://www.asp.net/web-api

http://msdn.microsoft.com/en-us/library/dn178467%28v=pandp.30%29.aspx

http://blogs.msdn.com/b/abhinaba/archive/2006/01/23/516163.aspx

Click to access aop-ecoop97.pdf

Click to access ui_08AspectOP.pdf

http://en.wikipedia.org/wiki/Aspect-oriented_software_development

http://en.wikipedia.org/wiki/Attribute-oriented_programming

http://www.codeproject.com/Articles/11385/Aspect-Oriented-Programming-in-NET

http://stackoverflow.com/questions/633710/what-is-the-best-implementation-for-aop-in-net

http://msdn.microsoft.com/en-us/library/system.web.http.filters%28v=vs.108%29.aspx

http://msdn.microsoft.com/en-us/library/system.web.mvc.filterattribute%28v=vs.108%29.aspx

http://www.asp.net/web-api/overview/hosting-aspnet-web-api/self-host-a-web-api

2 comments

  1. […] AOP with ASP.NET Web Api […]

  2. Jefferson Bien-Aime · · Reply

    Thank you for this tutorial. Can I also use autofac and log4net to do it?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: