How to Test for the Presence of an Action Filter with Constructor Arguments

How can I test for the presence of an Action Filter with constructor arguments?

Well, you have taken a good first step by recognizing that Web.config is just another dependency and wrapping it into a ConfigProvider to inject is an excellent solution.

But, you are getting tripped up on one of the design problems of MVC - namely, that to be DI-friendly, attributes should only provide meta-data, but never actually define behavior. This isn't an issue with your approach to testing, it is an issue with the approach to the design of the filter.

As pointed out in the post, you can get around this issue by splitting your action filter attribute into 2 parts.

  1. An attribute that contains no behavior to flag your controllers and action methods with.
  2. A DI-friendly class that implements IActionFilter and contains the desired behavior.

The approach is to use the IActionFilter to test for the presence of the attribute, and then execute the desired behavior. The action filter can be supplied with all dependencies and then injected when the application is composed.

IConfigProvider provider = new WebConfigProvider();
IActionFilter filter = new MaxLengthActionFilter(provider);
GlobalFilters.Filters.Add(filter);

NOTE: If you need any of the filter's dependencies to have a lifetime shorter than singleton, you will need to use a GlobalFilterProvider as in this answer.

The implementation of MaxLengthActionFilter would look something like this:

public class MaxLengthActionFilter : IActionFilter
{
public readonly IConfigProvider configProvider;

public MaxLengthActionFilter(IConfigProvider configProvider)
{
if (configProvider == null)
throw new ArgumentNullException("configProvider");
this.configProvider = configProvider;
}

public void OnActionExecuted(ActionExecutedContext filterContext)
{
var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor);
if (attribute != null)
{
var maxLength = attribute.MaxLength;

// Execute your behavior here, and use the configProvider as needed
}
}

public void OnActionExecuting(ActionExecutingContext filterContext)
{
var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor);
if (attribute != null)
{
var maxLength = attribute.MaxLength;

// Execute your behavior here, and use the configProvider as needed
}
}

public MaxLengthAttribute GetMaxLengthAttribute(ActionDescriptor actionDescriptor)
{
MaxLengthAttribute result = null;

// Check if the attribute exists on the controller
result = (MaxLengthAttribute)actionDescriptor
.ControllerDescriptor
.GetCustomAttributes(typeof(MaxLengthAttribute), false)
.SingleOrDefault();

if (result != null)
{
return result;
}

// NOTE: You might need some additional logic to determine
// which attribute applies (or both apply)

// Check if the attribute exists on the action method
result = (MaxLengthAttribute)actionDescriptor
.GetCustomAttributes(typeof(MaxLengthAttribute), false)
.SingleOrDefault();

return result;
}
}

And, your attribute which should not contain any behavior should look something like this:

// This attribute should contain no behavior. No behavior, nothing needs to be injected.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MaxLengthAttribute : Attribute
{
public MaxLengthAttribute(int maxLength)
{
this.MaxLength = maxLength;
}

public int MaxLength { get; private set; }
}

With a more loosely coupled design, testing for the existence of the attribute is much more straightforward.

[TestMethod]
public void Base_controller_must_have_MaxLengthFilter_attribute()
{
var att = typeof(BaseController).GetCustomAttribute<MaxLengthAttribute>();
Assert.IsNotNull(att);
}

How to prevent executing of specific custom action filter

You simply need to make another attribute and use .NET reflection to check for its existence.

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!HasMyIgnoreAttribute(filterContext))
{
//Do some security tests
}
base.OnActionExecuting(filterContext);
}

public bool HasMyIgnoreAttribute(ActionDescriptor actionDescriptor)
{
// Check if the attribute exists on the action method
bool existsOnMethod = actionDescriptor.IsDefined(typeof(MyIgnoreAttribute), false);

if (existsOnMethod)
{
return true;
}

// Check if the attribute exists on the controller
return actionDescriptor.ControllerDescriptor.IsDefined(typeof(MyIgnoreAttribute), false);
}

And then make a custom attribute to decorate your actions/controllers with.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MyIgnoreAttribute : Attribute
{
}

Usage

[MySecurity]
public class MyController
{
[MyIgnore]
public ActionResult Index()
{
return View();
}

public ActionResult About()
{
return View();
}
}

In general, it is best not to use ActionFilterAttribute if you are using dependency injection, since attributes should contain no behavior as in this answer. You should also consider using an authorization filter (or AuthorizationAttribute-inherited class) rather than an action filter for security checks, since it is done earlier in the pipeline.

ActionFilter and Authorize Attribute in MVC causing object reference error

Per MSDN:

Filters run in the following order:

  1. Authorization filters
  2. Action filters
  3. Response filters
  4. Exception filters

The reason your SessionExpire attribute fires after your AuthorizeSubscription attribute is because MVC always fires Authorization filters first.

So to fix that problem, you need your SessionExpire to implement IAuthorizationFilter (and presumably inherit Attribute).

In addition, you will need to set the order of your attributes, because the .NET framework does not guarantee which order they will be processed in.

[SessionExpire(Order=1)]
[AuthorizeSubscription(Order=2)]
public class StoreController : Controller
{
// Remaining implementation...

Do note that the best approach is to separate your filters from your attributes, which both allows them to be DI-friendly and also allows you to set the order explicitly by registering the filters globally in a specific order.

Constructor Dependency Injection WebApi Attributes

Yes, it is possible. You (like most people) are being thrown by Microsoft's marketing of Action Filter Attributes, which are conveniently put into a single class, but not at all DI-friendly.

The solution is to break the Action Filter Attribute into 2 parts as demonstrated in this post:

