如果你不想要任何背景或我需要这个的例子,那么请跳到底部的问题!
为了保持整洁,我最初在没有JavaScript的情况下构建了我的应用程序。我现在正试图在它的顶部添加一层不引人注目的JavaScript。
本着MVC的精神,我利用了RedirectToAction()
之类的简单路由和重新路由功能。
假设我有以下网址启动注册过程:
假设注册过程分两步:
http://www.mysite.com/signup/1
http://www.mysite.com/signup/2
假设我想要,如果启用了JavaScript,则注册表单会出现在像ThickBox这样的对话框中。
如果用户在步骤2退出注册过程,但稍后点击“注册”按钮,我想要此网址:
要执行某些业务逻辑,请检查会话。如果他们在中途离开之前的注册工作,那么我想提示他们恢复或重新开始。
我最终可能会遇到以下方法:
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 中的逻辑是:
然而,这失败了,因为一旦我从SignUp重定向,IsAjaxRequest()
就变为假。
显然,有很简单的方法可以解决这个特定的重定向问题,但我希望全局保持对Ajax请求的了解,以便在我的网站上解决此问题。
问题:
ASP.NET MVC是非常非常可扩展的。
是否可以拦截对RedirectToAction
的调用并在参数中注入类似“isAjaxRequest”的内容?
OR
还有其他方法可以安全地检测到原始呼叫是AJAX吗?
OR
我是否以完全错误的方式解决这个问题?
答案 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标头的值并将其存储在临时数据中。
这更好,因为有两件事。
答案 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 });
}
});