Reputation: 2483
I wonder if anyone else has come across this situation, that when posting JSON the FromBody Attribute disregards JsonProperty PropertyNames's that contain hypens.
I have commented the outputs in my code below to demonstrate what is posted, I can happily deserialize the HttpPost using Request.InputStream, but hoping there is a simpler more efficient method using the FromBody Attribute.
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("/");
}
Update
Still no further using [FromBody] to deal with hyphens, however using a ModelBinder approach, similar to that found on https://stackoverflow.com/questions/23995210/how-to-use-json-net-for-json-modelbinding-in-an-mvc5-project
has provided a workaround, please see my code below to demonstrate:
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)
{
}
Upvotes: 1
Views: 2891
Reputation:
You are using var requestInputStream = Request.InputStream;
which means that the method is located in a Controller class. Inside an ApiController class this would give a compile error.
It seems that MVC and API are mixed up. FromBody should be used inside an ApiController (uses System.Web.Http). But the method has a System.Web.Mvc.HttpPost attribute (and is located inside a controller).
[System.Web.Mvc.HttpPost]
public ActionResult Test([FromBody] Api.Test test)
In this case it doesn't matter as FromBody can be omitted since there are no other parameters. By default the request is deserialized in the 'test' parameter.
So the problem lies not in FromBody.
The question is: what should the method return? Should it redirect to home or should it respond with StatusCode(HttpStatusCode.Created)?
If you want to redirect then consider to POST a form instead of Json. This does not resolve the hyphen issue, but this is more logical. And you can add an AntiForgery token.
Another option is to use a DTO without the hyphens or deserialize Json from the InputStream, like you do in the code from your update.
So you can use the controller class for calls like this, but you cannot resolve the issue with the hypens unless you do something to handle the json yourself.
You can resolve the hyphen problem quite easy by moving the method to an ApiControler class. This is sufficient to solve the hyphen issue.
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;
}
}
Upvotes: 1