有没有办法在RedirectToAction中维护IsAjaxRequest()?

时间:2009-08-18 14:35:45

标签: asp.net-mvc ajax url-routing

如果你不想要任何背景或我需要这个的例子,那么请跳到底部的问题

为了保持整洁,我最初在没有JavaScript的情况下构建了我的应用程序。我现在正试图在它的顶部添加一层不引人注目的JavaScript。

本着MVC的精神,我利用了RedirectToAction()之类的简单路由和重新路由功能。

假设我有以下网址启动注册过程:

  

http://www.mysite.com/signup

假设注册过程分两步:

  

http://www.mysite.com/signup/1
  http://www.mysite.com/signup/2

假设我想要,如果启用了JavaScript,则注册表单会出现在像ThickBox这样的对话框中。

如果用户在步骤2退出注册过程,但稍后点击“注册”按钮,我想要此网址:

  

http://www.mysite.com/signup

要执行某些业务逻辑,请检查会话。如果他们在中途离开之前的注册工作,那么我想提示他们恢复或重新开始。

我最终可能会遇到以下方法:

public ActionResult SignUp(int? step)
{
    if(!step.HasValue)
    {
        if((bool)Session["SignUpInProgress"] == true)
        {
            return RedirectToAction("WouldYouLikeToResume");
        }
        else
        {
            step = 1;
        }
    }
    ...
}

public ActionResult WouldYouLikeToResume()
{
    if(Request.IsAjaxRequest())
    {
        return View("WouldYouLikeToResumeControl");
    }
    return View();
}

WouldYouLikeToResume 中的逻辑是:

  • 如果是AJAX请求,则仅返回用户控件或“部分”,以便模态弹出框不包含母版页。
  • 否则返回普通视图

然而,这失败了,因为一旦我从SignUp重定向,IsAjaxRequest()就变为假。

显然,有很简单的方法可以解决这个特定的重定向问题,但我希望全局保持对Ajax请求的了解,以便在我的网站上解决此问题。

问题:

ASP.NET MVC是非常非常可扩展的。

是否可以拦截对RedirectToAction的调用并在参数中注入类似“isAjaxRequest”的内容?

OR

还有其他方法可以安全地检测到原始呼叫是AJAX吗?

OR

我是否以完全错误的方式解决这个问题?

6 个答案:

答案 0 :(得分:5)

根据@joshcomley的要求,使用TempData方法的自动答案:

这假设你有一个BaseController,你的控制器继承它。

public class AjaxianController : /*Base?*/Controller
{
    private const string AjaxTempKey = "__isAjax";


    public bool IsAjax
    {
        get { return Request.IsAjaxRequest() || (TempData.ContainsKey(AjaxTempKey)); }
    }


    protected override RedirectResult Redirect(string url)
    {
        ensureAjaxFlag();
        return base.Redirect(url);
    }

    protected override RedirectToRouteResult RedirectToAction(string actionName, string controllerName, System.Web.Routing.RouteValueDictionary routeValues)
    {
        ensureAjaxFlag();
        return base.RedirectToAction(actionName, controllerName, routeValues);
    }

    protected override RedirectToRouteResult RedirectToRoute(string routeName, System.Web.Routing.RouteValueDictionary routeValues)
    {
        ensureAjaxFlag();
        return base.RedirectToRoute(routeName, routeValues);
    }


    private void ensureAjaxFlag()
    {
        if (IsAjax)
            TempData[AjaxTempKey] = true;

        else if (TempData.ContainsKey(AjaxTempKey))
            TempData.Remove(AjaxTempKey);
    }
}

要使用此功能,请使控制器继承AjaxianController并使用“IsAjax”属性而不是IsAjaxRequest扩展方法,然后控制器上的所有重定向都将自动维护ajax-or-not标志。

...

虽然没有测试过,所以要警惕错误: - )

...

另一种不需要使用我能想到的状态的通用方法可能需要您修改路线。

具体而言,您需要能够在路线中添加通用词,即

{controller}/{action}/{format}.{ajax}.html

然后不是检查TempData,而是检查RouteData [“ajax”]。

在扩展点上,不是设置TempData键,而是将“ajax”添加到RouteData中。

有关详细信息,请参阅this question on multiple format route

答案 1 :(得分:4)

这对我有用。 请注意,这不需要任何会话状态,这是一个潜在的并发问题:

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);

        if (this.Request.IsAjaxRequest)
        {
            if (filterContext.Result is RedirectToRouteResult)
            {
                RedirectToRouteResult rrr = (RedirectToRouteResult)filterContext.Result;
                rrr.RouteValues.Add("X-Requested-With",Request.Params["X-Requested-With"]);
            }
        }
    }

}

答案 2 :(得分:1)

也许你可以在重定向之前在TempData属性中添加一个AjaxRedirected键?

答案 3 :(得分:0)

传输状态的一种方法是添加额外的路由参数,即

