Logging using Decorators

Dec 11, 2013 at 4:25 PM
Hi,

I was looking into using interception to implement logging via AOP, but there seems to be a lot of encouragement to use the decorator pattern rather than interception. My plan was to wrap try/catch handling around the appropriate interfaces.

So my basic question: How would that similar "try/catch wrapping" functionality be implemented using decorators? Are there any good examples out there? I would prefer to continue to use SimpleInjector as my DI/IoC container if possible. Thanks in advance for your reply!

-Jon
Coordinator
Dec 11, 2013 at 7:20 PM
Edited Oct 21, 2014 at 10:24 AM
Hi Jon,

Writing a decorator is very similar to writing an interceptor (except that writing a decorator is much cleaner). There's a whole section about writing decorators in the Simple Injector documentation. Other interesting examples can be found in this article and this article for instance.

Do note that although it isn't bad practice to have try-catch logic as aspects around behavior, please make sure you aren't logging too much and too often, as explained here.

I hope this helps.
Dec 12, 2013 at 2:25 PM
Thanks for the feedback! At this point my goal is simply to wrap the Web API and ASP.NET style MVC "Controllers" since they are essentially the top entry point of the application. I have not followed a command pattern at this point, and to do so would likely require a lot more time than I have right now to finish the project. That being said, I think I will try that approach the next opportunity I have. I had been hoping that there would be an easy way to wrap the "Controller" calls with decorators rather than using the interception logic extension.

Granted, doing some more research, I like your thoughts on a custom HttpModule. Might be a lot more injectable than the Applicatoin Error.

As a side question, the command handler pattern looks a lot like CQRS. Are the two related, if so how?

Again, I appreciate the feedback, this is really cool stuff, and I am loving Simple Injector.
Coordinator
Dec 12, 2013 at 3:27 PM
Edited Oct 21, 2014 at 10:25 AM
The command pattern is certainly related to CQRS, since they are both based on the same principles: SOLID and message based architecture. CQRS however goes a step further and should be applied to 'collaborative domains', while the command/handler pattern in itself is more widely appliable.

About controllers, decorating MVC controllers with decorators is rather straightforward. Decorating API controllers on the other hand is is not as obvious to do. I happened to ask two questions on Stackoverflow about this. Here and here.

For MVC controllers, you can do the following:
var controllerTypeToProducerMappings = 
    SimpleInjectorMvcExtensions.GetControllerTypesToRegister(container, 
        Assembly.GetExecutingAssembly())
    .ToDictionary(t => t, 
        t => Lifestyle.Transient.CreateProducer(typeof(IController), t, container));

container.RegisterSingle<IControllerFactory>(
    new SimpleInjectorControllerFactory(controllerTypeToProducerMappings));
Where SimpleInjectorControllerFactory is a custom IControllerFactory:
class SimpleInjectorControllerFactory : DefaultControllerFactory
{
    IDictionary<Type, InstanceProducer> controllers;

    public SimpleInjectorControllerFactory(IDictionary<Type, InstanceProducer> controllers)
    {
        this.controllers = controllers;
    }

    protected override IController GetControllerInstance(RequestContext requestContext, 
        Type controllerType)
    {
        return (IController)this.controllers[controllerType].GetInstance();
    }
}
With this registration you can add custom decorators for IController:
container.RegisterDecorator(typeof(IController), typeof(SomeSpecialControllerDecorator));
Dec 20, 2013 at 5:09 PM
Thanks for that information. I feel like I'm right there, but I do have one last, probably silly, question. How will the decorator intercept calls to the controller's methods without knowing in advance what those methods are? I must be missing something here as the API Calls on one controller might be something like GetStuff(), PostStuff(), etc. Whereas the API calls on another controller might be GetOtherStuff(), PostOtherStuff(), etc. Leading to different signatures and such. How exactly would that work with a common decorator meant to wrap any controller, and whatever call that controller might make?

Thanks again for the responses though.

-Jon
Coordinator
Dec 21, 2013 at 9:29 AM
Edited Dec 21, 2013 at 9:30 AM
It depends on what aspect you are trying to add whether decorators is the best approach for MVC, since the design of MVC can sometimes be limiting. However, you can get the ActionDescriptor for the current action method using the controller and the RequestContext like this:
// This is the Execute method of the Decorator
public void Execute(RequestContext requestContext)
{
    var controller = (Controller)this.decoratee;

    var descriptor = new ReflectedControllerDescriptor(controller.GetType());

    ActionDescriptor action = descriptor.FindAction(controller.ControllerContext,
        requestContext.RouteData.GetRequiredString("action"));

    // The ActionDescriptor contains (almost) all information you need.
    var filterAttributes = action.GetFilterAttributes(true);

    this.decoratee.Execute(requestContext);
}
Downside of this approach is that this breaks when you start applying more decorators. I'm currently thinking about improving decorator support in a way that would make it easier to do this. But you will often find that the chosen presentation technology is a limiting factor in using good design. So if using decorators don't feel right in your case, you can also use an approach as I described here for Web API. You can do the same with MVC.
Marked as answer by dot_NET_Junkie on 12/29/2013 at 8:21 AM
Dec 23, 2013 at 2:54 PM
Oh man, thank you for the response. You'll laugh, but using filters was eventually where I eneded up. Using a filter that could be used in both MVC and WebAPI scenarios was where I finally landed, and it's been working as well as can be expected in the ASP.NET MVC land. Great responses and help throughout though, thank you so much!

-Jon