Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combining AjaxOnlyAttribute and ChildActionOnlyAttribute into one action filter

I want to be able to mark an action on controller to be called both from ajax calls and via RenderAction. The problem is that both this attributes derive or implement different abstractions. One way out is the next:

[AjaxOnly]
PartialViewResult GetViewAjax(int foo) { return GetView(foo); }
[ChildActionOnly]
PartialViewResult GetView(int foo) { ... }

But this is not neat at all.


The AjaxOnly attribute I am talking about is:

public sealed class AjaxOnlyAttribute : ActionFilterAttribute
{
    #region Public members

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext == null)
            throw new ArgumentNullException("filterContext");
        if (filterContext.HttpContext.Request.Headers["X-Requested-With"] != "XMLHttpRequest")
            filterContext.Result = new HttpNotFoundResult();
    }

    #endregion
}

This method is taken from MVC3 futures. An important comment why the condition is not filterContext.HttpContext.Request.IsAjaxRequest() was made by dev team and says the following:

// Dev10 #939671 - If this attribute is going to say AJAX *only*, then we need to check the header
// specifically, as otherwise clients can modify the form or query string to contain the name/value
// pair we're looking for.
like image 700
Yurii Hohan Avatar asked Mar 02 '12 13:03

Yurii Hohan


2 Answers

This doesn't make any sense. Those 2 attributes are mutually exclusive. If an action is marked with [ChildActionOnly] it can never be directly accessed by the client using an HTTP request (be it synchronous or asynchronous). So if you want an action to ever be accessible using AJAX, you should never decorate it with the [ChildActionOnly] attribute.

I don't know what this [AjaxOnly] attribute is and where it comes from but depending on how it is implemented you might need to tweak it in order to allow child action requests if it relies only on the Request.IsAjaxRequest() method. For example if it is something like this:

public class AjaxOnlyAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!filterContext.HttpContext.Request.IsAjaxRequest())
        {
            filterContext.Result = new HttpNotFoundResult();
        }
    }
}

you might want to tweak it like this:

public class AjaxOrChildActionOnlyAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!filterContext.HttpContext.Request.IsAjaxRequest() && 
            !filterContext.IsChildAction
        )
        {
            filterContext.Result = new HttpNotFoundResult();
        }
    }
}
like image 54
Darin Dimitrov Avatar answered Sep 21 '22 11:09

Darin Dimitrov


Inspired on Darin's answer and ChildActionOnlyAttribute's source code, this is the solution I have come up with, which I think it's a tiny bit better:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AjaxOrChildAttribute : ActionMethodSelectorAttribute
{
    public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
    {
        return controllerContext.IsChildAction || controllerContext.RequestContext.HttpContext.Request.IsAjaxRequest();
    }
}

This way, the validation is done before even trying to execute, and the error you get if you type in the url is the exact same one as trying any invalid url.

like image 22
Andrew Avatar answered Sep 20 '22 11:09

Andrew