Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Extensible Point of MVC: Custom Action Selection in MVC and WEB-API

0.00/5 (No votes)
10 Nov 2016 1  
How to create custom action invoker in MVC and WebAPI

Custom Action Selection in MVC

In this section, we will discuss custom action selection in MVC. We know that after selecting the controller, the framework selects the action by calling the IHttpActionSelector.SelectAction method. This method takes an HttpControllerContext and returns an HttpActionDescriptor. The default implementation is provided by the ApiControllerActionSelector class. To select an action, it looks at the following:

  • The HTTP method of the request.
  • The "{action}" placeholder in the route template, if present.
  • The parameters of the actions on the controller.

Now, in need, we can override the default behavior and hook our custom code in between. Let’s try to implement it. First of all, we have to inherit our custom class from "ActionMethodSelectorAttribute" class. The class only has one method called "IsValidForRequest" which is abstract in nature.

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
   public abstract class ActionMethodSelectorAttribute : Attribute
   {
       protected ActionMethodSelectorAttribute();
       public abstract bool IsValidForRequest
                 (ControllerContext controllerContext, MethodInfo methodInfo);
   }

As this method is abstract, we can define it in our custom class. Here is the sample code where we are doing so:

public class ActionDecorationAttribute : ActionMethodSelectorAttribute
    { 
        public override bool IsValidForRequest
           (ControllerContext controllerContext, MethodInfo methodInfo)
        {
            //Check for AJAX request
            if (!controllerContext.HttpContext.Request.IsAjaxRequest())
                throw new Exception("Accept AJAX call only");

            //Check for HTTPS request only
            if (!controllerContext.HttpContext.Request.IsSecureConnection)
                throw new Exception("Support only HTTPS");

            //Check for language
            if (controllerContext.HttpContext.Request.UserLanguages[0] != "en-US")
                throw new Exception("For English user only");

            return true;
        }
    }

Validating a few things from Request Context. The example code is very simple just to help demonstrate this. You can implement your actual logic in a real time problem. Now, we have to attach the custom class to action to inject the code snippet before the execution of the controller.

public class HomeController : Controller
    {
        [ActionDecoration]
        public ActionResult Index()
        {
            return new EmptyResult();
        }
    }

Now, when the request comes for Index action, before execution of Index, it will trigger the Validation method in the ActionDecorationAttribute class.

One question you may have is, "Couldn't we do the same thing in action filter?" Right? OK, but the purpose of the action selector and the action filter is totally different. The action selector’s job is to select action where Action filter is used to execute code before and action executes (in most cases).

Custom Action Invoker in Web API

We know that Web API supports routing based on HTTP verb by default. That means the framework will search a matched action based on request type and then matched with a parameter. To implement a custom action selector in Web API, we have to inherit class from:

"ApiControllerActionSelector" class. Here is the definition of "ApiControllerActionSelector" class. 
//
    // Summary:
    //     Represents a reflection based action selector.
    public class ApiControllerActionSelector : IHttpActionSelector
    {
        //
        // Summary:
        //     Initializes a new instance of the 
        //     System.Web.Http.Controllers.ApiControllerActionSelector class.
        public ApiControllerActionSelector();

        //
        // Summary:
        //     Gets the action mappings for the 
        //     System.Web.Http.Controllers.ApiControllerActionSelector.        
        // Parameters:
        //   controllerDescriptor:
        //     The information that describes a controller.
        //
        // Returns:
        //     The action mappings.
        public virtual ILookup<string, HttpActionDescriptor> 
        GetActionMapping(HttpControllerDescriptor controllerDescriptor);
        //
        // Summary:
        //     Selects an action for the System.Web.Http.Controllers.ApiControllerActionSelector.
        //
        // Parameters:
        //   controllerContext:
        //     The controller context.
        //
        // Returns:
        //     The selected action.
        public virtual HttpActionDescriptor SelectAction(HttpControllerContext controllerContext);
    }

In this example, we have created a very simple class which is inherited from the "ApiControllerActionSelector" class.

public class ApiActionSelector : ApiControllerActionSelector
    {
        public  override HttpActionDescriptor SelectAction( HttpControllerContext context)
        {
            HttpMessageContent requestContent = new HttpMessageContent( context.Request);
           
            var actionMethod = context.ControllerDescriptor.ControllerType
                .GetMethods(BindingFlags.Instance | BindingFlags.Public)
                .FirstOrDefault();

            if (actionMethod != null)
            {
                return new ReflectedHttpActionDescriptor(
                                   context.ControllerDescriptor, actionMethod);
            }

            return base.SelectAction(context);
        }
    }

So we are doing nothing much here. Just wanted to check action name. As the SelectAction method takes HttpControllerContext object as parameter, we will have visibility of every request object from there. In a real time scenario, you could implement some useful logic. Now we have to hook up the code in action. The way to hook is a little different than the MVC one which we have seen at the top section. We have to replace the default action selection mechanism. The good place to set up the code is the WebApiConfig class. Here is sample code to show you how:

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            //Hook up custom action selector
            config.Services.Replace(typeof(IHttpActionSelector), new ApiActionSelector());

        }
    }

Now when we call to the below action:

public class testController : ApiController
    {
        [System.Web.Http.ActionName("action1")]
        public void Get()
        {
            
        }
    }

We see that the execution sequence hits the SelectAction function in the ApiActionSelector class.

Conclusion

In this example, we have learned how to implement a custom action invoker both in MVC and Web API. Please don’t mix action invoker with action filter. The purpose of both are completely different.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here