如何将JSON POST数据作为对象传递给Web API方法?

350

ASP.NET MVC4 Web API应用程序定义了用于保存客户的post方法。 客户以JSON格式传递在POST请求正文中。 post方法中的客户参数包含属性的空值。

如何修复此问题,以便发布的数据将作为客户对象传递?

如果可能,应使用Content-Type: application/x-www-form-urlencoded,因为我不知道如何更改JavaScript方法中提交表单的Content-Type。

控制器:

public class CustomersController : ApiController {

  public object Post([FromBody] Customer customer)
        {
            return Request.CreateResponse(HttpStatusCode.OK,
            new
            {
                customer = customer
            });
        }
    }
}

public class Customer
    {
        public string company_name { get; set; }
        public string contact_name { get; set; }
     }

请求:

POST http://localhost:52216/api/customers HTTP/1.1
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded; charset=UTF-8

{"contact_name":"sdfsd","company_name":"ssssd"}
9个回答

561

编辑:2017年10月31日

相同的代码/方法也可适用于Asp.Net Core 2.0。 主要区别在于,在asp.net core中,Web API控制器和Mvc控制器合并为单个控制器模型。 因此,您的返回类型可能是IActionResult或其实现之一(例如:OkObjectResult)。


使用

contentType:"application/json"

当您发送数据时,需要使用 JSON.stringify 方法将其转换为 JSON 字符串,

然后模型绑定器将把 JSON 数据绑定到您的类对象上。

下面的代码将正常工作(已测试)

$(function () {
    var customer = {contact_name :"Scott",company_name:"HP"};
    $.ajax({
        type: "POST",
        data :JSON.stringify(customer),
        url: "api/Customer",
        contentType: "application/json"
    });
});

结果

在此输入图片描述

contentType 属性告诉服务器我们发送的数据格式为 JSON。由于我们发送了一个 JSON 数据结构,所以模型绑定将正常进行。

如果你检查 Ajax 请求的头信息,你会看到 Content-Type 值被设置为 application/json

如果你没有显式指定 contentType,它将使用默认的 content type,即 application/x-www-form-urlencoded;


在 2015 年 11 月进行编辑,以解决评论中提出的其他可能问题

传递一个复杂的对象

假设您有一个复杂的视图模型类作为 Web API 操作方法参数,如下所示:

public class CreateUserViewModel
{
   public int Id {set;get;}
   public string Name {set;get;}  
   public List<TagViewModel> Tags {set;get;}
}
public class TagViewModel
{
  public int Id {set;get;}
  public string Code {set;get;}
}

而您的 Web API 终端点如下:

public class ProductController : Controller
{
    [HttpPost]
    public CreateUserViewModel Save([FromBody] CreateUserViewModel m)
    {
        // I am just returning the posted model as it is. 
        // You may do other stuff and return different response.
        // Ex : missileService.LaunchMissile(m);
        return m;
    }
}
< p > < em > 在撰写本文时,ASP.NET MVC 6是最新的稳定版本,在MVC6中,Web api控制器和MVC控制器都继承自Microsoft.AspNet.Mvc.Controller基类。

要从客户端向方法发送数据,下面的代码应该可以正常工作

//Build an object which matches the structure of our view model class
var model = {
    Name: "Shyju",
    Id: 123,
    Tags: [{ Id: 12, Code: "C" }, { Id: 33, Code: "Swift" }]
};

$.ajax({
    type: "POST",
    data: JSON.stringify(model),
    url: "../product/save",
    contentType: "application/json"
}).done(function(res) {       
    console.log('res', res);
    // Do something with the result :)
});

模型绑定仅适用于某些属性,而非全部!为什么?

如果您没有使用 [FromBody] 特性来修饰 Web API 方法参数,则不会对所有属性进行绑定。

[HttpPost]
public CreateUserViewModel Save(CreateUserViewModel m)
{
    return m;
}

发送模型(原生JavaScript对象,不是JSON格式),不指定contentType属性值

