Showing posts with label FilterAttribute. Show all posts
Showing posts with label FilterAttribute. Show all posts

Tuesday, September 11, 2012

Dual-invocation using Autofac interception - AOP


- The project I've been working on used to have SQL as its primary datastore. We're implementing another data store using MongoDB as it will replace the SQL one soon. During the migration process, we have 2 versions of the website running parallel. The production version is using SQL which is viewed by customers and the other one using MongoDB which is being tested by our staff. So once the Mongo version development is finished, it will be kept running in parallel with the SQL version until we're completely confident that all the features are working fine. During that time, any changes made by users to the SQL db have to be applied to MongoDB.

- There're quite a few solutions in my mind. However, we've had some sort of DataAccess dlls to access Sql database before and another implementation of same interfaces which talk to MongoDB have been implemented. So the options is narrowed down to something like an adapter pattern which takes 2 instances of the same interface, invoke method on the first one and then invoke the same method on the second instance. Being a good fan of Composition over Inheritance, I was thinking of something like:
public class DualService<T> : T
{
    private readonly T _primary;
    private readonly T _secondary;
    
    public DualService(T primary, T secondary)    
    {
        _primary = primary;
        _secondary = secondary;    
    }
    
    public void MethodA()
    {
        _primary.MethodA();
        _secondary.MethodA();    
    }
    
    // So on
}

- Well, this approach is good only if the generic T interface has just a few methods. Ours has over 30 methods and it keeps counting ;), it's implemented by the "Outsource team" So I dropped this option, and thought about the Autofac Interceptor. The intercepter I'm gonna implement will take the secondary implementation and invoke the same method as the invocation if:

  1. The method is not decorated by IgnoreInterceptAttribute
  2. The method has a result type void. (Well, a command should not have return type here if you're curios about the reason)
  3. The method is decorated by DualInvocationAttribute

- Here is the implementation. This approach requires Castle.Core and AutofacContrib.DynamicProxy2. The 2 attributes are just simple attributes I created to enhance the filter:
[AttributeUsage(AttributeTargets.Method)]
public class IgnoreInterceptAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Method)]
public class DualInvocationAttribute : Attribute
{
}

public class DualInvocation<T> : IInterceptor where T: class 
{
    private readonly T _secondaryImplementation;
    private readonly Action<Exception> _onSecondaryImplementationInvokeError;

    public DualInvocation(T secondaryImplementation, Action<Exception> onSecondaryImplementationInvokeError = null)
    {
        _secondaryImplementation = secondaryImplementation;
        _onSecondaryImplementationInvokeError = onSecondaryImplementationInvokeError ?? (ex => Trace.WriteLine(ex));
    }

    public void Intercept(IInvocation invocation)
    {
        invocation.Proceed();
        var returnType = invocation.Method.ReturnType;
        var att = invocation.Method.GetCustomAttributes(typeof(IgnoreInterceptAttribute), true);
        if (att.Any())
        {
            return;
        }

        if (returnType != typeof(void))
        {
            att = invocation.Method.GetCustomAttributes(typeof(DualInvocationAttribute), true);
            if (!att.Any())
            {
                return;
            }
        }

        var methodInfo = invocation.Method;
        try
        {
            methodInfo.Invoke(_secondaryImplementation, invocation.Arguments);
        }
        catch (Exception ex)
        {
            _onSecondaryImplementationInvokeError(ex);
        }
    }
}

- You might wonder about how to use this class, take a look at the unit tests. You should probably do something either in code or autofac xml config, similar to what I did in the SetUp:
- Here is a dummy interface to demonstrate the Autofac registrations
public interface ISomeService
{
    void Update();

    void Update(int userId);

    [IgnoreIntercept]
    void Update(int userId, bool multi);

    int GetUser(int userId);

    [DualInvocation]
    bool DeleteUser(int userId);
}

- And here is the tests, the tests are using NSubstitute to mock the interface. (Pretty similar to Moq, etc)
[TestFixture]
public class MethodIntercept
{
    private ISomeService primary,
                         secondary;

    private IContainer container;
    private Action<Exception> exceptionHandler;

    [SetUp]
    public void RegisterAutofacComponents()
    {
        primary = Substitute.For<ISomeService>();
        secondary = Substitute.For<ISomeService>();

        var builder = new ContainerBuilder();

        builder.RegisterInstance(secondary).Named<ISomeService>("secondary").SingleInstance();

        builder.RegisterInstance(primary)
               .Named<ISomeService>("primary").SingleInstance()
               .EnableInterfaceInterceptors()
               .InterceptedBy(typeof (DualInvocation<ISomeService>));

        builder.RegisterType<DualInvocation<ISomeService>>()
               .WithParameters(new Parameter[]
               {
                   new ResolvedParameter((p, c) => p.Name == "secondaryImplementation", (p, c) => c.ResolveNamed("secondary", typeof (ISomeService))),
                   new NamedParameter("exceptionHandler", exceptionHandler)
               })
               .AsSelf()
               .InstancePerLifetimeScope();

        container = builder.Build();
    }

    [Test]
    public void Should_invoke_secondary_method_if_it_is_not_ignored()
    {
        // Arrange
        var primaryService = container.ResolveNamed<ISomeService>("primary");
        var secondaryService = container.ResolveNamed<ISomeService>("secondary");

        // Action
        primaryService.Update();

        // Assert
        secondaryService.Received(1).Update();
    }

    [Test]
    public void Should_invoke_secondary_overload_method_if_it_is_not_ignored()
    {
        // Arrange
        var primaryService = container.ResolveNamed<ISomeService>("primary");
        var secondaryService = container.ResolveNamed<ISomeService>("secondary");

        // Action
        primaryService.Update(1);

        // Assert
        secondaryService.Received(1).Update(1);
    }

