c#[FromBody]忽略HttpPost上的JsonProperty

时间:2017-06-19 13:24:25

标签: c# json asp.net-mvc asp.net-mvc-5

我想知道是否有其他人遇到过这种情况,当发布JSON时,FromBody属性会忽略包含超量的JsonProperty PropertyNames。

我已经在下面的代码中评论了输出,以演示发布的内容,我可以使用Request.InputStream快乐地反序列化HttpPost,但希望使用FromBody属性有一个更简单更有效的方法。

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]处理连字符,但使用ModelBinder方法,类似于https://stackoverflow.com/questions/23995210/how-to-use-json-net-for-json-modelbinding-in-an-mvc5-project上提供的方法提供了一种解决方法,请参阅下面的代码来演示:

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)
{
}

1 个答案:

答案 0 :(得分:1)

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

似乎MVC和API混在了一起。 FromBody应该在ApiController中使用(使用System.Web。 Http )。但该方法有一个System.Web。 Mvc .HttpPost属性(位于控制器内)。

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

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

所以问题不在于FromBody。

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

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

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

所以你可以使用控制器类进行这样的调用,但除非你自己动手处理json,否则无法解决问题。

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

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;

    }
}