WebApi receive parameters including dynamic list using FromBody - asp.net-mvc

I am trying to receive the below JSON format in webapi
{
"name": "Matt",
"age": "24",
"payload": {"key1": "value1", "key2": "value2"}
}
here the payload is dynamic and it can have any key and value.
My webapi looks like
public class Person
{
public string name{ get; set; }
public int age{ get; set; }
public string payload { get; set; }
}
public async Task<HttpResponseMessage> Post([FromBody]Person value)
{
// Getting name and age but not payload.
}
Questions
Is FromBody the way to get this values? If yes, what i am missing here. If no, what is the best practice for these kind of inputs?

Yes [FromBody] should be used. You can use an IDictionary<string,string> for payload.
public class Person
{
public string name{ get; set; }
public int age{ get; set; }
public IDictionary<string,string> payload { get; set; }
}
public async Task<HttpResponseMessage> Post([FromBody]Person value)
{
var value1 = value.payload["key1"];
var value2 = value.payload["key2"];
}

Related

Asp.Net Web Api 5: parameter not binding

I have a simple api endpoint and I want to pass a JSON object
The controller code:
[HttpPost, Route("member/list")]
public IHttpActionResult MemberList([FromBody] MemberListRequest listRequest)
{
var members = MemberService.ListMembers(listRequest);
return Ok(members);
}
The parameter class:
[Serializable]
public class MemberListRequest
{
public int? CountryId { get; set; }
public int? RegionId { get; set; }
public int? UnitId { get; set; }
public int? StatusId { get; set; }
public bool IncludeDeleted { get; set; }
public string Text { get; set; }
}
When I call my api from Postman (like in the image) I get a listRequest object with all the members set to default values. Content-Type is set to "application/json" in the request headers.
This is MVC 5. Any suggestion? What am doing wrong?
Okay, I am really surprized. It turns out that the [Serializable] attribute was preventing the parameter to bind correctly.
So I just removed it, and now it works.
public class MemberListRequest
{
public int? CountryId { get; set; }
public int? RegionId { get; set; }
public int? UnitId { get; set; }
public int? StatusId { get; set; }
public bool IncludeDeleted { get; set; }
public string Text { get; set; }
}

How do I use string-ified enum in the body of a POST request to an ASP.NET core server?