  1. An attribute that contains no behavior to flag your controllers and action methods with.
  2. A DI-friendly class that implements IActionFilter and contains the desired behavior.

The approach is to use the IActionFilter to test for the presence of the attribute, and then execute the desired behavior. The action filter can be supplied with all dependencies (through the constructor) and then injected when the application is composed.

IConfigProvider provider = new WebConfigProvider();
IActionFilter filter = new MaxLengthActionFilter(provider);
config.Filters.Add(filter);

NOTE: If you need any of the filter's dependencies to have a lifetime shorter than singleton, you will need to use a GlobalFilterProvider as in this answer.

To wire this up with StructureMap, you will need to return an instance of the container from your DI configuration module. The Application_Start method is still part of the composition root, so you can use the container anywhere within this method and it is still not considered a service locator pattern. Note that I don't show a complete WebApi setup here, because I am assuming you already have a working DI configuration with WebApi. If you need one, that is another question.

public class DIConfig()
{
public static IContainer Register()
{
// Create the DI container
var container = new Container();

// Setup configuration of DI
container.Configure(r => r.AddRegistry<SomeRegistry>());
// Add additional registries here...

#if DEBUG
container.AssertConfigurationIsValid();
#endif

// Return our DI container instance to the composition root
return container;
}
}

public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// Hang on to the container instance so you can resolve
// instances while still in the composition root
IContainer container = DIConfig.Register();

AreaRegistration.RegisterAllAreas();

// Pass the container so we can resolve our IActionFilter
WebApiConfig.Register(GlobalConfiguration.Configuration, container);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
}
}

public static class WebApiConfig
{
// Add a parameter for IContainer
public static void Register(HttpConfiguration config, IContainer container)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();

// Add our action filter
config.Filters.Add(container.GetInstance<IMaxLengthActionFilter>());
// Add additional filters here that look for other attributes...
}
}

The implementation of MaxLengthActionFilter would look something like this:

// Used to uniquely identify the filter in StructureMap
public interface IMaxLengthActionFilter : System.Web.Http.Filters.IActionFilter
{
}

public class MaxLengthActionFitler : IMaxLengthActionFilter
{
public readonly IConfigProvider configProvider;

public MaxLengthActionFilter(IConfigProvider configProvider)
{
if (configProvider == null)
throw new ArgumentNullException("configProvider");
this.configProvider = configProvider;
}

public Task<HttpResponseMessage> ExecuteActionFilterAsync(
HttpActionContext actionContext,
CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> continuation)
{
var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor);
if (attribute != null)
{
var maxLength = attribute.MaxLength;
// Execute your behavior here (before the continuation),
// and use the configProvider as needed

return continuation().ContinueWith(t =>
{
// Execute your behavior here (after the continuation),
// and use the configProvider as needed

return t.Result;
});
}
return continuation();
}

public bool AllowMultiple
{
get { return true; }
}

public MaxLengthAttribute GetMaxLengthAttribute(ActionDescriptor actionDescriptor)
{
MaxLengthAttribute result = null;

// Check if the attribute exists on the action method
result = (MaxLengthAttribute)actionDescriptor
.GetCustomAttributes(typeof(MaxLengthAttribute), false)
.SingleOrDefault();

if (result != null)
{
return result;
}

// Check if the attribute exists on the controller
result = (MaxLengthAttribute)actionDescriptor
.ControllerDescriptor
.GetCustomAttributes(typeof(MaxLengthAttribute), false)
.SingleOrDefault();

return result;
}
}

And, your attribute which should not contain any behavior should look something like this:

// This attribute should contain no behavior. No behavior, nothing needs to be injected.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MaxLengthAttribute : Attribute
{
public MaxLengthAttribute(int maxLength)
{
this.MaxLength = maxLength;
}

public int MaxLength { get; private set; }
}

Cannot create instance of repository object in action filter MVC

public class CheckFirstLoginAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);

string uName = HttpContext.Current.User.Identity.Name;

if (!string.IsNullOrEmpty(uName))
{
//Here is how I could get the instance of the repository
//I had to register the repository type and then
//I resolved it to obtain the object of the repository and
//access the methods in the repository
var container = new UnityContainer();
container.RegisterType<IUnitOfWork, UnitOfWork>();
container.RegisterType<UserRepository>();

UserRepository repo = container.Resolve<UserRepository>();

user cUser = repo.GetUserId(uName);
if (cUser.SubscriptionStatus == 1)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "Manage",
action = "ChangePassword"
}));
}
}
}
}

How to read action method's attributes in ASP.NET Core MVC?

You can access the MethodInfo of the action through the ControllerActionDescriptor class:

public void OnActionExecuting(ActionExecutingContext context)
{
if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
var actionAttributes = controllerActionDescriptor.MethodInfo.GetCustomAttributes(inherit: true);
}
}

The MVC 5 ActionDescriptor class used to implement the ICustomAttributeProvider interface which gave access to the attributes. For some reason this was removed in the ASP.NET Core MVC ActionDescriptor class.



Related Topics



Leave a reply



Submit