Refit with querystring array parameter - c#

I am using Refit as my rest client but i am facing a problem with this rest api, which has a array of complex objects inside a QueryString parameter.
public async Task<IActionResult> GetHierarchy([FromQuery] SearchParameter<RestHierarchy>[]? searchParameters, int pageNumber = 1, int pageSize = 50)
the SearchParameter class is defined as follow
public class SearchParameter<T>
{
public string FieldName { get; set; }
public LogicalOperators LogicalOperator { get; set; }
public string? Value1 { get; set; }
public string? Value2 { get; set; }
[JsonIgnore]
private Type? FieldType
{
get
{
Type ClassType = typeof(T);
var member = ClassType.GetMembers().FirstOrDefault(a => a.Name == FieldName);
Type type = member is PropertyInfo info ? info.PropertyType : ((FieldInfo)member).FieldType;
return type;
}
}
public override string ToString()
{
...
}
}
I am trying to configure Refit client to get a request like this
/hierarchies/GetHierarchy?pageNumber=1&pageSize=10&searchParameters[0].FieldName=Id&searchParameters[0].LogicalOperators=Equals&searchParameters[0].Value1=56334850-7940-4D89-6394-08DB036BBCE0
which is working properly from Postman.
I tried configuring the Refit call as follow
[Get("/Hierarchies/GetHierarchy")]
[Headers("Authorization: Bearer")]
Task<List<RestHierarchy>> GetHierarchyAsync([Query(CollectionFormat.Multi)] SearchParameter<RestHierarchy>[]? searchParameters = null);
but the request is not being constructed properly, as I obtain from Refit a request like this
[89efdeb8-3098-4c33-888a-97f974784823 - Request]========Start==========
[89efdeb8-3098-4c33-888a-97f974784823 - Request] GET /api/onestorebackoffice.businessRules/Hierarchies/GetHierarchy?Length=1&LongLength=1&Rank=1&SyncRoot=Id%20%3D%3D%20%228efee3e4-e187-4ec4-04d7-08db039b4f5b%22&IsReadOnly=False&IsFixedSize=True&IsSynchronized=False https/1.1
[89efdeb8-3098-4c33-888a-97f974784823 - Request] Host: https://localhost
[89efdeb8-3098-4c33-888a-97f974784823 - Request] Authorization: Bearer ...
[89efdeb8-3098-4c33-888a-97f974784823 - Request] Duration: 00:00:05.2867317
[89efdeb8-3098-4c33-888a-97f974784823 - Request]==========End==========
Note that if I modify the Rest API using a single SearchParameter, all works properly.
Can anyone help me solving this problem?
Thanks

Related

Deserializing the JSON object passed from Ajax to ASP.NET MVC

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;
}

.NET Core API gets null from Angular POST

I'm trying to POST data from Angular to my .NET Core API, but the incoming data is always null. See my code:
Here is my POST from Angular:
public insertCategory(categoryToInsert: ICategoryDTO): Observable<ICategoryDTO> {
const body: string = JSON.stringify(categoryToInsert);
return this.httpClient.post<ICategoryDTO>(this.apiURL + 'Categories/new', categoryToInsert);
}
ICategoryDTO being an object like this:
export interface ICategoryDTO {
Id: number;
Name: string;
}
export default ICategoryDTO;
//exemple object:
{Id: null, Name: "yyuyuy"}
This is what my API endpoint looks like on the .NET Core side:
[HttpPost("new")]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
[ProducesResponseType(409)]
[ProducesResponseType(500)]
public IActionResult CreateWithAuthor([FromBody] CategoryDTO data)
{
var a = 1;
return Ok(this._iCategoryBusiness.CreateWithAuthor(data));
}
CategoryDTO being defined by this class:
using System;
namespace Back.DTO
{
public class CategoryDTO
{
public int Id { get; set; }
public string Name { get; set; }
}
}
My problem: I have a breakpoint at var a = 1 and on that breakpoint data is always null when performing the post from my front-end. Doing it from Swagger works.
What I have tried: I have tried to stringify my object before passing it, which did nothing. I have also tried to pass a header along, both with stringified and non-stringified object, which did not work either. I changed the ICategoryDTO Id type from string to number but that did nothing.
Here's the header I have tried:
header = new HttpHeaders()
.set('Content-type', 'application/json');
With a request like this:
public insertCategory(categoryToInsert: ICategoryDTO): Observable<ICategoryDTO> {
const body: string = JSON.stringify(categoryToInsert);
return this.httpClient.post<ICategoryDTO>(this.apiURL + 'Categories/new', body, {headers: this.header});
}
Didn't work, same result.
Don't know what I'm doing wrong.
I fixed it by making my Id parameter not null, but 0 instead. I guess this makes sense because it isn't nullable on the .NET end. This took me two hours.

Angular 8 post formData to ASP.NET Core API cannot bind IEnumerable/List

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);

WebAPI post JSON string and map it to model

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.

JSON.Net in Api controllers w/ param containing Dictionary is always null

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.

Categories

Resources