在 ASP.NET MVC 中重定向和 POST JSON 对象






4.96/5 (13投票s)
本文介绍如何在 ASP.NET MVC 的 action 中 POST JSON 数据并重定向到该页面。
引言
我想尽量简短地陈述问题、可能的解决方案以及我在这里分享的最简洁的解决方案,因此我会尽量做到详尽且直截了当。
问题
我想要做的就是将一个 JSON 对象从一个页面 POST 到另一个页面,并重定向到该页面,我希望这能通过将 JSON 对象作为我的 controller post action 的一个参数来实现。请注意,我们是从一个页面到另一个页面(从一个 controller action 到另一个 controller action)来完成的。
以下是我的目标页面或 controller action
[HttpPost]
public ActionResult SubmitOrder(OrderViewModel vmOrder)
{
//Process the vmOrder if wanted
return View(vmOrder);
}
现在,设想我的源页面或 controller action 有以下 JavaScript 代码片段,我们希望将 vmOrder
POST 到目标页面。
function CollectOrderDetails(){
var order = new Object();
//Fill order data
//Submit the order object to destination page after serializing it
(e.g. ../MyApplication/SubmitOrder)
//====>JSON.stringify(order)
}
可能的解决方案
现在要执行提交(POST
),我有两个选择
- 使用 Ajax:我可以直接使用
jquery.post
或jquery.ajax
(当然,使用POST
动词)以及 "application/json
" 内容类型,这样序列化后的 order 对象将被发送到目标 controller action,ASP.NET MVC 将自动反序列化 JSON 对象到OrderViewModel
。这里的问题是,它是 AJAX!这意味着响应将返回到 Ajax 调用的
xhr
对象中,因此没有发生重定向。因此,我们设法毫不费力地发送了对象并进行了序列化,但未能获得表示重定向的新页面的响应。
- 使用 Ajax 并将响应注入文档:这与上一个相同,除了当响应从
xhr
对象返回时(在这种情况下,它将是表示目标页面的完整 HTML 响应),我们会将其注入文档。当然,这个选项很糟糕,为什么?因为首先,它会返回 HTML 和 JavaScript,但它们不会被解析,而且实际上我们仍然停留在源页面(URL 将保留在源页面),我们只是显示了目标页面。
当您想处理部分视图,或者处理返回静态内容且不包含要解析的
script
标签的页面时,此选项最为合适。 - 使用动态表单提交:这种方法我们都很熟悉,也是最好的方法。您在 JavaScript 中创建一个表单元素,创建一个隐藏的 HTML
input
元素,序列化 order 对象并将其存储在隐藏字段中,然后将隐藏的 input 元素附加到表单并提交。
var form = document.createElement("form");
<pre>var input = document.createElement("input");
input.setAttribute("type", "hidden") ;
input.setAttribute("name", "XOrder") ;
input.setAttribute("value", JSON.stringify(order));
form.appendChild(input);
document.body.appendChild(form);
form.submit();
现在,我们这里所做的是将对象序列化为 JSON 并将其发送到目标 action controller,但 action controller 的参数(OrderViewModel vmOrder
)将是 NULL
,为什么?简单来说,因为 MVC 接收到的 HTTP 请求是 html/text 请求而不是 JSON。请记住,当我们使用 Ajax 时,我们指定了内容类型为 application/json,这帮助我们将数据(payload)发送到目标,MVC 能够从内容类型识别出 JSON 并因此将其反序列化为 OrderViewModel
。而这里的情况不同,这是一个普通的表单提交,您可以通过遍历 this.Request.Form
在 ASP.NET(服务器端)读取数据,它将包含所有输入(隐藏的或非隐藏的,当然)。另外请记住,使用表单提交,您无法指定 JSON 作为内容类型,因为它不被浏览器支持。
最佳解决方案
好的……现在我想要的是:不是 AJAX,而是普通的表单提交(当然是 POST),并且我希望 controller action 的参数能够自动反序列化为我的视图模型参数(OrderViewModel
)。
为了做到这一点,我知道我必须更多地了解 ASP.NET MVC 中的 Model Binders。Model Binders 简单地将发送到 MVC controller 的 HTTP 请求转换为 controller 可以轻松理解和处理的对象和术语,并且通常通过约定来实现。
因此,我想要一个自定义的 binder,它将接收来自隐藏字段的序列化 JSON 对象,并自动将其转换为 controller action 中的参数,而无需担心那些烦琐的工作。
以下是代码
public class JsonModelBinder : DefaultModelBinder
{
public override BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
try
{
var strJson = controllerContext.HttpContext.Request.Form[bindingContext.ModelName];
if(string.IsNullOrEmpty(strJson))
{
return null;
}
else
{
JavaScriptSerializer serializer = new JavasScriptSerializer();
var model = serializer.Deserialize(strJson, bindingContex.ModelType);
var modelMetaData = ModelMetadataProviders.Current
.GetMetadataForType(()=>model, bindingContext.ModelType);
var validator= ModelValidator
.GetModelValidator(modelMetaData, controllerContext);
var validationResult = validator.Validate(null);
foreach(var item in validationResult)
{
bindingContext.ModelState
.AddModelError(itrem.MemberName, item.Message);
}
return model;
}
}
catch(Exception ex)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelType.Name, ex.Message);
}
}
上面的代码是一个 Model Binder,它继承了 MVC 的默认 Model Binder。在不深入细节的情况下,我们需要在正确的位置使用它,以便将 JSON 对象作为 controller action 的参数。
MVC 已经通过多种方式支持这一点,我认为最简洁的方式(而不改变您应用程序中的默认行为)是通过 .NET Attributes。MVC 已经有一个类 CustomModelBinderAttribute
,我们可以继承它并使用我们的自定义 Model Binders。
public class JsonBinderAttribute : CustomModelBinderAttribute
{
public overried IModelBinder GetBinder()
{
return new JsonModelBinder();
}
}
现在您将做两件事
第一:在目标 controller action 的参数上添加 JsonBinder
属性
[HttpPost]
public ActionResult SubmitOrder([JsonBinder]OrderViewModel vmOrder)
{
//Process the vmOrder if wanted
return View(vmOrder);
}
第二:当您从 JavaScript 发送数据时,将隐藏字段的名称设置为参数的名称
var form = document.createElement("form");
var input = document.createElement("input");
input.setAttribute("type", "hidden") ;
input.setAttribute("name", "vmOrder") ;
input.setAttribute("value", JSON.stringify(order));
form.appendChild(input);
document.body.appendChild(form);
form.submit();
关注点
为什么将隐藏字段的名称设置为参数的名称?因为我们希望在发送方和接收方之间建立某种基于约定的映射,参数的名称是最明显清晰的选择。
摘要
希望这对您有所帮助。我相信这是我第二次需要类似概念的、同时进行数据 POST 和重定向的功能,我第一次发布相关文章是在大约 4 年前……链接在这里:https://codeproject.org.cn/Articles/37539/Redirect-and-POST-in-ASP-NET。