$.ajax({
    type: "POST",
    data: model,
    url: "../product/save"
}).done(function (res) {
     console.log('res', res);
});

模型绑定只适用于模型上的平面属性,而不适用于类型为复杂/其他类型的属性。在我们的情况下,IdName属性将正确地绑定到参数m,但Tags属性将成为一个空列表。

如果您正在使用短版本$.post,发送请求时将使用默认的Content-Type,这会导致相同的问题出现。

$.post("../product/save", model, function (res) {
    //res contains the markup returned by the partial view
    console.log('res', res);
});

4
不确定我做了什么,但今早回来发现情况跟之前一样。控制器中的对象为空。又要重新开始了,哈哈。 - Grayson
1
请确保在使用Fiddler时,将内容类型写为“Content-Type: application/json”。谢谢! - ioWint
1
你简单地解决了我一天的工作!!! 这个小函数 "JSON.stringify(data)" 就做到了! - Gil Allen
1
请记住,如果您这样做(更改Content-Type标头)并且正在进行CORS请求,则jQuery将在您的POST之前开始添加预检OPTIONS请求,服务器必须处理它们。 - Arbiter
1
由于复杂类型的问题,我认为只需指定'contentType: 'application/json;'并将JS对象转换为JSON字符串,就不需要使用[FromBody]属性了。 - BornToCode
显示剩余12条评论

71
在WebApi中处理POST请求可能会很棘手!想要补充已经正确的答案。。
将重点放在POST上,因为处理GET是很简单的。我不认为有多少人会在网上搜索解决WebAPI的GET问题。不管怎样。。
如果您的问题是——在MVC Web Api中如何——使用除HTTP动词以外的自定义操作方法名?执行多个POST请求?发布多个简单类型?通过jQuery发布复杂类型?
那么以下解决方案可能会有所帮助:
首先,在Web API中使用自定义操作方法,可以添加一个Web API路由:
public static void Register(HttpConfiguration config)
{
    config.Routes.MapHttpRoute(
        name: "ActionApi",
        routeTemplate: "api/{controller}/{action}");
}

然后您可以创建像这样的操作方法:

[HttpPost]
public string TestMethod([FromBody]string value)
{
    return "Hello from http post web api controller: " + value;
}

现在,从您的浏览器控制台启动以下jQuery代码:

$.ajax({
    type: 'POST',
    url: 'http://localhost:33649/api/TestApi/TestMethod',
    data: {'':'hello'},
    contentType: 'application/x-www-form-urlencoded',
    dataType: 'json',
    success: function(data){ console.log(data) }
});

第二,要执行多个帖子,很简单,创建多个操作方法并用[HttpPost]属性进行装饰。使用[ActionName("MyAction")]来分配自定义名称等。下面将在第四点中介绍jQuery。
第三,首先,在单个操作中发布多个简单类型是不可能的。此外,即使是发布单个简单类型(除了在查询字符串或REST样式中传递参数),也有一种特殊格式。这就是我与Rest客户端(如Fiddler和Chrome的高级REST客户端扩展)争论不休并在网上搜寻了近5个小时的重点,最终,以下URL证明对我有所帮助。将引用相关内容,因为链接可能会失效!
Content-Type: application/x-www-form-urlencoded
in the request header and add a = before the JSON statement:
={"Name":"Turbo Tina","Email":"na@Turbo.Tina"}

注意到了奇怪的语法吗?

http://forums.asp.net/t/1883467.aspx?The+received+value+is+null+when+I+try+to+Post+to+my+Web+Api

无论如何,让我们结束这个故事。接下来:

第四,通过jQuery发布复杂类型,当然,$.ajax()会迅速发挥作用:

假设操作方法接受一个Person对象,该对象具有id和name属性。因此,从javascript中:

var person = { PersonId:1, Name:"James" }
$.ajax({
    type: 'POST',
    url: 'http://mydomain/api/TestApi/TestMethod',
    data: JSON.stringify(person),
    contentType: 'application/json; charset=utf-8',
    dataType: 'json',
    success: function(data){ console.log(data) }
});

