Today when finding answer to this question, i came across Mike's blog post. In this post Mike inherited the filter from ActionFilterAttribute and checked request status in onActionExecuting method.
I was about to paste the link then i decided to investigate the mechanism of other attributes like AcceptVerb, HttpPost etc. It turned out that they inherit from ActionMethodSelectorAttribute which is an abstarct class and implement its abtsract method IsValidForRequest
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class HttpPostAttribute : ActionMethodSelectorAttribute {
private static readonly AcceptVerbsAttribute _innerAttribute = new AcceptVerbsAttribute(HttpVerbs.Post);
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) {
return _innerAttribute.IsValidForRequest(controllerContext, methodInfo);
}
}
Listing 1: Mvc's HttpPostAttribute
After this finding i decided to make a new filter and inherit it from ActionMethodSelectorAttribute so i could implement IsValidForRequest method and it all ended up like
public class AjaxOnlyAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
return controllerContext.RequestContext.HttpContext.Request.IsAjaxRequest();
}
}
Listing 2: AjaxOnlyAttribute
The end result of Mike's AjaxOnlyAttribute and this one is same but what is the result of using either approach in mvc request pipeline. An excerpt from InvokeAction method of ControllerActionInvoker class could help us here.
ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
if (actionDescriptor != null) {
FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);
try {
AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
if (authContext.Result != null) {
// the auth filter signaled that we should let it short-circuit the request
InvokeActionResult(controllerContext, authContext.Result);
}
else {
if (controllerContext.Controller.ValidateRequest) {
ValidateRequest(controllerContext);
}
IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
}
}
Listing 3: Excerpt from InvokeAction method
on line 2 of Listing 3 FindAction method of ControllerActionInvoker class is called. This method through series of method calls, calls FindAction method of ActionMethodSelector class. FindAction method in turn runs all the ActionMethodSelecorAttributes on the action method to find out if method is valid for this request.
It necessarily means that if IsValidForRequest method returns false for any action method it will not be processed further in the request and none of the filters applied to this action method will be executed.
If AjaxOnly (or any other filter for that matter) is created by inheriting from ActionFilter, AuthorizeFilter, ExceptionFilter or ResultFilter attributes it will be executed later in the request pipeline. This can be observed from code in Listing 3. Information of these filters is acquired on line 4 and until this point all ActionMethodSelector attributes would be executed.
In the end it is probably safe to state that if AjaxOnlyAttribute inherits from ActionMethodSelectorAttribute mvc will behave as if action method does not exist in case of non-ajax calls. Conversely, if AjaxOnly attribute is implemented by inheriting from ActionFilterAttribute mvc will not allow the action method to execute in case of non-ajax calls. Apparently, there seems to be no consequences of using either approach but i personally feel that this logic belongs to ActionMethodSelectorAttribute