public ActionResult WouldYouLikeToResume(bool isAjax)
{
    if(isAjax || Request.IsAjaxRequest())
    {
        return PartialView("WouldYouLikeToResumeControl");
    }
    return View();
}

然后在注册方法中:

return RedirectToAction("WouldYouLikeToResume", new { isAjax = Request.IsAjaxRequest() });

// Don't forget to also set the "ajax" parameter to false in your RouteTable
// So normal views is not considered Ajax

然后在您的RouteTable中,将“ajax”参数默认为false。

或者另一种方法是覆盖BaseController中的扩展点(你有一个,对吗?)总是传递IsAjaxRequest状态。

...

TempData方法也是有效的,但在做任何看起来像RESTful的事情时,我对状态有点过敏: - )

虽然没有测试/美化路线,但你应该明白这一点。

答案 4 :(得分:0)

我只想提供我认为比目前接受的答案更好的答案。

使用此:

public class BaseController : Controller
{
    private string _headerValue = "X-Requested-With";

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var ajaxHeader = TempData[_headerValue] as string;
        if (!Request.IsAjaxRequest() && ajaxHeader != null)
            Request.Headers.Add(_headerValue, ajaxHeader);
    }

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);
        if (Request.IsAjaxRequest() && IsRedirectResult(filterContext.Result))
            TempData[_headerValue] = Request.Headers[_headerValue];
    }

    private bool IsRedirectResult(ActionResult result)
    {
        return result.GetType().Name.ToLower().Contains("redirect");
    }
}

然后让所有控制器继承此。

它的作用:

在执行某项操作之前,会检查TempData中是否有值。如果有,则手动将其值添加到Request对象的标题集合中。

执行某个操作后,会检查结果是否为重定向。如果它是重定向,并且在执行此操作之前请求是Ajax请求,则它会读取已发送的自定义ajax标头的值并将其存储在临时数据中。

这更好,因为有两件事。

  1. 它更短更清洁。
  2. 在读取临时数据后,它将请求标头添加到Request对象。这允许Request.IsAjaxRequest()正常工作。没有调用自定义IsAjax属性。
  3. 感谢:queen3包含此解决方案的question。我确实修改了它以清理它,但最初是他的解决方案。

答案 5 :(得分:0)

问题出在Client-Cache中。 要解决此问题,只需添加一个cachebreaker即可 比如“?_ = XXXXXX”到302响应中的位置网址。

这是我的工作过滤器。在GlobalFilter Collection中注册它。 我将位置标头添加到重定向响应中,因此客户端脚本可以在ajax调用中获取目标URL。 (对于Google-Analytics)

public class PNetAjaxFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var request = filterContext.HttpContext.Request;

        if(request.QueryString["_"] == "ajax")
        {
            filterContext.HttpContext.Request.Headers["X-Requested-With"] = "XMLHttpRequest";
            request.QueryString.Remove("_");
        }
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    //public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        var context = filterContext.HttpContext;

        if (!context.Request.IsAjaxRequest())
            return;

        var request = context.Request;

        String noCacheQuery = String.Empty;

        if (request.HttpMethod == "GET")
        {
            noCacheQuery = request.QueryString["_"];
        }
        else if (context.Response.IsRequestBeingRedirected)
        {
            var pragma = request.Headers["Pragma"] ?? String.Empty;
            if (pragma.StartsWith("no-cache", StringComparison.OrdinalIgnoreCase))
            {
                noCacheQuery = DateTime.Now.ToUnixTimestamp().ToString();
            }
            else
            {
                //mode switch: one spezial cache For AjaxResponse
                noCacheQuery = "ajax";
            }
        }

        if (!String.IsNullOrEmpty(noCacheQuery))
        {
            if (context.Response.IsRequestBeingRedirected)
            {
                var location = context.Response.RedirectLocation;

                if (location.Contains('?'))
                    location += "&_=" + noCacheQuery;
                else
                    location += "?_=" + noCacheQuery;

                context.Response.RedirectLocation = location;
            }
            else
            {
                var url = new UriBuilder(request.Url);

                if (url.Port == 80 && url.Scheme == Uri.UriSchemeHttp)
                    url.Port = -1;
                else if(url.Port == 443 && url.Scheme == Uri.UriSchemeHttps)
                    url.Port = -1;

                if(!String.IsNullOrEmpty(url.Query))
                    url.Query = String.Join("&", url.Query.Substring(1).Split('&').Where(s => !s.StartsWith("_=")));

                context.Response.AppendHeader("Location", url.ToString());
            }
        }
    }
}

这里是jQuery:

         var $form = $("form");
         var action = $form.attr("action");
         var $item = $("body");

         $.ajax({
                type: "POST",
                url: action,
                data: $form.serialize(),
                success: function (data, status, xhr) {
                    $item.html(data);

                    var source = xhr.getResponseHeader('Location');
                    if (source == null) //if no redirect
                        source = action;

                    $(document).trigger("partialLoaded", { source: source, item: $item });
                }
            });