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)
- ActionFilterAttribute
- AuthorizationFilterAttribute
- ExceptionFilterAttribute
Here’s a List of possible filters for MVC: (System.Web.Http.Mvc.Filters)
- System.Web.Mvc.ActionFilterAttribute
- System.Web.Mvc.AuthorizeAttribute
- System.Web.Mvc.ChildActionOnlyAttribute
- System.Web.Mvc.HandleErrorAttribute
- System.Web.Mvc.RequireHttpsAttribute
- System.Web.Mvc.ValidateAntiForgeryTokenAttribute
- 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:
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:
Code: https://github.com/damienbod/AOPwithWebApi
Links:
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
[…] AOP with ASP.NET Web Api […]
Thank you for this tutorial. Can I also use autofac and log4net to do it?