With Action Filters in ASP.NET Core you can alter the behavior of an Action Method before or after the method is called. You can use it to alter the arguments passed to the method or the returned result, also if you want to take some action when an Action Method is called, for example logging something everytime someone adds a product to the cart.
Action Filters can come in very handy in nopCommerce development when there is a need to extend or manipulate nopCommerce but without touching the core. In a nopCommerce plugin you can easily create a custom Action Filter and add it to the ASP.NET filter pipeline.
In this simple example we are going to create a custom Action Filter that manipulates the product names before the user loads the product details page. Perhaps not the most useful Action Filter but you will get an understanding of how they are created.
To create the Action Filter we need to perform following steps:
ActionFilterAttribute
.Let's get started by creating a new class ProductDetailsActionFilter
in a plugin (for convenience I usually put all filters in Filters
folder in root of the plugin). Let the class derive from ActionFilterAttribute
.
Filters/ProductDetailsActionFilter.cs
public class ProductDetailsActionFilter : ActionFilterAttribute
{
}
ActionFilterAttribute
has 5 methods that can be overridden in order to hook into various stages of the call to an Action Method:
OnActionExecuting
is called before the action is executed.OnActionExecuted
is called after the action is executed.OnResultExecuting
is called before the action's result is executed.OnResultExecuted
is called after the action's result is executed.OnActionExecutionAsync
is called asynchronously before the action is executed, with this method you can execute code both before and after the action is executed.We want to manipulate the product name before the result is executed and we will need to do this in the OnResultExecuting
method
public override void OnResultExecuting(ResultExecutingContext context)
{
// TODO only proceed if we are on the ProductDetails Action Method in the ProductController
var result = context.Result as ViewResult;
if (result == null) return;
var model = result.Model as ProductDetailsModel;
if (model == null) return;
model.Name += " - This is magic from a Custom Action Filter";
}
From the passed ResultExecutionContext
we can access the action result. In this case the result will be of type ViewResult
since that is what's returned from ProductDetails
action method if everything went well. The null check is important since a method could return something else if the code does not take the expected path, in this case if a product is not found a RedirectToRoute
result is returned which would casuse our casting to ViewResult
to be null and we want to stop executing our filter. Next we need to get hold of the returned model and cast it to ProductDetailsModel
. From the model we can manipulate the name property.
Our filter will be called everytime any Action Method in our application is called. We need make sure we only proceeed executing our filter only if the ProductDetails
Action Method on the ProductController is called. We can do that by inspecting the ResultExecutingContext
that gets passed to our OnResultExecuting
method.
public override void OnResultExecuting(ResultExecutingContext context)
{
if (!(context.ActionDescriptor is ControllerActionDescriptor actionDescriptor)) return;
if (actionDescriptor.ControllerTypeInfo != typeof(ProductController) ||
actionDescriptor.ActionName != "ProductDetails" ||
context.HttpContext.Request.Method != "GET")
{
return;
}
var result = context.Result as ViewResult;
if (result == null) return;
var model = result.Model as ProductDetailsModel;
if (model == null) return;
model.Name += " - This is magic from a Custom Action Filter";
}
if (!(context.ActionContext.ActionDescriptor is ControllerActionDescriptor actionDescriptor)) return;
if this code look unfamiliar it is the pattern matching feature in C# 7. It is just a shorter way of typing
var actionDescriptor = context.ActionContext.ActionDescriptor as ControllerActionDescriptor;
if (actionDescriptor == null) return;
From the action descriptor we can get the controller currently called by actionDescriptor.ControllerTypeInfo
. If this is the ProductController
we know we are in the right controller
actionDescriptor.ControllerTypeInfo == typeof(ProductController)
Now we need the check if it is the ProductDetails
method that is called by looking at the ActionName
property
actionDescriptor.ActionName == "ProductDetails"
Optionally (in this case) we can also check that the current request is of the desired HTTP method (GET
). Since the ProductDetails
method only exists for GET
requests this check is not needed. But it's common that an Action Method exists for multiple HTTP methods, and this is how to distinguish them
context.HttpContext.Request.Method == "GET"
We can easily add the filter to the ASP.NET filter pipeline by creating a class that inherits nopCommmerce INopStartup interfaace
public class NopStartup : INopStartup
{
public void ConfigureServices(IServiceCollection services, IConfigurationRoot configuration)
{
services.Configure<MvcOptions>(options =>
{
options.Filters.Add<ProductDetailsActionFilter>();
});
}
public void Configure(IApplicationBuilder application)
{
}
public int Order => 0;
}
That is it. If we run the application and navigates to a product, the name should have been manipulated like this:
As mentioned earlier this was just a very simple example of a custom action filter, from here you can do all kinds of things without having to touch the core of nopCommerce which will make future upgraded much easier.
Download complete code from this Gist: https://gist.github.com/martingust/d5cc3204ba505ae6472335ff32dbc8eb
"
Great tutorial...
Thanks for that - really useful!
thank you!! great
Thank you.
Thank you thank you. well explained steps
Thank you for posting a detailed blog that clearly describes how to create a Custom Action Filter and provides a link to the code for future upgrades.
Cool
Thank you
Guest
Great tutorial. Was of great assistance to me. Thank you!