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

0 comments:

Post a Comment