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;
}
}