bilpor bilpor -4 years ago 69
C# Question

MVC Security Filters passing interfaces

I have a sealed class in the form:

public sealed class AuthorizeLaoAttribute : AuthorizeAttribute


and then typically I'd use it in an MVC controller:

[HttpGet]
[AuthorizeLaoAttribute]
public ActionResult Overview(int id)


I want to take this one step further in that I want to inject an interface into the AuthorizeLaoAttributeClass so that I can perform some complex logic on determining if the user is authorised.

So I want to include a constructor in the form :

public AuthorizeLaoAttribute(IServiceUserService serviceUserService)


on the sealed class. However in the controller it complains the the value I'm passing needs to be static, a constant type,typeof or an array. I could just pass interger values in based when using them and then work directly against the classes by declaring 'New' in my sealed class method, but it breaks my DI pattern. Is there a way to do what I want to do.

so if I do something like:

public AuthorizeLaoAttribute(int id)


and then

[AuthorizeLaoAttribute(12)]
public ActionResult Overview(int id)


there are no complaints, but I dont really want to instantiate the object in my security class.

Answer Source

You can do this using a passive attribute. What that means is that the attribute class itself doesn't have any code, because, as you determined, you can't inject anything into an attribute.

The steps are:

  • Create the attribute you want to place on controllers or methods.
  • Create an IAuthorizationFilter. This is where the real work is done. The filter checks to see if the attribute is on the called controller or method. If it is, it does its filtering. If the attribute is not present it does nothing.
  • At application startup, resolve an instance of the filter from your container (this enables you to dependency injection.) Add that filter to your global filter collection. So the filter is technically going to execute for every request. But when it executes it's going to check for the attribute. So it ends up working like a "normal" authorization attribute. The filter only really applies when the attribute is present.

This approach was developed by Mark Seemann, author of Dependency Injection in .NET. I'm just throwing that out there because it might sound a little convoluted, but it comes from a good source.

I use it and it works. Here's a blog post that describes it in more detail, and shows how to simplify it by creating a base class for the filter that handles checking for the attribute. That way you don't have to write that part over and over. At first I thought the whole concept was a little heavy, but use of the base class makes it a lot simpler. Now the only part you need to write is the actual logic of the filter itself.

The example in the blog post deals with Web API, but it applies to MVC filters as well. Here's the base class that I use for MVC authorization filters. In addition to checking for the presence of the attribute it also passes the attribute itself to the filter's behavior in case the attribute has its own properties that the filter needs to inspect.

public abstract class AuthorizationFilterBehaviorBase<TAttribute> : IAuthorizationFilter where TAttribute : Attribute
{
    private readonly IContextAttributeInspector _attributeInspector;

    protected AuthorizationFilterBehaviorBase(IContextAttributeInspector attributeInspector)
    {
        _attributeInspector = attributeInspector;
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        TAttribute attribute = null;
        if (_attributeInspector.TryGetActionAttribute(filterContext.ActionDescriptor, out attribute)
            || _attributeInspector.TryGetControllerAttribute(filterContext, out attribute))
        {
            OnAuthorizationBehavior(filterContext, attribute);
        }
    }

    protected abstract void OnAuthorizationBehavior(AuthorizationContext authorizationContext, TAttribute attribute);
}

The interface and class that look for the attribute are broken out separately to maintain SRP.

public interface IContextAttributeInspector
{
    bool ControllerHasAttribute<TAttribute>(ControllerContext controllerContext) where TAttribute : Attribute;
    bool ActionHasAttribute<TAttribute>(ActionDescriptor actionDescriptor) where TAttribute : Attribute;
    bool TryGetControllerAttribute<TAttribute>(ControllerContext controllerContext, out TAttribute attribute) where TAttribute : Attribute;
    bool TryGetActionAttribute<TAttribute>(ActionDescriptor actionDescriptor, out TAttribute attribute) where TAttribute : Attribute;
}  

public class ContextAttributeInspector : IContextAttributeInspector
{
    public bool ControllerHasAttribute<TAttribute>(ControllerContext controllerContext) where TAttribute : Attribute
    {
        return controllerContext.Controller.GetType()
            .GetCustomAttributes(false)
            .Any(attribute => attribute.GetType().IsAssignableFrom(typeof(TAttribute)));
    }

    public bool ActionHasAttribute<TAttribute>(ActionDescriptor actionDescriptor) where TAttribute : Attribute
    {
        return actionDescriptor
            .GetCustomAttributes(typeof(TAttribute), true)
            .Any();
    }

    public bool TryGetControllerAttribute<TAttribute>(ControllerContext controllerContext, out TAttribute attribute) where TAttribute : Attribute
    {
        var foundAttribute = controllerContext.Controller.GetType()
            .GetCustomAttributes(false)
            .FirstOrDefault(customAttribute => customAttribute.GetType().IsAssignableFrom(typeof(TAttribute)));
        if (foundAttribute != null)
        {
            attribute = (TAttribute)foundAttribute;
            return true;
        }
        attribute = null;
        return false;
    }

    public bool TryGetActionAttribute<TAttribute>(ActionDescriptor actionDescriptor, out TAttribute attribute) where TAttribute : Attribute
    {
        var foundAttribute = actionDescriptor
            .GetCustomAttributes(typeof(TAttribute), true)
            .FirstOrDefault();
        if (foundAttribute != null)
        {
            attribute = (TAttribute)foundAttribute;
            return true;
        }
        attribute = null;
        return false;
    }
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download