3

I'm trying to add logging to methods decorated with an attribute using Spring.Net for AOP.

Step 1: Reference 'Spring.Core', 'Spring.Aop', 'Common.Logging'

Step 2: Create an advice:

using AopAlliance.Intercept;

namespace MyApp.Aspects
{
    public class LoggingAdvice : IMethodInterceptor
    {
      public object Invoke(IMethodInvocation invocation)
      {
        //todo: log started
        object rval = invocation.Proceed();
        return rval;
        //todo: log finished
      }
    }
}

Step 3: Create an attribute:

using System;

namespace MyApp.Aspects
{
  public class LoggingAttribute : Attribute
  {
  }
}

Step 4: Edit web.config

<configuration>
  <configSections>
    <sectionGroup name="spring">
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
    </sectionGroup>
  </configSections>

  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>

    <objects xmlns="http://www.springfrmework.net">

      <object id="loggingAdvice" type="MyApp.Aspects.LoggingAdvice, MyApp"></object>
      <object id="loggingAdvisor" type="Spring.Aop.Support.DefaultPointcutAdvisor, Spring.Aop">
        <property name="Advice" ref="loggingAdvice" />
      </object>

      <object type="Spring.Aop.Framework.AutoProxy.AttributeAutoProxyCreator, Spring.Aop">
        <property name="AttributeTypes" value="MyApp.Aspects.LoggingAttribute"/>
        <property name="InterceptorNames" value="loggingAdvisor"/>
      </object>

    </objects>
  </spring>
</configuration>

Step 5: Decorate a method with the attribute:

using System.Web.Mvc;

namespace MyApp.Controllers
{
  public class MyController : Controller
  {
    [Logging]
    public ActionResult DoStuff()
    {
      //todo: implement
    }
  }
}

The advice is never triggered. What am I missing?

tobsen
  • 5,328
  • 3
  • 34
  • 51
David
  • 3,736
  • 8
  • 33
  • 52
  • How do you instantiate the object? – Ondrej Tucny Feb 02 '12 at 15:21
  • The object is instantiated normally, so **not** using ContextRegistry.GetContext(); That sould be possible according to this post: http://stackoverflow.com/questions/2480927/can-spring-net-function-as-postsharp – David Feb 02 '12 at 15:30
  • 1
    If it isn't created by spring, spring can't create a dynamic proxy of your object instance. Spring.Net does runtime-weaving. It seems that the original author of the article you linked didn't know the difference between runtime weaving and compiletime weaving / IL Merging of AOP Aspects. – tobsen Feb 02 '12 at 15:59

2 Answers2

7

This has to do with the fact that the controller is created and then actually calls DoStuff() on it self. The controller obviously does not hold a proxy to itself and therefore the call to DoStuff() does not get intercepted by Spring.Net AOP.

As tobsen mentions in his answer, you will have to get the controller from spring, otherwise interception will not take place. I assume you're using spring mvc support here to create controllers, but this does not clearly show from your question and you might have left it out.

How to intercept action methods on MVC 3 controllers

Summary

See below for details and an example.

  1. Use an InheritanceBasedAopConfigurer
  2. Declare methods you want to intercept as virtual
  3. Configure your interceptors

Spring's default interception mechanism does not work ...

When a request is made to an MVC app, then from the request url a controller is chosen by the MVC framework. On this controller, the Execute() method is called, which in turn is responsible for invoking the action methods. It is important to realize that action methods are always called from within the controller.

Spring.NET aop uses dynamic weaving. By default, at runtime a proxy is created for objects for which aop advisors are declared in the configuration. This proxy intercepts calls and forwards calls to the target instance. This is done when proxying interfaces and classes (using proxy-target-type="true"). When the target object invokes a method on it self, it will not do this through the spring proxy and the method does not get intercepted. This why the default aop mechanism doesn't work for mvc controllers.

... but using an InheritanceBasedAopConfigurer does the trick

To intercept calls on action methods, you should use an InheritanceBasedAopConfigurer. This will create an inheritance based proxy that does not delegate to a target object, instead interception advice is added directly in the method body before invoking the base class method.

Note that for this interception method to work, the methods have to be virtual.

The following xml config works:

