Dan
Dan

Reputation: 5637

ASP.NET MVC: Register action filter without modifying controller

I'm working with nopCommerce and I need to add in my only Action Filter, however, I don't want to modify the core controllers to avoid my code being overwritten when a new update is released.

I've setup my Action Filter:

public class ProductActionFilterAttribute : ActionFilterAttribute
{

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Result is ViewResult)
        {
            ...
        }
        base.OnActionExecuted(filterContext);
    }

}

If I were to modify the controller, I could just add [ProductActionFilter] to the action I want it assigned to.

Is there a way I can register my custom Action Filter to a specific action without modifying the controller?

Upvotes: 23

Views: 27339

Answers (5)

rfmodulator
rfmodulator

Reputation: 3738

If you want your filter to be registered for every action (or it is otherwise OK to do so), then MVC 3 allows you to apply Global action filters. Of course, this requires that nopCommerce is built on MVC 3, which I believe the newest version is?

Upvotes: 2

zekeriya kocairi
zekeriya kocairi

Reputation: 346

This approach works for NopCommerce 4.10

This code will redirect "/Register" requests with "GET" Method to "YourCustomAction" action inside "YourCustomController".

Step 1: implement INopStartup

 public class NopStartup : INopStartup
 {
        public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
        {
            services.Configure<MvcOptions>(config =>
            {
                config.Filters.Add<YourCustomActionFilter>();
            });
        }

        public void Configure(IApplicationBuilder application)
        {

        }

        public int Order => 0;
    }

Step 2 :

public class YourCustomActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!(context.ActionDescriptor is ControllerActionDescriptor actionDescriptor)) return;

        if (actionDescriptor.ControllerTypeInfo == typeof(CustomerController) &&
            actionDescriptor.ActionName == "Register" &&
            context.HttpContext.Request.Method == "GET")
        {
                    string controllerName = nameof(YourCustomController).Replace("Controller", "");
                    string actionName = nameof(YourCustomController.YourCustomAction);
                    var values = new RouteValueDictionary(new
                    {
                        action = actionName,
                        controller = controllerName
                    });
                    context.Result = new RedirectToRouteResult(values);
        }
    }
}

With this approach, you can cut down the registration process and add some extra check/process then you can go on the registration process.

Upvotes: 1

sashaeve
sashaeve

Reputation: 9617

I think global filters is what you need.

Once you created the filter register it in the global.asax:

protected void Application_Start() {

    AreaRegistration.RegisterAllAreas();

    // Register global filter
    GlobalFilters.Filters.Add(new MyActionFilterAttribute());

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes); 
}

Add custom validation logic to filter if you want to apply it not to all actions.

Upvotes: 33

ulty4life
ulty4life

Reputation: 3012

In NopCommerce 3.5 (the latest as of this answer, and newer than the question date), the best way I've found to add a global action filter is by creating a plugin with an IStartupTask implementation in it. This method completely avoids altering any NopCommerce core files.

The NopCommerce Application_Start event initializes the EngineContext, which creates the NopEngine instance. The NopEngine initialization finds all IStartupTask implementations, and executes them in their specified order. So an IStartupTask is the place to do anything that needs to happen on application start.

Sample code below:

public class Plugin : BasePlugin
{
    public Plugin()
    {
    }

    /// <summary>
    /// Check to see if this plugin is installed
    /// </summary>
    public static bool IsInstalled(ITypeFinder typeFinder)
    {
        IEnumerable<Type> types = typeFinder.FindClassesOfType<IPluginFinder>(true);

        if (types.Count() == 1)
        {
            IPluginFinder plugins = Activator.CreateInstance(types.First()) as IPluginFinder;
            PluginDescriptor descriptor = plugins.GetPluginDescriptorBySystemName("MyPluginName");

            if (descriptor != null && descriptor.Installed)
            {
                return true;
            }
        }

        return false;
    }
}

/// <summary>
/// Redirects to the 404 page if criteria not met
/// </summary>
public class FluffyTextureRequiredAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (Kitten.Texture != Textures.Fluffy)
        {
            var routeValues = new RouteValueDictionary();
            routeValues.Add("controller", "Common");
            routeValues.Add("action", "PageNotFound");

            filterContext.Result = new RedirectToRouteResult(routeValues);
        }
    }
}

/// <summary>
/// Does application start event stuff for the plugin, e.g. registering
/// global action filters
/// </summary>
public class StartupTask : IStartupTask
{
    private ITypeFinder _typeFinder;

    public StartupTask()
    {
        //IStartupTask objects are created via Activator.CreateInstance with a parameterless constructor call, so dependencies must be manually resolved.
        _typeFinder = EngineContext.Current.Resolve<ITypeFinder>();
    }

    public void Execute()
    {
        // only execute if plugin is installed
        if (Plugin.IsInstalled(_typeFinder))
        {
            // GlobalFilters is in System.Web.Mvc
            GlobalFilters.Filters.Add(new FluffyTextureRequiredAttribute());
        }
    }

    public int Order
    {
        get { return int.MaxValue; }
    }
}

Upvotes: 2

Carlos Martinez T
Carlos Martinez T

Reputation: 6528

What about creating a partial class. As of version 2.60 all controllers are partials:

public partial class CatalogController : BaseNopController

You can put the filter to the class and then query the action name.

Upvotes: 0

Related Questions