然后这个动作将会是这样:

[HttpPost]
public string TestMethod(Person person)
{
    return "Hello from http post web api controller: " + person.Name;
}

所有上述方法都对我有用!干杯!

4
我似乎每隔几个月都会遇到这个问题,大多数情况下我最终都能解决它,但这一次我已经放弃了。上面提到的所有提示对我都没有用,所以我决定把这个方法放弃。如果这么难做到正确,那还有必要吗?这只不过是一种方便而已 - 把内容作为字符串获取并使用Newtonsoft进行转换。完成。在尝试了一个小时的“简单”方法后,用“困难”的方法解决它可能只需要30秒钟。我对这种方法并不是很满意,但它有根本性的问题吗? - Kinetic
在WebApi2中,我们现在可以使用路由装饰器。因此,这个问题主要得到了解决。http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2 - Vaibhav
2
想要添加一点观察。有时候,在WebAPI端传递复杂类型(例如:DTO)时,模型绑定失败(null)的原因是模型中的一个或多个属性不兼容(或无法解析)。比如说,如果将一个Guid属性分配给一个无效的GUID,则会出现这种情况。 在这种情况下,请尝试使用所有对象属性的默认/空值,然后再试一次。 - Vaibhav

12

我刚刚在玩这个东西时发现了一个相当奇怪的结果。假设你在C#中有以下形式的类公共属性:

public class Customer
{
    public string contact_name;
    public string company_name;
}

那么你必须像Shyju建议的那样使用JSON.stringify技巧,并像这样调用它:

var customer = {contact_name :"Scott",company_name:"HP"};
$.ajax({
    type: "POST",
    data :JSON.stringify(customer),
    url: "api/Customer",
    contentType: "application/json"
});

然而,如果你在你的类中像这样定义getter和setter:

public class Customer
{
    public string contact_name { get; set; }
    public string company_name { get; set; }
}

那么你可以更简单地调用它:

$.ajax({
    type: "POST",
    data :customer,
    url: "api/Customer"
});

这使用了HTTP头部:

Content-Type:application/x-www-form-urlencoded

我不太确定这里正在发生什么,但看起来像是框架中的一个错误(特性?)。可能不同的绑定方法调用了不同的“适配器”,而应用程序/json的适配器使用公共属性,而表单编码数据的适配器则没有。

不过,我不知道哪种做法被认为是最佳实践。


8
属性和字段之间的区别就在于它们的定义方式。属性是最佳实践。在第一个示例中,你所谓的属性实际上是字段。当你在它们上面使用get/set时,它们将自动创建后备字段,从而成为属性。 - crthompson
这是非常真实而又奇怪的。只有字段的普通类不会绑定到表单提交,但属性可以。顺便说一下:这仍然无法解释为什么会出现这种情况...?我只能猜测内部逻辑只会将JSON数据绑定到字段,将表单提交数据绑定到属性,就是这样...? - James Wilkins
1
这是因为代码只查找属性。由于使用公共字段并不是最佳实践,微软团队决定不允许最佳实践的情况,我认为这是一个非常好的理由。 - Erik Philips

1
使用 JSON.stringify() 方法将字符串转换为 JSON 格式,确保在进行 AJAX 调用时传递以下属性:
  • contentType: 'application/json'
下面是提供的 jQuery 代码,用于向 ASP.NET Web API 进行 ajax post 调用:

var product =
    JSON.stringify({
        productGroup: "Fablet",
        productId: 1,
        productName: "Lumia 1525 64 GB",
        sellingPrice: 700
    });

$.ajax({
    URL: 'http://localhost/api/Products',
    type: 'POST',
    contentType: 'application/json',
    data: product,
    success: function (data, status, xhr) {
        alert('Success!');
    },
    error: function (xhr, status, error) {
        alert('Update Error occurred - ' + error);
    }
});


2
数据类型不是必需的。 - Erik Philips