The server code is
[HttpPost("/<route>/update2")]
public StatusCodeResult UpdatePanel2([FromBody] PanelUpdateReq updateRequest)
{
if (updateRequest == null)
return BadRequest();
return Ok();
}
public enum ZZ
{
A,
B
}
public class D
{
public int Index { get; set; }
public string Path1 { get; set; }
public string Path2 { get; set; }
public ZZ DefectType { get; set; }
public double foo { get; set; }
public int bar { get; set; }
}
public class PanelUpdateReq
{
public int Number { get; set; }
public string Path { get; set; }
public List<D> Items { get; set; }
}
I find that when I use a number value for the DefectType enum (i.e. "DefectType": 0), the request returns OK. But if I send the letter "DefectType": "A" then the server cannot parse the request and returns a bad request.
Here's the full request :
{
"Number" : 2738,
"Path" : "abc/cd/2738",
"Items": [
{
"Index" : 1,
"Path1" : "some path 1",
"Path2" : "some path 2",
"DefectType" : 0, // or "A" which does not work
"foo": 10.0,
"bar" : 11
}
]
}
Any idea what's going wrong here? I have looked at multiple other questions here on SO (here's 1 for example), and the consensus is that using "A" should work.
I am using Postman client, and content type is set as application/json in the header if that matters.
If you pass a string to the enum, you can configure the serialization of the enum in the startup.
services.AddControllers()
.AddJsonOptions(option=>
{
option.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
Then, it can receive the value.

MVC - how to use hyphen in model properties

I am getting json response from some server in format like:
[
{
"email":"john.doe#sendgrid.com",
"sg_event_id":"VzcPxPv7SdWvUugt-xKymw",
"sg_message_id":"142d9f3f351.7618.254f56.filter-147.22649.52A663508.0",
"timestamp":"1386636112",
"smtp-id":"<142d9f3f351.7618.254f56#sendgrid.com>",
"event":"processed",
"category":"category1",
"id": "001",
"purchase": "PO1452297845",
"Segmentid": "123456"
},
{
"email":"not an email address",
"smtp-id":"<4FB29F5D.5080404#sendgrid.com>",
"timestamp":"1386636115",
"reason":"Invalid",
"event":"dropped",
"category":"category2",
"id":"001",
"purchase":"PO1452297845",
"Segmentid":"123456"
},
{
"email":"john.doe#sendgrid.com",
"sg_event_id":"vZL1Dhx34srS-HkO-gTXBLg",
"sg_message_id":"142d9f3f351.7618.254f56.filter-147.22649.52A663508.0",
"timestamp":"1386636113",
"smtp-id":"<142d9f3f351.7618.254f56#sendgrid.com>",
"event":"delivered",
"category":"category1",
"id": "001",
"ip": "174.56.33.234",
"url": "http://www.google.com/",
"purchase": "PO1452297845",
"Segmentid":"123456"
}]
In mvc controller action I am getting these response via model like:
[ValidateInput(false)]
[HttpPost]
public async Task<ActionResult> Index(ResponseModel[] rec)
{
}
My model is like:
public class ResponseModel
{
public int ReportId { get; set; }
public string raw { get; set; }
public int event_post_timestamp { get; set; }
public string SegmentId { get; set; }
public string url { get; set; }
public string type { get; set; }
public string status { get; set; }
public string attempt { get; set; }
public string useragent { get; set; }
public string ip { get; set; }
public string reason { get; set; }
public string response { get; set; }
public string newsletter { get; set; }
public string category { get; set; }
public string sg_message_id { get; set; }
public string sg_event_id { get; set; }
[Column("smtp-id")]
[DataMember(Name="smtp-id")]
[JsonProperty("smtp-id")]
public string smtp_id { get; set; }
public string email { get; set; }
[Column("event")]
[JsonProperty("event")]
[DataMember(Name = "event")]
public string #event { get; set; }
public int timestamp { get; set; }
}
In action I am getting all property initialized but not smtp-id. So please suggest me how can I map response "smtp-id" attribute to my model.
Create your own ActionFilterAttribute similar to what was done here
I know this is a really old post but I came across this and found that there are two things needed.
[JsonProperty(PropertyName = "message-id")] using Newtonsoft.Json and
a simple 'custom' model binder https://stackoverflow.com/a/34030497/2455159
Then any Json data object in can be bound to a model/ viewmodel in a controller accepting posted data like a webhook, that has invalid (at least a '-' ) character in property names/ attributes.

Need to deserialize a nested json array to server side array

I have an array nested in an object in a JSON string which I need deserialized at the server:
var orderStatus = {"auth": "xxxx", "resourceType": "order.status", "idSet": "2980", "lifecycleEvent": "modified", "objects": { "orders": [ { "id": "2980", "statusId": "6" } ] }
I use Robert Koritnik's plugin like this:
$.ajax({url: "receiveJson", type: "POST", data: $.toDictionary(orderStatus) });
My .net class file is:
public class orders
{
public string Id { get; set; }
public string statusId { get; set; }
}
public class objects
{
public orders orders { get; set; }
}
public class OrderStatus
{
public string clientName { get; set; }
public string source { get; set; }
public string auth { get; set; }
public string resourceType { get; set; }
public string idSet { get; set; }
public string lifecycleEvent { get; set; }
public objects objects { get; set; }
}
my controller code is:
public JsonResult receiveJson(OrderStatus orderStatus)
So the orders object is the array. It works up to creating orders as an object but id and status id in the orders object are null.
I have no control over the JSON I will receive, it has to be in this format.
I am new to JSON and .NET MVC. Don't know how to specify server side orders object as an array.
Fixed it by slightly amending my server side classes:
public class order
{
public string Id { get; set; }
public string statusId { get; set; }
}
public class objects
{
public List<order> orders { get; set; }
}
public class OrderStatus
{
public string clientName { get; set; }
public string source { get; set; }
public string auth { get; set; }
public string resourceType { get; set; }
public string idSet { get; set; }
public string lifecycleEvent { get; set; }
public objects objects { get; set; }
}
So the "orders" class has been changed to "order". "objects.orders" property is amended to be a list.
Now the jsondata is deserialized all the way down.

How can I handle large JSON input from Postmark in my MVC application?

This is related to this question, but in this case it's not something I am returning but rather the model binding. I am using Postmark to handle incoming emails, which posts to a page with a JSON payload.
I have a model as below and an action that takes in this JSON payload (posted with application/json) and processes it.
public class EmailModel
{
public IDictionary<string, string> Headers { get; set; }
public string From { get; set; }
public string Cc { get; set; }
public string HtmlBody { get; set; }
public string TextBody { get; set; }
public string ReplyTo { get; set; }
public string Tag { get; set; }
public string To { get; set; }
public string MessageID { get; set; }
public string MailboxHash { get; set; }
public string Subject { get; set; }
public List<Attachment> Attachments { get; set; }
}
public class Attachment
{
public string Content { get; set; }
public int ContentLength { get; set; }
public string ContentType { get; set; }
public string Name { get; set; }
}
This works fine for small attachments, but for anything that exceeds the default maxJsonLength property causes an error in deserialization. ("Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLength property.") Because I want to accept image attachments, this means most images fail.
I've tried updating the web.config, but as per those other threads, this doesn't help for MVC controllers. I figure I can probably do what was mentioned in a custom IModelBinder, but I'm struggling with how to intercept the deserialization. (In other words, it still fails because the deserialization has happened already).
Any suggestions? I'm sure it's just something stupid that I'm missing....
You could write a custom JsonValueProviderFactory that uses Json.NET:
public sealed class JsonDotNetValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
throw new ArgumentNullException("controllerContext");
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
return null;
var reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
var bodyText = reader.ReadToEnd();
return String.IsNullOrEmpty(bodyText) ? null : new DictionaryValueProvider<object>(JsonConvert.DeserializeObject<ExpandoObject>(bodyText, new ExpandoObjectConverter()) , CultureInfo.CurrentCulture);
}
}
and in your Application_Start:
ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().FirstOrDefault());
ValueProviderFactories.Factories.Add(new JsonDotNetValueProviderFactory());

Resources