65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (13投票s)

2014年5月14日

CPOL

5分钟阅读

viewsIcon

76854

downloadIcon

619

本文介绍如何在 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),我有两个选择

  1. 使用 Ajax:我可以直接使用 jquery.post jquery.ajax (当然,使用 POST 动词)以及 "application/json" 内容类型,这样序列化后的 order 对象将被发送到目标 controller action,ASP.NET MVC 将自动反序列化 JSON 对象到 OrderViewModel

    这里的问题是,它是 AJAX!这意味着响应将返回到 Ajax 调用的 xhr 对象中,因此没有发生重定向。

    因此,我们设法毫不费力地发送了对象并进行了序列化,但未能获得表示重定向的新页面的响应。

  2. 使用 Ajax 并将响应注入文档:这与上一个相同,除了当响应从 xhr 对象返回时(在这种情况下,它将是表示目标页面的完整 HTML 响应),我们会将其注入文档。

    当然,这个选项很糟糕,为什么?因为首先,它会返回 HTML 和 JavaScript,但它们不会被解析,而且实际上我们仍然停留在源页面(URL 将保留在源页面),我们只是显示了目标页面。

    当您想处理部分视图,或者处理返回静态内容且不包含要解析的 script 标签的页面时,此选项最为合适。

  3. 使用动态表单提交:这种方法我们都很熟悉,也是最好的方法。您在 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

© . All rights reserved.