Click here to Skip to main content
14,174,399 members
Click here to Skip to main content
Add your own
alternative version

Stats

5.8K views
1 bookmarked
Posted 10 Nov 2016
Licenced CPOL

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

, 10 Nov 2016
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sourav Kayal
Software Developer DELL International
India India
I am software developer from INDIA. Beside my day to day development work, i like to learn new technologies to update myself. I am passionate blogger and author in various technical community including dotnetfunda.com , c-sharpcorner.com and codeproject. My area of interest is modern web technology in Microsoft stack. Visit to my personal blog here.

http://ctrlcvprogrammer.blogspot.in/

You may also be interested in...

Pro

Comments and Discussions

 
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web03 | 2.8.190524.3 | Last Updated 10 Nov 2016
Article Copyright 2016 by Sourav Kayal
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid