关于json:c#[FromBody]忽略HttpPost上的JsonProperty

c# [FromBody] ignores JsonProperty on HttpPost

我想知道是否还有其他人遇到过这种情况,即在发布JSON时,FromBody属性会忽略包含连字符的JsonProperty PropertyNames。

我已经在下面的代码中注释了输出以演示所发布的内容,我可以使用Request.InputStream来愉快地反序列化HttpPost,但希望有一种使用FromBody Attribute的更简单有效的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public class Test
{
    [JsonProperty("id")]
    public int Id { get; set; }

    [JsonProperty("forename")]
    public string Forename { get; set; }

    [JsonProperty("address")]
    public Address Address { get; set; }

    [JsonProperty("records-submissions")]
    public IList<RecordsSubmission> RecordsSubmissions { get; set; }

    [JsonProperty("anotherrecordssubmissions")]
    public IList<RecordsSubmission> AnotherRecordsSubmissions { get; set; }
}

public class Address
{
    [JsonProperty("value")]
    public string Value { get; set; }
}

public class RecordsSubmission
{
    [JsonProperty("record-id")]
    public int RecordId { get; set; }

    [JsonProperty("timestamp")]
    public long Timestamp { get; set; }
}

using (var webClient = new WebClient())
{
    var address = new Api.Address {Value ="Somewhere"};

    var recordSubmission = new Api.RecordsSubmission
    {
        RecordId = 2,
        Timestamp = 1497541704606
    };

    var recordsSubmissions = new List<Api.RecordsSubmission> {recordSubmission};

    var anotherRecordsSubmissions = new List<Api.RecordsSubmission> {recordSubmission};

    var test = new Api.Test
    {
        Id = 1,
        Forename ="Joe",
        Address = address,
        RecordsSubmissions = recordsSubmissions,
        AnotherRecordsSubmissions = anotherRecordsSubmissions
    };

    var testSerializeObject = JsonConvert.SerializeObject(test);

    // testSerializeObject Output
    // {"id":1,"forename":"Joe","address":{"value":"Somewhere","versions":null},"records-submissions":[{"record-id":2,"timestamp":1497541704606}],"anotherrecordssubmissions":[{"record-id":2,"timestamp":1497541704606}]}

    var testDeserializeObject = JsonConvert.DeserializeObject<Api.Test>(testSerializeObject);

    // Correct deserialization occurred, class Test is fully populated

    var serializeObject = JsonConvert.SerializeObject(testDeserializeObject);

    // serializeObject Output
    // {"id":1,"forename":"Joe","address":{"value":"Somewhere","versions":null},"records-submissions":[{"record-id":2,"timestamp":1497541704606}],"anotherrecordssubmissions":[{"record-id":2,"timestamp":1497541704606}]}

    webClient.Headers.Add(HttpRequestHeader.ContentType,"application/json");
    webClient.Encoding = Encoding.UTF8;
    webClient.UploadString(new Uri("https://localhost:44380/api-test"),"POST", serializeObject);
}

[System.Web.Mvc.HttpPost]
public ActionResult Test([FromBody] Api.Test test)
{
    // The class test is only partially populated,
    // [FromBody] ignores JsonProperty PropertyNames with hypens
    // RecordsSubmission with the JsonProperty of"records-submissions" was isgnored and test.RecordsSubmission was null, where as AnotherRecordsSubmissions was fully populated
    try
    {
        var requestInputStream = Request.InputStream;
        requestInputStream.Seek(0, SeekOrigin.Begin);

        var json = new StreamReader(requestInputStream).ReadToEnd();

        var testDeserializeObject = JsonConvert.DeserializeObject<Api.Test>(json);

        // Correct deserialization occurred
    }
    catch (Exception)
    {
        // Do something
    }

    return Redirect("/");
}

更新资料

仍然没有进一步使用[FromBody]来处理连字符,但是使用类似于https://stackoverflow.com/questions/23995210/how-to-use-json-net-for-json-modelbinding-in-an-mvc5-project上的ModelBinder方法提供了一种解决方法,请参见下面的代码来演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class JsonNetModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext modelBindingContext)
    {
        controllerContext.HttpContext.Request.InputStream.Position = 0;

        var inputStream = controllerContext.RequestContext.HttpContext.Request.InputStream;

        var streamReader = new StreamReader(inputStream, Encoding.UTF8);

        var json = streamReader.ReadToEnd();

        return JsonConvert.DeserializeObject(json, modelBindingContext.ModelType);
    }
}

[System.Web.Mvc.HttpPost]
public ActionResult Test([ModelBinder(typeof(JsonNetModelBinder))] Api.Test test)
{
}


您正在使用var requestInputStream = Request.InputStream;,这意味着该方法位于Controller类中。在ApiController类中,这将产生编译错误。

看来MVC和API混在一起了。 FromBody应该在ApiController内部使用(使用System.Web.Http)。但是该方法具有System.Web.Mvc.HttpPost属性(位于控制器内部)。

1
2
[System.Web.Mvc.HttpPost]
public ActionResult Test([FromBody] Api.Test test)

在这种情况下,因为没有其他参数,因此可以忽略FromBody无关紧要。默认情况下,该请求在'test'参数中反序列化。

因此,问题不在于FromBody。

问题是:该方法应该返回什么?它应该重定向到家庭还是应返回StatusCode(HttpStatusCode.Created)?

如果要重定向,请考虑以POST形式而不是Json形式。这不能解决连字符问题,但这更合乎逻辑。您可以添加一个AntiForgery令牌。

另一个选择是使用DTO而不使用连字符或从InputStream反序列化Json,就像在更新代码中一样。

因此,您可以使用controller类进行此类调用,但是除非您自己进行一些处理json的操作,否则无法解决连字符的问题。

通过将方法移至ApiControler类,可以非常轻松地解决连字符问题。这足以解决连字符问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SomeApiController : ApiController
{
    [HttpPost]
    public IHttpActionResult Test(Api.Test test)
    {
        var res = test.RecordsSubmissions;
        return StatusCode(HttpStatusCode.Created);
    }

    // Is equivalent of Test:
    [HttpPost]
    public void Test2(Api.Test test)
    {
        var res = test.RecordsSubmissions;

    }
}