I've seen some tutorials out there that claim to work, but they are outdated or simply do not work.
How can I use JSON.Net to serialize and deserialize the data received to and sent from my API controllers?
We are using VS2012.
Update
I have a model like this
public class SearchModel
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public Dictionary<string, object> Terms { get; set; }
}
And an Api controller like this
public class ModelSearchApiController : ApiController
{
public List<Model> Get([FromUri] SearchModel search)
{
return new List<Model>();
}
}
However, search provides the correct value set in the Ajax request, the property Terms is always an empty dictionary.
I know we can provide a value like [ { Key:"foo", Value:123 } ] but why can't I just pass a normal JSON object (ie { foo:123 }) ??? Why can it serialize a Dictionary into a nice standard JSON object, but cannot take that exact same object and recreate a Dictionary. This is beyound me.
Edit
In other words, if the browser sends these arguments :
pageIndex: 0
pageSize: 100
terms[foo]: Bar
terms[buz]: 1234
What would be the required object signature? Because the object mentionned above does not work and the dictionary is just empty.
JSON.NET is the default serializer for ASP.NET Web API - it can convert between JSON and CLR objects, and does so for all JSON input. However, you're not trying to convert a JSON input to your SearchModel - you're trying to convert from the URI-based format which is similar to application/x-www-form-urlencoded, into the CLR type SearchModel, and that is not supported by JSON.NET (it's not JSON!). In general, the serializers are used to convert (on incoming requests) from the request body to the action parameter.
Let's look at this (complete) example below (assuming the default route, to "api/{controller}"). It's very similar to your question, but I also added a Post method in addition to the GET method.
public class ModelSearchApiController : ApiController
{
public List<Model> Get([FromUri] SearchModel search)
{
return new List<Model>
{
new Model { PageIndex = search.PageIndex, PageSize = search.PageSize, Terms = search.Terms }
};
}
public List<Model> Post(SearchModel search)
{
return new List<Model>
{
new Model { PageIndex = search.PageIndex, PageSize = search.PageSize, Terms = search.Terms }
};
}
}
public class Model
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public Dictionary<string, object> Terms { get; set; }
}
public class SearchModel
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public Dictionary<string, object> Terms { get; set; }
}
If you send this request to the server:
POST http://localhost:64699/api/ModelSearchApi HTTP/1.1
User-Agent: Fiddler
Host: localhost:64699
Content-Type: application/json
Content-Length: 65
{"PageIndex":1,"PageSize":10,"Terms":{"foo":"bar","foo2":"bar2"}}
It will be bound, as you expect, to the SearchModel parameter - the Terms property will be a dictionary with two entries (foo=bar, foo2=bar2).
Now, for the GET parameter. ASP.NET Web API has a concept of model binders and value provider, which would be the component which would convert between the query string into the action parameters. The default binder / provider do not support the "arbitrary" name/value pair syntax *for dictionary inside complex types. You can, as you pointed out, use the key/value pair syntax, and that will be understood, as shown below.
GET http://localhost:64699/api/ModelSearchApi?PageIndex=1&PageSize=10&Terms[0][key]=foo&Terms[0][value]=bar HTTP/1.1
User-Agent: Fiddler
Host: localhost:64699
Now, for your problem you have two options. You can change your API to use a custom model binder or value provider which knows how to understand the "simple" name/value syntax, as shown below:
public class ModelSearchApiController : ApiController
{
public List<Model> Get([ModelBinder(typeof(MySearchModelBinder))] SearchModel search)
{
return new List<Model>
{
new Model { PageIndex = search.PageIndex, PageSize = search.PageSize, Terms = search.Terms }
};
}
}
public class MySearchModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
SearchModel value = new SearchModel();
value.Terms = new Dictionary<string,object>();
foreach (var queryParams in actionContext.Request.GetQueryNameValuePairs())
{
if (queryParams.Key == "PageIndex")
{
value.PageIndex = int.Parse(queryParams.Value);
}
else if (queryParams.Key == "PageSize")
{
value.PageSize = int.Parse(queryParams.Value);
}
else if (queryParams.Key.StartsWith("Terms."))
{
value.Terms.Add(queryParams.Key.Substring("Terms.".Length), queryParams.Value);
}
}
bindingContext.Model = value;
return true;
}
}
Another option is to pre-process your input data on the client prior to sending to the server, using a function similar to the one below.
function objToKVPArray(obj) {
var result = [];
var k;
for (k in obj) {
if (obj.hasOwnProperty(k)) {
result.push({ key: k, value: obj[k] });
}
}
return result;
}
You can take reference the link below. Hope this help.
And here is sample using Json.net with web API.
Related
I'm trying to send the JSON object through Ajax request to ASP.NET MVC. While getting the JSON object in the backend, it receives the object in string format as shown below.
How to deserialize it into a C# object?
C# ASP.NET MVC code:
public string GetLists(string list1)
{
return JsonConvert.SerializeObject(list1, JsonSetting);
}
JavaScript code:
button.onclick=()=> {
let xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.response)
}
}
let list1=[
{"id":"1a","level":1},
{"id":"2b","level":14},
{"id":"3c","level":23}
]
var params ="list1=" + JSON.stringify(list1)
xhr.open("POST", "/Status/GetLists");
xhr.setRequestHeader('content-type', "application/x-www-form-urlencoded");
xhr.send(params);
}
Output:
[
{\"id\":\"1a\",\"level\":1},
{\"id\":\"2b\",\"level\":14},
{\"id\":\"3c\",\"level\":23}
]
You can use System.Text.Json.JsonSerializer
but you should include a type for deserialization so that c# knows the kind of data you are trying to receive.
In your code, you only give back the string but not a c# object so you have to specify a class or an interface to model your data.
Example snippet for receiving the data:
using System;
using System.Collections.Generic;
using System.Text.Json;
// define the structure of the object you want to recieve
interface MyDataStructure
{
public string id { get; set; }
public int level { get; set; }
}
class Program
{
public void ReadData(string jsonData)
{
// Creating a list of objects from your type. The Json Serializer is flexible and also workes when using Arrays
List<MyDataStructure> data = JsonSerializer.Deserialize<List<MyDataStructure>>(jsonData);
foreach (var obj in data){
Console.WriteLine($"{obj.id}||{obj.level}");
}
}
}
I recommend you should check out the docs on deserialization and serialization.
An alternative to the .Net JsonSerializer is Newtonsofts Json.NET
your data is serialized twice, at first you serialize it manually , after this net serialized it automaticaly to send data back. So you don't need to serialize it at all, net will do it for you. Change your action to this
public ActionResult<List<Item>> GetLists([FromBody] List<item> list)
{
return Ok(list);
}
class
public class Item
{
public string id { get; set; }
public string level { get; set; }
}
and ajax
var params =JSON.stringify(list1);
xhr.open("POST", "/Status/GetLists");
xhr.setRequestHeader('content-type', "application/json");
xhr.send(params);
You are trying to send a json object with a wrong content-type, in your case the content-type should be application/json and you don't have to add "params" + JSON.stringify(list1) as this will be sent in the request body, you should send just the list1 variable.
xhr.setRequestHeader('content-type', "application/json");
xhr.send(JSON.stringify(list1));
Then you will be able to deserialize the object in your backend endpoint like:
public class Item
{
public string id { get; set; }
public string level { get; set; }
}
[HttpPost]
public List<Item> GetLists([FromBody] object value)
{
var items = JsonConvert.DeserializeObject<List<Item>>(value.ToString());
return items;
}
How do I get a Dictionairy<string, string> from form-data?
View model:
public class SubmitRequestModel
{
public Dictionairy<string, string> InputFields { get; set; }
public List<IFormFile> Attachments { get; set; }
}
Action:
[HttpPut("{id:int}")]
public async Task<IActionResult> Submit(int id, [FromForm] SubmitRequestModel model)
{
// model.InputFields is initialized but it's count is 0
// do whatever
}
This is an API controller. Not cshtml or razor related.
So the model.InputFields is not null but it's count is 0. When I look at the raw request. I can see that the input is received, but it is not bound to the dictionairy in the model.
The values of the Request.Form collection: https://prnt.sc/11x532p
I need to use form data because we are uploading files with the request. This requires multipart/form-data.
How do I successfully parse the data to the model?
Info on how I tested this:
I have swagger set-up. I import the generated Swagger OpenAPI json in to Postman and test this way. I believe that this should be the correct request, that Swagger generated. But I'm not sure if it formatted the dictionairy the right way. It would be the right way for a JSON data request. But I'm not sure if that applicates here.
You can achieve this with custom IModelBinder:
public class DictionaryBinder<TKey, TValue> : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
if (bindingContext.HttpContext.Request.HasFormContentType)
{
var form = bindingContext.HttpContext.Request.Form;
var data = JsonSerializer.Deserialize<Dictionary<TKey, TValue>>(form[bindingContext.FieldName]);
bindingContext.Result = ModelBindingResult.Success(data);
}
return Task.CompletedTask;
}
}
The model class will become:
public class SubmitRequestModel
{
[FromForm]
[ModelBinder(BinderType = typeof(DictionaryBinder<string, string>))]
public Dictionary<string, string> InputFields { get; set; }
public List<IFormFile> Attachments { get; set; }
}
I think your problem is the same as the given link, just go through the link it may help you.
How to bind form inputs to dictionary values
or
Bind dynamic form data
So one solution, which is not my favourite but it works, is to parse the dictionairy manually.
First thing in the action:
if (model.InputFields == null || model.InputFields.Count == 0)
{
var rawInputFields = Request.Form["InputFields"].ToString();
model.InputFields = JsonSerializer.Deserialize<Dictionary<string, string>>(rawInputFields);
}
As title says
TypeScript model
export interface RigheOrdiniBuoniSpesaData {
id: number;
id_ordine: number;
id_taglio_buono_spesa: number;
quantita: number;
}
which is part of another bigger object:
export class OrdiniBuoniSpesaData {
id: number;
// OTHER FIELD
// OTHER FIELD
// OTHER FIELD
righe_ordine: RigheOrdiniBuoniSpesaTableData;
}
Save method
saveOrder(model: OrdiniBuoniSpesaData) {
const headerPost: HttpHeaders = new HttpHeaders();
headerPost.set('Content-type', 'application/x-www-form-urlencoded');
const formData: FormData = new FormData();
formData.append('id_cliente', model.id_cliente.toString());
// VARIOUS FORM FIELDS
//THIS IS ARRAY DATA
formData.append('righe_ordine', JSON.stringify(model.righe_ordine));
return this.http
.post<boolean>(
requestURL,
formData,
{ headers: headerPost }
)
.pipe(
catchError(this.handleError)
);
}
Order json (valid Json) is visible in Chrome request capture clearly along with all data:
[{"id":0,"id_ordine":0,"id_taglio_buono_spesa":1,"quantita":1},{"id":0,"id_ordine":0,"id_taglio_buono_spesa":1,"quantita":1},{"id":0,"id_ordine":0,"id_taglio_buono_spesa":1,"quantita":1},{"id":0,"id_ordine":0,"id_taglio_buono_spesa":3,"quantita":14}]
On API Side
Receiving model for JSON
public class RigheOrdiniBuoniSpesaViewModel
{
public long id { get; set; }
public long id_ordine { get; set; }
public long id_taglio_buono_spesa { get; set; }
public int quantita { get; set; }
}
Which is in
public class OrdiniBuoniSpesaViewModel
{
public long id { get; set; }
//OTHER VARIOUS FIELDS
//I TRIED ALSO WITH LIST INSTEAD OF IENUMERABLE
public IEnumerable<RigheOrdiniBuoniSpesaViewModel> righe_ordine {get;set;}
}
(I TRIED ALSO WITH LIST INSTEAD OF IENUMERABLE, STILL NO LUCK!)
Api controller signature:
[HttpPost]
public async Task<IActionResult> PostSaveOrder([FromForm] OrdiniBuoniSpesaViewModel model)
{.....code}
All the fields are binded correctly except for the righe_ordine array!
I can see every field correctly but the array has count = 0.
Strangely enough, if I examine the asp net request object (this.Request.Form) in the QuickWatch debug in visual studio:
this.Request.Form["righe_ordine"]
{[{"id":0,"id_ordine":0,"id_taglio_buono_spesa":1,"quantita":1},
{"id":0,"id_ordine":0,"id_taglio_buono_spesa":1,"quantita":1},
{"id":0,"id_ordine":0,"id_taglio_buono_spesa":1,"quantita":1},
{"id":0,"id_ordine":0,"id_taglio_buono_spesa":3,"quantita":14}]}
Microsoft.Extensions.Primitives.StringValues
is present and correctly populated..... but for some reason binding it to the OrdiniBuoniSpesaViewModel fails....
What am I doing wrong? Any idea?
EDIT:
For the moment the only solution I found is to directly catch the value just after entering the controller:
string righeJson = this.Request.Form["righe_ordine"].ToString();
if(!string.IsNullOrEmpty(righeJson))
model.righe_ordine = JsonConvert.DeserializeObject<IEnumerable<RigheOrdiniBuoniSpesaViewModel>>(righeJson);
I think you need to change your model name like this
In your js code
formData.append('righeOrdine', JSON.stringify(model.righe_ordine));
and c#
public class OrdiniBuoniSpesaViewModel
{
public long id { get; set; }
//OTHER VARIOUS FIELDS
//I TRIED ALSO WITH LIST INSTEAD OF IENUMERABLE
public IEnumerable<RigheOrdiniBuoniSpesaViewModel> RigheOrdine {get;set;}
}
Update: Just for enhancement
Maybe you need to parse it before using it in your controller
You can read my full code here
We will need to create interface and class to implement it.
public interface IJsonParseService<T> where T : class
{
T ToObject(string json);
}
public class JsonParseService<T> : IJsonParseService<T> where T : class
{
public T ToObject(string json)
{
return JObject.Parse(json).Root.ToObject<T>();
}
}
Then register it
services.AddScoped(typeof(IJsonParseService<>), typeof(JsonParseService<>));
So your code should be something like this
private readonly IJsonParseService<RigheOrdiniBuoniSpesaViewModel> _jsonParsePostOptionDefaultVm;
jsonParsePostOptionDefaultVm.ToObject(viewModel.RigheOrdiniBuoniSpesaViewModel);
I am trying to define a Controller Action in ASP.NET Core 2.2.
The tricky part is that I prefer this to be a GET endpoint, and the data that it must recieve is a collection of custom objects. Here is my sample code:
[Route("api/example")]
[ApiController]
public class ExampleController : ControllerBase
{
[HttpGet("getData")]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
public async Task<IActionResult> GetMarketData([FromQuery] MyObject[] queryData)
{
return this.Ok(0);
}
}
public class MyObject
{
public int A { get; set; }
public int B { get; set; }
}
I am expecting this to bind to something like
http://localhost/api/example/getData/?queryData=[A=1,B=1],queryData=[A=2,B=2]
However I can't seem to get it to work.
(Sending a request to the URL, does not parse the objects, and I receive an empty array in my controller)
I'm not sure if this is the best way to approach this, and maybe I need to change the place where I bind the data from?
The only thing I care about is being able to recieve an array (or some kind of a collection) of MyObject that I can process and return a response. I would also prefer for this to be a GET request, as, after all, I am trying to query this API and get data from it.
I know I can get it to work with using a [FromBody] attribute, but as far as I know GET requests should not use the body.
Any help is gladly appreciated.
Your GET request must be constructed as follows:
GET: /api/example/getData?queryData[0].A=1&queryData[0].B=2&queryData[1].A=3
Very similar to model binding when using <form>s :)
Your QueryString should look like:
/TestMe?queryData[0].A=1&queryData[0].B=1&queryData[1].A=2&queryData[1].B=2
If your code looks like:
public class MyObject
{
public int A { get; set; }
public int B { get; set; }
}
[Route("/TestMe")]
public IActionResult TestMe([FromQuery] MyObject[] queryData)
{
return Json(queryData);
}
Note that [FromQuery] isn't even required.
it's not going to work since there's not a default binder for collection types, you'd have to use a custom binder. I made my own implementation of a generic array model binder, here it goes:
// generic array model binder
public class ArrayModelBinder<TType> : IModelBinder {
public Task BindModelAsync(ModelBindingContext bindingContext) {
if (bindingContext.ModelMetadata.IsEnumerableType) {
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString();
if (!string.IsNullOrEmpty(value)) {
var elementType = typeof(TType);
var typeConverter = TypeDescriptor.GetConverter(elementType);
var splittedValues = value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
var values = splittedValues.Select(t => typeConverter.ConvertFromString(t.Trim())).ToArray();
var typedValues = Array.CreateInstance(elementType, values.Length);
values.CopyTo(typedValues, 0);
bindingContext.Model = typedValues;
return SuccessBinding(bindingContext, typedValues);
}
return SuccessBinding(bindingContext, null);
}
return FailedBinding(bindingContext);
}
private static Task SuccessBinding(ModelBindingContext bindingContext, Array typedValues) {
bindingContext.Result = ModelBindingResult.Success(typedValues);
return Task.CompletedTask;
}
private static Task FailedBinding(ModelBindingContext bindingContext) {
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
To use it on your Action you'll just have to use this piece of code:
public async Task<IActionResult> GetMarketData([ModelBinder(BinderType = typeof(ArrayModelBinder<object>))] MyObject[] queryData)
{
return this.Ok(0);
}
I have the source of this implementation and other things in a repo of my own Library feel free to check it out CcLibrary.AspNetCore
I must create webhook endpoint that will consume JSON messages.
Messages is send as x-www-form-urlencoded in form:
key = json
value = {"user_Id": "728409840", "call_id": "1114330","answered_time": "2015-04-16 15:37:47"}
as shown in PostMan:
request looks like this:
json=%7B%22user_Id%22%3A+%22728409840%22%2C+%22call_id%22%3A+%221114330%22%2C%22answered_time%22%3A+%222015-04-16+15%3A37%3A47%22%7D
To get values from request as my class (model) I must create temporary object containing single string property:
public class Tmp
{
public string json { get; set; }
}
and method inside my controller that consumes that request:
[AllowAnonymous]
[Route("save_data")]
[HttpPost]
public IHttpActionResult SaveData(Tmp tmp)
{
JObject json2 = JObject.Parse(tmp.json);
var details = json2.ToObject<CallDetails>();
Debug.WriteLine(details);
//data processing
return Content(HttpStatusCode.OK, "OK", new TextMediaTypeFormatter(), "text/plain");
}
As You can see Tmp class is useless.
Is there a way to get request data as this class:
public class CallDetails
{
public string UserId { get; set; }
public string CallId { get; set; }
public string AnsweredTime { get; set; }
}
I'm aware of IModelBinder class, but before I start I'd like to know if there is an easier way.
I can't change web-request format, by format I mean that is will always be POST containing single key - JSON yhat has json string as value.
You can use JsonProperty attribute for mapping json object properties to c# object properties:
public class CallDetails
{
[JsonProperty("user_id")]
public string UserId { get; set; }
[JsonProperty("call_id")]
public string CallId { get; set; }
[JsonProperty("answered_time")]
public string AnsweredTime { get; set; }
}
Then it can be used without temp class:
[AllowAnonymous]
[Route("save_data")]
[HttpPost]
public IHttpActionResult SaveData(CallDetails callDetails)
Update. Because the data is sent as x-www-form-urlencoded - I think the way you handled it is most straightforward and not so bad. If you want to check another options here're some of them:
Option 1 - custom model binder. Something like this:
public class CustomModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var body = actionContext.Request.Content.ReadAsStringAsync().Result;
body = body.Replace("json=", "");
var json = HttpUtility.UrlDecode(body);
bindingContext.Model = JsonConvert.DeserializeObject<CallDetails>(json);
return true;
}
}
And usage: SaveData([ModelBinder(typeof(CustomModelBinder))]CallDetails callDetails). Downside - you'll lose validation and maybe other stuff defined in web api pipeline.
Option 2 - DelegatingHandler
public class NormalizeHandler : DelegatingHandler
{
public NormalizeHandler(HttpConfiguration httpConfiguration)
{
InnerHandler = new HttpControllerDispatcher(httpConfiguration);
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var source = await request.Content.ReadAsStringAsync();
source = source.Replace("json=", "");
source = HttpUtility.UrlDecode(source);
request.Content = new StringContent(source, Encoding.UTF8, "application/json");
return await base.SendAsync(request, cancellationToken);
}
}
Usage:
[AllowAnonymous]
[HttpPost]
public IHttpActionResult SaveData(CallDetails callDetails)
Downside - you'll need to define custom route for it:
config.Routes.MapHttpRoute(
name: "save_data",
routeTemplate: "save_data",
defaults: new { controller = "YourController", action = "SaveData" },
constraints: null,
handler: new NormalizeHandler(config)
);
You don´t forget to decode the url encoded before use JObject.Parse ?, it´s maybe works. And the properties of the object don´t match the json atributes
Json.NET by NewtonSoft can help you deserialize an object. If your json property names don't match your actual class names you can write a custom converter to help.
EDIT
You could try this if you are on MVC6. Change your parameter from type Tmp to type CallDetails and mark it with attribute [FromBody], like this:
public IHttpActionResult SaveData([FromBody]CallDetails details)
For example look at the section "Different model binding" in this post. However, I'm still thinking that you will need to deserialize manually because the property names of class CallDetails don't exactly match the incoming JSON properties.