ASP.NET MVC3 AOP proxy controller does not intercept all methods, only IController.Execute

I have a project with several layers - among them the web interface (ASP.NET MVC3) and the end of the service (mostly business logic). The project takes several months, so everything works as expected. Now I'm trying to add a logging aspect to some of the MVC3 controller methods using custom [Log] attributes.

I use Castle Windsor to inject dependencies. To get the logical aspect, I use Castle DynamicProxy via SNAP . Controllers are solved using the WindsorControllerFactory from the useful Krzysztof Koźmic tutorial, but I changed it to see the default interface for the controller (see below).

In my service layer:

 [Log(LoggingLevel.Info)] public void Save(MyBusinessDto dto) { // business logic and other checks this.repository.Save(mbo); } 

In my IWindsorInstaller web interface for controllers:

 private static BasedOnDescriptor FindControllers() { return AllTypes .FromThisAssembly() .BasedOn<IController>() .WithService.DefaultInterface(); } 

In my (slightly tuned) WindsorControllerFactory , which is looking for the default controller interface:

 protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == null) { throw new HttpException(404, string.Format(Error404, requestContext.HttpContext.Request.Path)); } string controllerName = controllerType.Name; string defaultInterfaceName = 'I' + controllerName; Type defaultInterface = controllerType.GetInterface(defaultInterfaceName); object controller = this.kernel.Resolve(defaultInterface); return (IController)controller; } 

In my controllers:

 public class MyBusinessController : MyBusinessControllerBase, IMyBusinessController { [Log(LoggingLevel.Debug)] public ActionResult CreateOrUpdate(MyBusinessFormModel fm) { // Convert form model to data transfer object, // perform validation and other checks this.service.Save(dto); return View(fm); } } 

All this works fine in a service project, but methods are not intercepted in controllers.

  • I confirmed that WindsorControllerFactory returns proxy controllers.
  • I confirmed that the controllers have a registered interceptor.
  • I confirmed that MasterProxy in SNAP intercepts the controller, but it only intercepts IController.Execute(RequestContext requestContext) .

How to intercept all controller methods that have my [Log] attribute?

Update 1: I looked at using DynamicProxy directly, not SNAP, but this is secondary to make it work for controllers as well.

Update 2 + 4: It looks like SNAP is missing from github back to github .

Update 3: This is what I see in the Visual Studio debugger when hacking the WindsorControllerFactory (see above). The checked controller variable is what MVC returns, and it is really proxied.

  • controller {Castle.Proxies.IMyBusinessControllerProxy}
    • __interceptors {Castle.DynamicProxy.IInterceptor [1]}
      • [0] {Snap.MasterProxy}
    • __target {My.Business.Web.Controllers.MyBusinessController}
      • service {Castle.Proxies.IMyBusinessServiceProxy}
      • (other contructor injections)
    • MyInjectedProperty {My.Business.Useful.MyOtherType}
+4
source share
2 answers

In IController GetControllerInstance(...) do not serve proxy servers, service proxy classes using virtual methods.

User methods in the controller returned from the IController GetControllerInstance(...) will not be accessible through the proxied IMyBusinessController interface, but will be discarded from IController to the actual controller class; e.g. MyBusinessController . Instead, use the class proxy to force MVC3-cast to return the proxy. Also, mark the methods as virtual , otherwise the interception proxy will not be able to intercept method calls and check user attributes.

In controllers, add virtual to your methods with attributes:

 public class MyBusinessController : MyBusinessControllerBase, IMyBusinessController { [Log(LoggingLevel.Debug)] public virtual ActionResult CreateOrUpdate(MyBusinessFormModel fm) { // Convert form model to data transfer object, // perform validation and other checks this.service.Save(dto); return View(fm); } } 

Why is only Execute(...) intercepted? The IController interface contains only Execute(...) . Execute is called on the proxy server of the returned controller, so it can be intercepted. But as soon as the MVC3 internal ControllerBase.Execute(...) receives the call, it casts to the class that it expected from ControllerFactory .

The problem is similar to this leak , including bypassing the proxy interface. I think this could be solved in several ways; perhaps by creating a user-type converter, creating a proxy class from the target interface proxy in the factory, smart Windsor configurations, etc.

Krzysztof Koźmic installer IController and WindsorControllerFactory should work out of the box. Interface proxies can be recommended in a larger picture (and they work well until they use sniffers in controllers), but in this case there may be a reason not to go so far as to avoid further side effects.

Thanks to Marius for pointing me in the right direction!

+4
source

Since DynamicProxy (SNAP uses dynamicproxy) cannot intercept non-virtual methods, I assume that the returned proxy is a derived class of your controller, and thus non-virtual methods are ignored. You need to either make SNAP (I don’t know how it works), return the proxy server with the goal (your implementation), or just try to make your controller methods virtual.

+2
source

Source: https://habr.com/ru/post/1392000/


All Articles