如何让 ASP.NET MVC Ajax 响应重定向到新页面而不是将视图插入 UpdateTargetId?

我正在使用 Ajax。BeginForm 创建一个表单,该表单将对某个控制器操作执行 Ajax 回发,然后如果操作成功,用户应该被重定向到另一个页面(如果操作失败,则使用 AjaxOptions UpdateTargetId 显示状态消息)。

using (Ajax.BeginForm("Delete", null,
new { userId = Model.UserId },
new AjaxOptions { UpdateTargetId = "UserForm", LoadingElementId = "DeletingDiv" },
new { name = "DeleteForm", id = "DeleteForm" }))
{
[HTML DELETE BUTTON]
}

如果删除成功,我将返回一个重定向结果:

[Authorize]
public ActionResult Delete(Int32 UserId)
{
UserRepository.DeleteUser(UserId);
return Redirect(Url.Action("Index", "Home"));
}

但是家庭控制器索引视图正被加载到 UpdateTargetId 中,因此我得到了一个页面中的页面。我在想两件事:

  1. 要么是我在设计这个 错误的,应该处理这种类型的 动作不同(不使用 ajax)。
  2. 而不是返回重定向 结果,返回一个具有 其中的 javascript 实现了 在客户端重定向。

有人对第一条有意见吗?或者如果 # 2是一个好的解决方案,那么“重定向 javascript 视图”会是什么样子呢?

154267 次浏览

The behavior you're trying to produce is not really best done using AJAX. AJAX would be best used if you wanted to only update a portion of the page, not completely redirect to some other page. That defeats the whole purpose of AJAX really.

I would suggest to just not use AJAX with the behavior you're describing.

Alternatively, you could try using jquery Ajax, which would submit the request and then you specify a callback when the request completes. In the callback you could determine if it failed or succeeded, and redirect to another page on success. I've found jquery Ajax to be much easier to use, especially since I'm already using the library for other things anyway.

You can find documentation about jquery ajax here, but the syntax is as follows:

jQuery.ajax( options )


jQuery.get( url, data, callback, type)


jQuery.getJSON( url, data, callback )


jQuery.getScript( url, callback )


jQuery.post( url, data, callback, type)

While not elegant, works for me in certain situations.

Controller

if (RedirectToPage)
return PartialView("JavascriptRedirect", new JavascriptRedirectModel("http://www.google.com"));
else
... return regular ajax partialview

Model

    public JavascriptRedirectModel(string location)
{
Location = location;
}


public string Location { get; set; }

/Views/Shared/JavascriptRedirect.cshtml

@model Models.Shared.JavascriptRedirectModel


<script type="text/javascript">
window.location = '@Model.Location';
</script>

You can use JavascriptResult to achieve this.

To redirect:

return JavaScript("window.location = 'http://www.google.co.uk'");

To reload the current page:

return JavaScript("location.reload(true)");

Seems the simplest option.

I m not satisfied by the best answer by the Joseph, instead of fixing the correct problem, he told that this is wrong use case. In fact there are many places for example if you are converting an old codebase to ajaxified code and there you NEED it, then you NEED it. In programming there is no excuse because its not only you who is coding its all bad and good developers and you have to work side by side. So if I don't code redirection in ajax my fellow developer can force me to have a solution for it. Just like I like to use all AMD patterned sites or mvc4, and my company can keep me away from it for a year.

So let's talk on the solution now.

I have done hell heck of ajax request and response handling and the simplest way I found out was to send status codes to the client and have one standard javascript function to understand those codes. If i simply send for example code 13 it might meant a redirect.

So a json response like { statusCode: 13, messsage: '/home/logged-in' } of course there are tons of variations proposed like { status: 'success', code: 13, url: '/home/logged-in', message: 'You are logged in now' }

etc , so up to your own choice of standard messages

Usually I Inherit from base Controller class and put my choice of standard responses like this

public JsonResult JsonDataResult(object data, string optionalMessage = "")
{
return Json(new { data = data, status = "success", message = optionalMessage }, JsonRequestBehavior.AllowGet);
}


public JsonResult JsonSuccessResult(string message)
{
return Json(new { data = "", status = "success", message = message }, JsonRequestBehavior.AllowGet);
}


public JsonResult JsonErrorResult(string message)
{
return Json(new { data = "", status = "error", message = message }, JsonRequestBehavior.AllowGet);
}


public JsonResult JsonRawResult(object data)
{
return Json(data, JsonRequestBehavior.AllowGet);
}

About using $.ajax intead of Ajax.BeginForm I would love to use Jquery ajax and I do, but again its not me in the whole world to make decisions I have an application full of Ajax.BeginForm and of course I didnt do that. But i have to live with it.

So There is a success callback in begin form too, you don't need to use jquery ajax to use callbacks Something about it here Ajax.BeginForm, Calls Action, Returns JSON, How do I access JSON object in my OnSuccess JS Function?

Thanks

If you're redirect from the JavaScript class

same view - diferent controller

<strike>window.location.href = `'Home'`;</strike>

is not same view

<strike>window.location.href = `'Index/Home'`;</strike>

You can return a JSON with the URL and change the window.location using JavaScript at client side. I prefer this way than calling a JavaScript function from the server, which I think that it's breaking the separation of concerns.

Server side:

return Json(new {result = "Redirect", url = Url.Action("ActionName", "ControllerName")});

Client side:

if (response.result == 'Redirect')
window.location = response.url;