<!-- 
When not specifying an object id or name, 
spring will assign a name to it like [typename]#[0,1,2,..]  
-->  
<object type="MyApp.Controllers.HomeController, MyApp" 
        singleton="false" />

<object id="myInterceptor" type="Spring.Aop.Support.AttributeMatchMethodPointcutAdvisor, Spring.Aop">
  <property name="Attribute" value="MyApp.MyAttribute, MyApp" />
  <property name="Advice">
    <object type="MyApp.MyAdvice, MyApp" />
  </property>
</object>

<object type="Spring.Aop.Framework.AutoProxy.InheritanceBasedAopConfigurer, Spring.Aop">
  <property name="ObjectNames">
    <list>
      <value>*Controller#*</value>
    </list>
  </property>
  <property name="InterceptorNames">
    <list>
      <value>myInterceptor</value>
    </list>
  </property>
</object>

A working example is available on github. It is based on a standard mvc 3 application with Spring.Net Mvc3 support. Relevant files are:

References

Community
  • 1
  • 1
Marijn
  • 10,367
  • 5
  • 59
  • 80
  • That's right. Look at the spring.net documentation and it will show you what you need to do to have your Controller instances come from the spring container. When the MVC framework requests them from the spring container instead of creating them itself, your advice will be triggered. – John Ruiz Feb 02 '12 at 16:07
  • The above example xml (I think) adds point cuts to HomeController. If I have many controllers, how is best to add point cuts to them all? I see wildcard matching by object name, but not by type. What I'd like to do is something like: – sming May 27 '14 at 13:26
  • @sming no, spring.net will add pointcuts to all controllers defined with an id matching pattern `*controller#*`. If you don't specify an object id, spring.net will do it for you with the convention `[typename]#[0,1,2,..]`. – Marijn May 27 '14 at 14:24
  • So you will not have to do any extra work in addition to specifying the controller in the configuration file (which I think you're require to do). I'm not sure how you could implement such a type filter. – Marijn May 27 '14 at 14:28
  • I'm successfully using this technique but for this method: [SetMethodInfoAsMessage] virtual public ActionResult GetAccount(int Id, int subAccountId = 0) { ... I get: "The parameters dictionary contains an invalid entry for parameter 'subAccountId' for method 'System.Web.Mvc.ActionResult GetAccount(Int32, Int32)' in 'InheritanceAopProxy_02f388f0cb6f4ca3b18bc700bc4785d3'. The dictionary contains a value of type 'System.Reflection.Missing', but the parameter requires a value of type 'System.Int32'." is this a known issue? Many thanks! BTW other methods with default parameters work just fine. – sming May 27 '14 at 14:55
  • This is rather specific and might be better suited on another thread. I had something similar once when an old version of a dependent assembly was deployed to the bin of the mvc app. Or could it have something to do with de default value for the parameter? But these are just wild guesses. Good to hear you got it to working. – Marijn May 27 '14 at 19:10
3

The way you are approaching may be correct, however, I can't see how you are retrieving instances of your proxied MyController class. As you indicated in the comment, you are not using spring to retrieve instances of MyController. Basically spring hasn't a chance to intercept that call and therfore cannot create a dynamic proxy (e.g. a adviced-wrapped instance around the original instance).

I strongly suggest you have a look at the examples which are doing same thing you are trying to accomplish but are using a already built-in logging advice.

Also be sure to enable debug-logging in spring and read the log carefully. Spring writes in the log which types are proxied and if advices are wrapped around.

As for your problem, here is a wild guess: I am currently not sure but you might be missing the assembly of the Attribute in your declaration:<property name="AttributeTypes" value="MyApp.Aspects.LoggingAttribute"/> perhaps rewriting it to: <property name="AttributeTypes" value="MyApp.Aspects.LoggingAttribute, MyApp"/> helps spring to find the attribute.

tobsen
  • 5,328
  • 3
  • 34
  • 51
  • This is correct, but op also has to take into account that a controller calls methods on it self, so the standard aop proxies can't be used, tried to clarify in my answer. – Marijn Feb 02 '12 at 16:06
  • @Marijn your are also correct. I added the tag asp.net-mvc so this indicates the environment op works in more clearly. Your answer seems to be really helpful for that specific environment. Good job ;) – tobsen Feb 03 '12 at 15:43