I am attempting to pass a complex object in a GET request. The C# model object looks like this:
public class GetRequest
{
public string ID { get; set; }
public List<string> RequestedFields { get; set; }
public Dictionary<string, List<string>> RequestedGrids { get; set; }
}
Looking at the request in Chrome I see the following under "Query String Parameters":
ID: 1
RequestedFields[]: someTxtField
RequestedFields[]: someOtherField
RequestedGrids[someGrid][]: keyColumn
RequestedGrids[someGrid][]: someDataColumn
I would expect such a request to be deserialized correctly as the parameter in the following action:
[HttpGet]
[ActionName("get")]
public Dictionary<string,object> GetStuff([FromUri] GetRequest get_req)
However, whenever a request enters this action, the RequestedGrids property of the parameter always has a count of 0, while the other properties are populated fine. Is there a way to make this work?
Addition
The object going into the JQuery $.get call looks like this:
{ ID: p_key, RequestedFields: p_page.dataIds, RequestedGrids: p_page.grids }
Where RequestedFields is a plain array of strings and RequestedGrids is a plain object where each object property value is an array of strings.
When you are needing to send this much data, especially complex data, it is best to send the data as part of the body of a POST request. This way, you can make sure that the structure matches completely and can easily convert to/from JSON format.
What does the input look like on the GET? Dictionary types are sort of painful to deserialize in general - see Deserialize JSON string to Dictionary<string,object> for details...
The easiest method to accomplish this seems to be to just pass a JSON query string instead of using the auto-binding. So the action looks like this:
[HttpGet]
[ActionName("get")]
public Dictionary<string, object> GetStuff(string get_str)
{
var get_req = JsonConvert.DeserializeObject<GetRequest>(get_str);
//Do ur stuff
}
And JS looks like this:
$.get('<action path>', { get_str: JSON.stringify(get_obj) }, ...
Related
I have a web api core project that if I send just the list parameter than the API receives the values, however if I send both parameters that the controller is looking for then both parameters are seen as null
My contoller:
[HttpPost]
[Route("/jobApi/RunBD")]
public int RunBDReport([FromBody]int month, [FromBody] IEnumerable<ClientModel> clients)
{
billingDetailCycle objBillDetail = new billingDetailCycle();
if (ModelState.IsValid)
{
return objBillDetail.Run(clients.ToList(), month);
}
else
{
return 500;
}
}
ClientModel:
public class ClientModel
{
public string BlockOfBus { get; set; }
public string ClientId { get; set; }
public string Location { get; set; }
public string SuppressSsn { get; set; }
}
The request I am sending:
{"month":7,
"ClientModel":[{"blockOfBus":"XXX",
"clientId":"123456",
"location":"",
"suppressSsn":"N"}]}
This causes both parameters to be seen as null by the controller, however if I send my request like this:
[{"blockOfBus":"XXX",
"clientId":"123456",
"location":"",
"suppressSsn":"N"}]
Then the controller is able to see the list object I am sending (however it obviously returns 500 as the model is not valid)
[FromBody] can only be used once since the request body can only be read once.
Don't apply [FromBody] to more than one parameter per action method. Once the request stream is read by an input formatter, it's no longer available to be read again for binding other [FromBody] parameters.
Reference Model Binding in ASP.NET Core
Create a single model that matches the expected data.
public class DbReport {
public int month { get; set; }
public ClientModel[] ClientModel { get; set; }
}
And update the action accordingly
[HttpPost]
[Route("/jobApi/RunBD")]
public int RunBDReport([FromBody]DbReport report) {
billingDetailCycle objBillDetail = new billingDetailCycle();
if (ModelState.IsValid) {
return objBillDetail.Run(report.ClientModel.ToList(), report.month);
} else {
return 500;
}
}
There can be only one parameter modified with [FromBody] attribute. So you need to either modify your method like this :
[Route("/jobApi/RunBD/{month}")]
public int RunBDReport(int month, [FromBody] IEnumerable<ClientModel> clients)
Then make the request like this :
url :/jobApi/RunBD/7
body :
[{"blockOfBus":"XXX",
"clientId":"123456",
"location":"",
"suppressSsn":"N"}]
Or modify both your method and model like this :
public class BdPayload{
public int Month {get; set;}
public IEnumerable<ClientModel> ClientModel {get;set;}
}
[Route("/jobApi/RunBD")]
public int RunBDReport( [FromBody] BdPayload model)
and then you can use the second request's body.
Try:
{"month":7,
"clients":[{"blockOfBus":"XXX",
"clientId":"123456",
"location":"",
"suppressSsn":"N"}]}
It looks like your ClientModel enumerable is mistitled in the payload
Try changing the route to:
[Route("/jobApi/RunBD/{month}")]
public int RunBDReport([FromUri]int month, [FromBody] IEnumerable<ClientModel> clients)
The payload needs to be passed as an array, like in Jonathan's answer.
There are few simple rules that help you get through these kind of issues when trying to pass data to your Web API endpoint. These are the default rules based on which the parameter binding happens. Based on these rules, you need to be applying the attributes like [FromBody] and [FromUri]
GET method call takes both primitive and complex types as a part of the query string
POST method call takes a primitive type parameter by default in the query string and the complex type needs to be passed as a part of the request body.
PUT and PATCH follow similar default rules as that of POST.
DELETE method's default rules are inline with the GET method.
Here by primitive types, I mean types like int and complex types are the classes that we create.
You can tackle the problem that you're dealing with by applying any of the solutions that others have already mentioned -- like moving your complex type into your request body and passing the primitive type through the query string OR wrapping both the primitive and complex types into a single model and deserialize the request body to the model type (which is done as a part of the parameter binding inherently).
The following documentation illustrates how to use the Options Pattern in ASP.NET Core to create a strongly-typed options class to access JSON configuration data.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options
This C# class
public class MyOptions
{
public string Option1 { get; set; }
public int Option2 { get; set; }
}
represents a portion of this JSON configuration file (the first two root-level properties)
{
"option1": "value1_from_json",
"option2": -1,
"subOptions": {
"subOption1": "subvalue1_from_json",
"subOption2": 200
}
}
I want to add another C# property named SubOptions to the MyOptions class that returns the raw data of the subOptions JSON sub-section, without creating a strongly-typed class for that sub-section of the JSON configuration file, but I don't know what data type to use (or if it's even possible to do that).
If I use string, I get a runtime error when service.Configure<MyOptions>(Configuration); is called, saying System.InvalidOperationException: 'Cannot create instance of type 'System.String' because it is missing a public parameterless constructor.
If I use object or dynamic, I get a different runtime error when service.AddSingleton(cfg => cfg.GetService<IOptions<MyOptions>>().Value); is called to register an instance of the MyOptions class, saying System.ArgumentNullException: 'Value cannot be null. Parameter name: type'
If I use JObject, I get {} back when I access the SubOptions property of the MyOptions object that's injected into my API Controller.
I know I can convert the sub-section to a JSON string property by escaping the sub-section data, but I want to avoid treating the sub-section as a string, and instead leave it as raw JSON.
Is it possible to do what I want to do? Is there a data type that works with the Options Pattern that will allow me to access the JSON sub-section without having to create a strongly-typed class?
*For background, I'm trying to create an API Controller method that returns the content of the JSON sub-section to the API client. I want to avoid using a strongly-typed class for the sub-section, so that the JSON configuration file can be edited on the server, adding new properties and values to the sub-section that will be returned to the API client, without having to update the C# code and redeploy the API service. In other words, I want the JSON sub-section to be 'dynamic', and just pull it and send it to the client. *
You can sorta do get raw configuration object by forcing your SubOptions property to be of IConfigurationSection:
public class MyOptions
{
public string Option1 { get; set; }
public int Option2 { get; set; }
public IConfigurationSection SubOptions { get; set; } // returns the "raw" section now
public string SubOptions_take2 { get; set; }
}
so you would still bind your strongly typed object in your Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration);
...
}
but this is where luck appears to run out, because even though it is a whole section - as far as options binder is concerned it's all been deserialised and parsed into hierarchy of values already. There appears to be no easy way to reassemble it back into one string. Injecting IOptionsMonitor allows you to get the values by opting for .GetChildren() but I could not find an obvious way to get the whole hierarchy without writing custom code to just recursively walk it (which I will leave out for you to play with should you feel this is worth the effort):
public IndexModel(IOptionsMonitor<MyOptions> options)
{
_options = options.CurrentValue;
var subOptions = _options.SubOptions as ConfigurationSection;
var children = subOptions.GetChildren(); // you see, the config has already been parsed into this hierarchy of items - it's too late to get the raw string value
var s = JsonConvert.SerializeObject(children);
// will produce something like this JSON:
//[{"Path":"SubOptions:subOption1","Key":"subOption1","Value":"subvalue1_from_json"},{"Path":"SubOptions:subOption2","Key":"subOption2","Value":"200"}]
}
one way around it will be to actually encode your json as string in the config file:
"subOptions_take2": "{\"subOption1\": \"subvalue1_from_json\",\"subOption2\": 200}"
then you can just grab it later:
public IndexModel(IOptionsMonitor<MyOptions> options)
{
_options = options.CurrentValue;
var subOptions_string = _options.SubOptions_take2;// this is valid json now: {"subOption1": "subvalue1_from_json","subOption2": 200}
}
I guess, you can use JObject from Newtonsoft.Json package - it's the default JSON parser & serializer in Asp.Net Core
How to post a dynamic JSON property to a C# ASP.Net Core Web API using MongoDB?
It seems like one of the advantages of MongoDB is being able to store anything you need to in an Object type property but it doesn't seem clear how you can get the dynamic property info from the client ajax post through a C# Web API to MongoDB.
We want to allow an administrator to create an Event with Title and Start Date/Time but we also want to allow the user to add custom form fields using Reactive Forms for whatever they want such as t-shirt size or meal preference... Whatever the user may come up with in the future. Then when someone registers for the event, they post EventID and the custom fields to the Web API.
We can have an Event MongoDB collection with _id, event_id, reg_time, and form_fields where form_fields is an Object type where the dynamic data is stored.
So we want to POST variations of this JSON with custom FormsFields:
Variation 1:
{
"EventId": "595106234fccfc5fc88c40c2",
"RegTime":"2017-07-21T22:00:00Z",
"FormFields": {
"FirstName": "John",
"LastName": "Public",
"TShirtSize": "XL"
}
}
Variation 2:
{
"EventId": "d34f46234fccfc5fc88c40c2",
"RegTime":"2017-07-21T22:00:00Z",
"FormFields": {
"Email": "John.Public#email.com",
"MealPref": "Vegan"
}
}
I would like to have an EventController with Post action that takes a custom C# EventReg object that maps to the JSON above.
EventController:
[HttpPost]
public void Post([FromBody]EventReg value)
{
eventService.AddEventRegistration(value);
}
EventReg Class:
public class EventReg
{
public EventReg()
{
FormFields = new BsonDocument();
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string EventRegId { get; set; }
[BsonElement("EventId")]
[BsonRepresentation(BsonType.ObjectId)]
public string EventId { get; set; }
[BsonElement("reg_time")]
public DateTime RegTime
{
set; get;
}
[BsonElement("form_fields")]
public MongoDB.Bson.BsonDocument FormFields { get; set; }
}
EventService
public string AddEventRegistration(EventReg eventReg)
{
this.database.GetCollection<EventReg>("event_regs").InsertOne(eventReg);
return eventReg.EventRegId;
}
Right now, if I post to the controller, my EventReg is null because it must not know how to map my JSON FormFields properties to a BsonDocument.
What type can I use for FormFields?
Can I have the FormFields property be a BsonDocument and is there an easy way to map the Web API parameter to that?
Is there an example of how some custom serializer might work in this case?
We could maybe use a dynamic type and loop through the posted properties but that seems ugly. I have also seen the JToken solution from a post here but that looks ugly also.
If MongoDB is meant to be used dynamically like this, shouldn't there be a clean solution to pass dynamic data to MongoDB? Any ideas out there?
In ASP.NET Core 3.0+ Newtonsoft.Json is not the default JSON serializer anymore. Therefore I would use JsonElement:
[HttpPost("general")]
public IActionResult Post([FromBody] JsonElement elem)
{
var title = elem.GetProperty("title").GetString();
...
The JToken example works to get data in but upon retrieval it causes browsers and Postman to throw an error and show a warning indicating that content was read as a Document but it was in application/json format. I saw the FormFields property being returned as {{"TShirtSize":"XL"}} so maybe double braces was a problem during serialization.
I ended up using the .NET ExpandoObject in the System.Dynamic namespace. ExpandoObject is compatible with the MongoDB BsonDocument so the serialization is done automatically like you would expect. So no need for weird code to manually handle the properties like the JToken example in the question.
I still believe that a more strongly typed C# representation should be used if at all possible but if you must allow any JSON content to be sent to MongoDB through a Web API with a custom C# class as input, then the ExpandoObject should do the trick.
See how the FormFields property of EventReg class below is now ExpandoObject and there is no code to manually handle the property of the object being saved to MongoDB.
Here is the original problematic and overly complex JToken code to manually populate an object with standard type properties and a dynamic FormFields property:
[HttpPost]
public void Post([FromBody]JToken token)
{
if (token != null)
{
EventReg eventReg = new EventReg();
if (token.Type == Newtonsoft.Json.Linq.JTokenType.Object)
{
eventReg.RegTime = DateTime.Now;
foreach (var pair in token as Newtonsoft.Json.Linq.JObject)
{
if (pair.Key == "EventID")
{
eventReg.EventId = pair.Value.ToString();
}
else if (pair.Key == "UserEmail")
{
eventReg.UserEmail = pair.Value.ToString();
}
else
{
eventReg.FormFields.Add(new BsonElement(pair.Key.ToString(), pair.Value.ToString()));
}
}
}
//Add Registration:
eventService.AddEventRegistration(eventReg);
}
}
Using ExpandoObject removes the need for all of this code. See the final code below. The Web API controller is now 1 line instead of 30 lines of code. This code now can insert and return the JSON from the Question above without issue.
EventController:
[HttpPost]
public void Post([FromBody]EventReg value)
{
eventService.AddEventRegistration(value);
}
EventReg Class:
public class EventReg
{
public EventReg()
{
FormFields = new ExpandoObject();
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string EventRegId { get; set; }
[BsonElement("event_id")]
[BsonRepresentation(BsonType.ObjectId)]
public string EventId { get; set; }
[BsonElement("user_email")]
public string UserEmail { get; set; }
[BsonElement("reg_time")]
public DateTime RegTime{ get; set; }
[BsonElement("form_fields")]
public ExpandoObject FormFields { get; set; }
}
EventService:
public string AddEventRegistration(EventReg eventReg)
{
this.database.GetCollection<EventReg>("event_regs").InsertOne(eventReg);
return eventReg.EventRegId;
}
If you are not sure about the type of Json you are sending i.e. if you are dealing with dynamic json. then the below approach will work.
Api:
[HttpPost]
[Route("demoPath")]
public void DemoReportData([FromBody] JObject Jsondata)
Http client call from .net core app:
using (var httpClient = new HttpClient())
{
return await httpClient.PostAsJsonAsync(serviceUrl,
new demoClass()
{
id= "demoid",
name= "demo name",
place= "demo place"
};).ConfigureAwait(false);
}
You can define FormFields as a string and send data to the API in string format after converting JSON to string:
"{\"FirstName\":"John\",\"LastName\":\"Public\",\"TShirtSize\":\"XL\"}"
Then in your controller parse the string to BsonDocument
BsonDocument.Parse(FormFields);
I would use AutoMapper to automate the conversion between the dto and the document
I have a .net mvc 4 webapi project that I'm trying to pass an array of an object to a method on my controller.
I've found some examples here on SO that talk about needing to set my object's properties with: param1=whatever¶m2=bling¶m3=blah.
But I don't see how I can pass in a collection using that.
Here is my method signature. Notice I've decorated the argument with the [FromUri] attribute.
public List<PhoneResult> GetPhoneNumbersByNumbers([FromUri] PhoneRequest[] id)
{
List<PhoneResult> prs = new List<PhoneResult>();
foreach (PhoneRequest pr in id)
{
prs.Add(PhoneNumberBL.GetSinglePhoneResult(pr.PhoneNumber, pr.RfiDate, pr.FinDate, pr.State));
}
return prs;
}
here is my simple PhoneRequest object:
public class PhoneRequest
{
public string PhoneNumber { get; set; }
public string RfiDate { get; set; }
public string FinDate { get; set; }
public string State { get; set; }
}
and here's a sample of what I'm using to pass in:
http://localhost:3610/api/phonenumber/getphonenumbersbynumbers/
[{"PhoneNumber":"8016667777","RfiDate":"","FinDate":"2012-02-11","State":"UT"},
{"PhoneNumber":"8018889999","RfiDate":"2012-12-01","FinDate":"","State":"UT"}]
using this comes back with "bad request"
I also tried this
http://localhost:3610/api/phonenumber/getphonenumbersbynumbers?
id=[{"PhoneNumber":"8016667777","RfiDate":"","FinDate":"2012-02-11","State":"UT"},
{"PhoneNumber":"8018889999","RfiDate":"2012-12-01","FinDate":"","State":"UT"}]
which does reach the method, but the array is null.
how can I pass in an array of my PhoneRequest object to my Web API method?
Try passing the PhoneRequest[] from the uri in this format:
http://localhost:3610/api/phonenumber/getphonenumbersbynumbers?
id[0][PhoneNumber]=8016667777&id[0][FinDate]=2012-02-11&id[0][State]=UT&
id[1][PhoneNumber]=8018889999&id[1][RfiDate]=2012-12-01&id[1][State]=UT
I suggest you use POST for this.
As you query string grows, you will run into problems with the maximum length of the URL, which is browser dependent.
If you have a lot of parameters to pass, a POST is perfectly acceptable even if you are really only GETting data. What you will lose, however, is the ability for the user to bookmark a particular page with the query string.
I created a custom model binder, the FieldValueModelBinder class, which can effectively pass any object containing nested array or generic list types of data with query strings having field-name pairs without imbedding any JSON and XML structures. The model binder can resolve all issues discussed above. Since this question was extended by the question ID 19302078, you can see details of my answer in that thread.
Ok, so I am using MVC 3 and it is great at de-serializing a JSON data set into a strongly typed object that is passed to my controller action. Unfortunately I have not found a solution to a more dynamic case.
Does the built in Json de-serialisation and classes have support for an "undefined" property set? For example lets say I have some fixed data like name and age, but I also want to pass down a dynamically created rating list where the user could enter (or select) a movie and set a rating value in a table.
The model data structure could be something like this:
public class UserRatings
{
public string Name { get; set; }
public int Age { get; set; }
public Dictionary<string,int> Ratings { get; set; }
}
But assuming that my Json dataset looks like this from javascript:
var data = { Name: name, Age: age, Ratings: rating };
Where the rating variable is a dynamically constructed object that consist of the name (or id) of of the movie as key and rating as number. Naturally de-serialization of this in the controller action will not be successful as it does not understand the mapping of Ratings to the rather complex dictionary object. But is there a generic Json collection that I could use instead for Ratings as an intermediate format?
I have tried making the Ratings object a Json string in javascript and just sending a string down, but I am unable to find a "factory" or something that can make a Json structure in C# that I can iterate over to get the data out. The classes Json and JsonResult does not help me in this regard it seems. Basically how can I use the built in Json support in MVC to do my own de-serialisation into some generic Json collection object?
You Could Use JavaScriptSerializer or DataContractSerializer with Some ActionFilters. They're very flexible
See my answer Passing dynamic json object to C# MVC controller basically using a dynamic type and a ValueProviderFactory is the cleanest way to deserialize Json to something more dynamic.
There's another option which i prefer using for being neater...(we eliminate the step of getting data from the request stream)
Here's a code sample
Cat catObj = new Cat();
if (TryUpdateModel<Cat>(catObj))
{
//do stuff
}
else
{
//invalid input
}
The TryUpdateModel resides in the controller namespace and hence no need to add any additional reference.
If you just need the Json sent in as part of the request you could obtain it using the following block of code(you could also obtain it from Request.Form)
using (StreamReader reader = new StreamReader(Request.InputStream))
{
var inputJson = reader.ReadToEnd();
}