Of course you can add more logic because there could be an error on the server side and in that case the result property could indicate this situation and avoid the redirection.

I needed to do this because I have an ajax login form. When users login successfully I redirect to a new page and end the previous request because the other page handles redirecting back to the relying party (because it's a STS SSO System).

However, I also wanted it to work with javascript disabled, being the central login hop and all, so I came up with this,

    public static string EnsureUrlEndsWithSlash(string url)
{
if (string.IsNullOrEmpty(url))
throw new ArgumentNullException("url");
if (!url.EndsWith("/"))
return string.Concat(url, "/");
return url;
}


public static string GetQueryStringFromArray(KeyValuePair<string, string>[] values)
{
Dictionary<string, string> dValues = new Dictionary<string,string>();
foreach(var pair in values)
dValues.Add(pair.Key, pair.Value);
var array = (from key in dValues.Keys select string.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(dValues[key]))).ToArray();
return "?" + string.Join("&", array);
}


public static void RedirectTo(this HttpRequestBase request, string url, params KeyValuePair<string, string>[] queryParameters)
{
string redirectUrl = string.Concat(EnsureUrlEndsWithSlash(url), GetQueryStringFromArray(queryParameters));
if (request.IsAjaxRequest())
HttpContext.Current.Response.Write(string.Format("<script type=\"text/javascript\">window.location='{0}';</script>", redirectUrl));
else
HttpContext.Current.Response.Redirect(redirectUrl, true);


}

Using JavaScript will definitely do the job.

You can also use Content if this is more your style.

Example:

MVC Controller

[HttpPost]
public ActionResult AjaxMethod()
{
return Content(@"http://www.google.co.uk");
}

Javascript

$.ajax({
type: 'POST',
url: '/AjaxMethod',
success: function (redirect) {
window.location = redirect;
}
});

Add a helper class:

public static class Redirector {
public static void RedirectTo(this Controller ct, string action) {
UrlHelper urlHelper = new UrlHelper(ct.ControllerContext.RequestContext);


ct.Response.Headers.Add("AjaxRedirectURL", urlHelper.Action(action));
}


public static void RedirectTo(this Controller ct, string action, string controller) {
UrlHelper urlHelper = new UrlHelper(ct.ControllerContext.RequestContext);


ct.Response.Headers.Add("AjaxRedirectURL", urlHelper.Action(action, controller));
}


public static void RedirectTo(this Controller ct, string action, string controller, object routeValues) {
UrlHelper urlHelper = new UrlHelper(ct.ControllerContext.RequestContext);


ct.Response.Headers.Add("AjaxRedirectURL", urlHelper.Action(action, controller, routeValues));
}
}

Then call in your action:

this.RedirectTo("Index", "Cement");

Add javascript code to any global javascript included file or layout file to intercept all ajax requests:

<script type="text/javascript">
$(function() {
$(document).ajaxComplete(function (event, xhr, settings) {
var urlHeader = xhr.getResponseHeader('AjaxRedirectURL');


if (urlHeader != null && urlHeader !== undefined) {
window.location = xhr.getResponseHeader('AjaxRedirectURL');
}
});
});
</script>

As ben foster says you can return the Javascripts and it will redirect you to the desired page.

To load page in the current page:

return JavaScript("window.location = 'http://www.google.co.uk'");'

To load page in the new tab:

return JavaScript("window.open('http://www.google.co.uk')");

You can simply write in Ajax Success like below :

 $.ajax({
type: "POST",
url: '@Url.Action("GetUserList", "User")',
data: { id: $("#UID").val() },
success: function (data) {
window.location.href = '@Url.Action("Dashboard", "User")';
},
error: function () {
$("#loader").fadeOut("slow");
}
});

You can simply do some kind of ajax response filter for incomming responses with $.ajaxSetup. If the response contains MVC redirection you can evaluate this expression on JS side. Example code for JS below:

$.ajaxSetup({
dataFilter: function (data, type) {
if (data && typeof data == "string") {
if (data.indexOf('window.location') > -1) {
eval(data);
}
}
return data;
}
});

If data is: "window.location = '/Acount/Login'" above filter will catch that and evaluate to make the redirection.

You can get a non-js-based redirection from an ajax call by putting in one of those meta refresh tags. This here seems to be working: return Content("<meta http-equiv=\"refresh\" content=\"0;URL='" + @Url.Action("Index", "Home") + "'\" />");

Note: I discovered that meta refreshes are auto-disabled by Firefox, rendering this not very useful.

How about this :

public ActionResult GetGrid()
{
string url = "login.html";
return new HttpStatusCodeResult(System.Net.HttpStatusCode.Redirect,url)
}

And then

$(document).ajaxError(function (event, jqxhr, settings, thrownError) {
if (jqxhr.status == 302) {
location.href = jqxhr.statusText;
}
});

Or

error: function (a, b, c) {
if (a.status == 302) {
location.href = a.statusText;
}
}

The accepted answer works well except for the fact that the javascript is briefly displayed in whatever the ajax target element is. To get around this, create a partial view called _Redirect with the following code:

@model string
<script>
window.location = '@Model';
</script>

Then, in the controller replace

return Redirect(Url.Action("Index", "Home"));

with

return PartialView("_Redirect",Url.Action("Index", "Home"));

The effect is the same as the accepted answer, but without the brief artifact in the display. Place the _Redirect.cshtml in the shared folder so it can be used from anywhere.