    [Test]
    public void Should_invoke_secondary_overload_method_if_it_is_decorated_by_DualInvocationAttribute()
    {
        // Arrange
        var primaryService = container.ResolveNamed<ISomeService>("primary");
        var secondaryService = container.ResolveNamed<ISomeService>("secondary");

        // Action
        primaryService.DeleteUser(1);

        // Assert
        secondaryService.Received(1).DeleteUser(1);
    }

    [Test]
    public void Should_not_invoke_secondary_method_if_it_is_not_void()
    {
        // Arrange
        var primaryService = container.ResolveNamed<ISomeService>("primary");
        var secondaryService = container.ResolveNamed<ISomeService>("secondary");

        // Action
        primaryService.GetUser(1);

        // Assert
        secondaryService.DidNotReceive().GetUser(Arg.Any<int>());
    }

    [Test]
    public void Should_not_invoke_secondary_method_if_it_is_ignored()
    {
        // Arrange
        var primaryService = container.ResolveNamed<ISomeService>("primary");
        var secondaryService = container.ResolveNamed<ISomeService>("secondary");

        // Action
        primaryService.Update(1, true);

        // Assert
        secondaryService.DidNotReceive().Update(Arg.Any<int>(), Arg.Any<bool>());
    }

    [Test]
    public void Should_not_throw_exception_if_cannot_invoke_secondary_method()
    {
        // Arrange
        exceptionHandler = ex => Trace.WriteLine(ex);
        secondary.When(x => x.Update()).Do(callInfo => { throw new Exception(); });

        var primaryService = container.ResolveNamed<ISomeService>("primary");
        var secondaryService = container.ResolveNamed<ISomeService>("secondary");

        // Action
        primaryService.Update();

        // Assert
        secondaryService.Received(1).Update();
    }
}


- I reckon this approach is clean, simple and It works on my machine . Cheers.

Friday, September 23, 2011

Combine logic of MVC FilterAttribute classes

    Hi mates, today I would like to write another blog about the reason why I need an Autofac IContainer as the dependency in some of my classes. In my team, we are developing a website that can allow users create and manage information about organisations. We have frontend pages for normal users to manage their organisations and we also have backend pages for the administrator to do that. So on frontend pages, we created a custom MVC filter attribute to decorate on every action methods that requires a need to check whether current user has the permission to view or edit the organisation. This filter attribute is also used in other places where we need similar authorization logic. On the other hand, we just need built-in Authorize attribute for all the admin pages. That looks like a straightforward solution, right?

    However, life is not that easy. Life is like a dick and sometime it's getting hard for no reason :D. The team decided to use MVC areas for Administrator and Organisations. That means the OrganisationManageController will be shared between those areas. Therefore, the authorization requirement would be: "Either the user is admin or the owner of the orngaisation, let him in". The first thing poped up in my mind was creating another filter attribute, inherit from Authorize filter attribute and copy the logic of my custom filter attribute which willl check the ownership. But not long after that, I realized it was so naive and ugly since the logic will be duplicated in those 2 attributes. Not only that, I was going to violate the cross-concern of the MVC Filter attributes because each of them should care and know about one and only one responsibility.

    Then I come up with the idea i'm gonna write in this blog. I decided to create another Filter attribute that implements IAuthorizeFilter. It will be resonpsible for executing all the nested IAuthorizeFilter attributes and let the user pass in if one of the filter was satisfied. The usasge will look like:
[HttpGet, Either(typeof(AuthorizeAttribute), @"Roles = ""Administrator""", typeof(CheckOwnerAttribute))]
public ActionResult Edit(ObjectId id)
{
	// ..........
}

    The EitherAttribute class will have 1 public constructor that accepts an array of objects, begin by the type of an IAuthorizeFilter, followed by it's setter information or parameters for it's constructor or both and so on. So on method OnAuthorization, it will iterate through the child filters and execute method OnAuthorization on this filter. If after executing, the filterContext.Result is modified which means the user is not authorized, i'll reset the result and go for next filter. If it's the last filter which modified the filterContext.Result, i'll just simply return. Here is the implementation:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class EitherAttribute : FilterAttribute, IAuthorizationFilter
{
    // I need a dependency of ContainerFactory here
    public Func<IContainer> ContainerFactory { get; set; }
    
    private readonly object[] _filterConstructDescriptions;

    public EitherAttribute(params object[] filterConstructDescriptions)
    {
        _filterConstructDescriptions = filterConstructDescriptions;
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        List<IAuthorizationFilter> _filters = CreateFilters(_filterConstructDescriptions);
        for (var i = 0; i < _filters.Count(); i++)
        {
            var filter = _filters[i];
            filter.OnAuthorization(filterContext);
            if (filterContext.Result == null)
            {
                return;
            }
            
            // Check next authorize filter
            if (i < _filters.Count() - 1)
            {
                filterContext.Result = null;
            }
        }
    }

    // ..........
}

    There is one interesting point to note here is the EitherAttribute has a dependency on Autofac IContainer because the nested filter will be created by Activator and if it has dependency on some services, we can use IContainer to inject require informations in. That's the reason I wrote about in previous post.

    So with this awesome attribute, I don't have to duplicate the logic across different attributes and now can use it for many different IAuthorizeFilter classes. There is only 1 limitation which is the type it support is IAuthorizeFilter only but I haven't known yet any other cases that need to extend the EitherAttribute to support other filter types.

    So please checkout the source code that includes some unit tests. Cheers