【问题标题】:Is there a way to maintain IsAjaxRequest() across RedirectToAction?有没有办法跨 RedirectToAction 维护 IsAjaxRequest()?
【发布时间】:2009-08-18 14:35:44
【问题描述】:

如果您不想要任何上下文或我为什么需要此示例的示例,请跳至底部的 问题

为了保持整洁,我最初构建我的应用程序时没有使用 JavaScript。我现在正尝试在其顶部添加一层不显眼的 JavaScript。

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

假设我有以下 URL 来启动注册过程:

http://www.mysite.com/signup

假设注册过程有两个步骤:

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

假设我希望,如果启用了 JavaScript,则注册表单会出现在诸如厚框之类的对话框中。

如果用户在第 2 步离开注册过程,但后来点击了“注册”按钮,我想要这个 URL:

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() 就会变为 false。

显然有一些非常简单的方法可以修复这个特定的重定向,但我想在全球范围内保持对 Ajax 请求的了解,以便在我的网站上解决这个问题。

问题:

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

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

是否有其他方法可以安全地检测到发起呼叫是 AJAX 呼叫?

我是不是走错了路?

【问题讨论】:

  • 这似乎在 MVC 中已经修复很久了。在 v5 中,一切都按预期工作,唯一的问题是您可能需要将 [OutputCache(Duration = 0)] 添加到相关方法中,否则浏览器可能会重用以前的“完整”内容而不是发出 ajax 请求。

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


【解决方案1】:

根据@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

然后,您将检查 RouteData["ajax"],而不是检查 TempData。

在扩展点上,您无需设置 TempData 键,而是将“ajax”添加到您的 RouteData。

请参阅this question on multiple format route 了解更多信息。

【讨论】:

  • @chakrit - 谢谢,实际上我想到了这样的解决方案,我在重写的 RedirectToAction 方法中注入了新的 isAjax 参数。但是后来我(最初)放弃了它,因为当你执行 RedirectToAction("Home", new { id = 1 }) 之类的操作时,MVC 使用反射来确定路由值中应该包含什么,而我没有花式将属性注入对象。但当然,这只是归结为采用 RouteValueCollection 的 RedirectToAction(),此时注入新值应该很容易。我只是一个彻头彻尾的白痴!谢谢!
  • @chakrit(和其他任何人)-我已经走这条路了,但没有使用我认为更清洁的 TempData。我可能会在接下来的几周内发布关于最终解决方案的博客,并在此处放置博客链接:) 但出于所有意图和目的,这就是我一直在寻找的答案!
  • @joshcomley 完成后别忘了与我们分享 :-)
  • 我发现了错误 ))) 当你第一次重定向 throw ajax 时它工作正常但是如果你打开没有 ajax 的相同请求 url(过去 url 到浏览器)它会像 ajax 一样打开所以你应该覆盖 OnActionExecuting 方法并添加 ajax 检查来解决它...请更新答案)
  • OnActionExecuting 不需要它无法帮助它需要将 TempData 更改为 ViewData
【解决方案2】:

这对我有用。 请注意,这不需要任何可能存在并发问题的会话状态:

    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"]);
            }
        }
    }

}

【讨论】:

  • 不错的@crazybert - 这是我所见过的这个问题的最佳解决方案。
  • 我非常喜欢这个。一个问题:Request.Params 应该是 Request.Headers(或者如果你保留参数,键应该是“HTTP_X_REQUESTED_WITH”)?
  • @jamesfm,这在我的项目中按原样工作了几个月,所以它肯定可以按上面列出的那样工作,但也许还有更优雅的标题方式。
【解决方案3】:

也许您可以在进行重定向之前在 TempData 属性中添加 AjaxRedirected 键?

【讨论】:

  • 但是我该如何全局做到这一点?而不是为这个特定的例子手动编码?
  • 是的,tempdata 就是这样做的方法。请注意,虽然 tempdata 使用会话状态。如果你有一个网络农场,你需要相应地处理那个状态
  • @josh:你可以创建一个扩展方法。此扩展方法将检查 IsAjaxRequest() 是否为真或 TempData[IsAjaxRequest] 是否为真
【解决方案4】:

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

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

然后在Signup方法中:

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 的事情时,我对状态有点过敏 :-)

虽然还没有测试/美化路线,但你应该明白了。

【讨论】:

  • 就像我在问题中所说的那样,对于这个实例有很多简单的方法可以手动解决它,但是全局呢?事实上,我的确切问题是问是否可以自动化您的答案!
  • @joshcomley 一会儿...想出一个自动答案:-)
  • 我同意你所说的关于 RESTful 状态的说法,这就是为什么我犹豫要不要走 TempData 路线。是的,我确实有一个“BaseController”。 “压倒一切的扩展点”听起来像是我追求的动物……你能详细说明一下吗? :)
【解决方案5】:

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

使用这个:

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 属性。

感谢:queen3 他的question 包含此解决方案。我确实对其进行了修改以对其进行清理,但最初是他的解决方案。

【讨论】:

【解决方案6】:

问题出在客户端缓存中。 要克服这个问题,只需添加一个 cachebreaker 像 302 响应中的“?_=XXXXXX”到位置网址。

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

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 });
                }
            });

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-28
    • 2021-12-02
    • 1970-01-01
    • 1970-01-01
    • 2020-12-10
    • 1970-01-01
    • 2013-03-01
    • 2020-04-02
    相关资源
    最近更新 更多