I have a project with several layers - among them the web front end (ASP.NET MVC3) and the service back end (mainly business logic). The project is a few months old, so everything is working as expected. Now I am trying to add a logging aspect to some of the MVC3 controller methods using custom [Log]
attributes.
I am using Castle Windsor for dependency injection. To get a logging aspect I leverage Castle DynamicProxy through SNAP. Controllers are being resolved using WindsorControllerFactory
from Krzysztof Koźmic's helpful tutorial - but I modified it to look for 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 web front end's IWindsorInstaller
for controllers:
private static BasedOnDescriptor FindControllers()
{
return AllTypes
.FromThisAssembly()
.BasedOn<IController>()
.WithService.DefaultInterface();
}
In my (slightly customized) WindsorControllerFactory
that looks for the default interface for the controller:
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);
}
}
This all works fine in the service project, but in the controllers the methods are not being intercepted.
- I have confirmed that the
WindsorControllerFactory
returns proxied controllers. - I have confirmed that the controllers have the interceptor registered.
- I have confirmed that the
MasterProxy
in SNAP intercepts the controller - but it only interceptsIController.Execute(RequestContext requestContext)
.
How can I intercept all controller methods that have my [Log]
attribute?
Update 1: I have considered using DynamicProxy directly instead of SNAP, but this is secondary to getting it to work for controllers as well.
Update 2+4: It seems that SNAP is missing from github back on github.
Update 3: This is what I see in the Visual Studio debugger when breaking in the WindsorControllerFactory
(see above). The inspected controller
variable is what is returned to MVC, and it is indeed 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}
In
IController GetControllerInstance(...)
, don't serve interface proxies, serve class proxies withvirtual
methods.The user-implemented methods in the controller returned from
IController GetControllerInstance(...)
will not be accessed through the proxiedIMyBusinessController
interface, but cast fromIController
to to the actual class of the controller; for exampleMyBusinessController
. Use a class proxy instead, to make MVC3's cast return the proxy. Also, mark methods asvirtual
, otherwise the intercepting proxy won't be able to intercept the method calls and check for custom attributes.In the controllers, add
virtual
to your methods with attributes:Why is only
Execute(...)
intercepted? TheIController
interface only containsExecute(...)
. Execute is called on the returned controller interface proxy, thus it can be intercepted. But once MVC3's internalControllerBase.Execute(...)
gets the call, it performs the cast to the class it expected from theControllerFactory
.The problem is similar to
this
leaking, in that both bypass the interface proxy. I guess it could be solved in a number of ways; perhaps by creating a custom type converter, creating a class proxy from the interface proxy's target in the factory, a clever Windsor configurations etcetera.Krzysztof Koźmic's IController installer and
WindsorControllerFactory
should work out of the box. Interface proxies may be recommended in the bigger picture (and they work well until using interceptors in the controllers) but in this case there might be a reason not to go that far, to avoid further side effects.Thanks to Marius for pointing me in the right direction!