0

1) 在客户端,您可以像下面这样以字符串形式发送http.post请求

var IndexInfo = JSON.stringify(this.scope.IndexTree);
this.$http.post('../../../api/EvaluationProcess/InsertEvaluationProcessInputType', "'" + IndexInfo + "'" ).then((response: any) => {}

2) 然后在您的 Web API 控制器中,您可以对其进行反序列化

public ApiResponce InsertEvaluationProcessInputType([FromBody]string IndexInfo)
    {
var des = (ApiReceivedListOfObjects<TempDistributedIndex>)Newtonsoft.Json.JsonConvert.DeserializeObject(DecryptedProcessInfo, typeof(ApiReceivedListOfObjects<TempDistributedIndex>));}

3) 你的 ApiReceivedListOfObjects 类应该像下面这样

public class ApiReceivedListOfObjects<T>
    {
        public List<T> element { get; set; }

    }

4) 在第二步的JsonConvert.DeserializeObject命令之前,确保您的序列化字符串(此处为IndexInfo)变成以下结构

var resp = @"
    {
        ""element"": [
        {
            ""A"": ""A Jones"",
            ""B"": ""500015763""
        },
        {
            ""A"": ""B Smith"",
            ""B"": ""504986213""
        },
        {
            ""A"": ""C Brown"",
            ""B"": ""509034361""
        }
        ]
    }";

0

0
确保您的WebAPI服务期望一个与您传递的JSON相匹配的结构的强类型对象。同时,确保您对要POST的JSON进行字符串化。
以下是我的JavaScript代码(使用AngularJS):
$scope.updateUserActivity = function (_objuserActivity) {
        $http
        ({
            method: 'post',
            url: 'your url here',
            headers: { 'Content-Type': 'application/json'},
            data: JSON.stringify(_objuserActivity)
        })
        .then(function (response)
        {
            alert("success");
        })
        .catch(function (response)
        {
            alert("failure");
        })
        .finally(function ()
        {
        });

这是我的WebAPI控制器:

[HttpPost]
[AcceptVerbs("POST")]
public string POSTMe([FromBody]Models.UserActivity _activity)
{
    return "hello";
}

0
@model MVCClient.Models.ProductDetails

@{
    ViewBag.Title = "ProductDetails";
}
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<script type="text/javascript">

    $(document).ready(function () {
        $("#Save").click(function () {
            var ProductDetails = new Object();
            ProductDetails.ProductName =  $("#txt_productName").val();
            ProductDetails.ProductDetail = $("#txt_desc").val();
            ProductDetails.Price= $("#txt_price").val();
            $.ajax({
                url: "http://localhost:24481/api/Product/addProduct",
                type: "Post",
                dataType:'JSON',
                data:ProductDetails, 

                success: function (data) {
                    alert('Updated Successfully');
                    //window.location.href = "../Index";
                },
                error: function (msg) { alert(msg); }
            });
        });
    });
    </script>
<h2>ProductDetails</h2>

<form id="form1" method="post">
    <fieldset>
        <legend>ProductDetails</legend>


        <div class="editor-label">
            @Html.LabelFor(model => model.ProductName)
        </div>
        <div class="editor-field">

            <input id="txt_productName" type="text" name="fname">
            @Html.ValidationMessageFor(model => model.ProductName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.ProductDetail)
        </div>
        <div class="editor-field">

            <input id="txt_desc" type="text" name="fname">
            @Html.ValidationMessageFor(model => model.ProductDetail)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Price)
        </div>
        <div class="editor-field">

            <input id="txt_price" type="text" name="fname">
            @Html.ValidationMessageFor(model => model.Price)
        </div>



        <p>
            <input id="Save" type="button" value="Create" />
        </p>
    </fieldset>

</form>
    <div>
        @Html.ActionLink("Back to List", "Index")
    </div>

</form>



@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

0

以下代码可以在Web API 2中返回JSON格式的数据,而不是XML格式:

将以下代码放入Global.asax文件中:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接