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)
{
if (!controllerContext.HttpContext.Request.IsAjaxRequest())
throw new Exception("Accept AJAX call only");
if (!controllerContext.HttpContext.Request.IsSecureConnection)
throw new Exception("Support only HTTPS");
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.
public class ApiControllerActionSelector : IHttpActionSelector
{
public ApiControllerActionSelector();
public virtual ILookup<string, HttpActionDescriptor>
GetActionMapping(HttpControllerDescriptor controllerDescriptor);
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 }
);
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.