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).
Related
I am trying to create a request in postman to upload files to a dotnet core endpoint that expects the following shape:
public class MyDto
{
public DateTime Time { get; set; }
public List<Logs> Files { get; set; } = null!;
public class Logs
{
public ServiceEnum Service { get; set; }
public List<IFormFile> LogFiles { get; set; }
}
}
Function signature of action:
[HttpPost("{id}/log")]
public async Task<ActionResult> SaveLogs([FromRoute] string id, [FromForm] MyDto myDto)
My postman request looks like this:
When I try debugging this I can see that myDto.Files[0].Service is set yet, myDto.Files[0].LogFiles in null.
I was expecting that uploading multiple files would be mapped into myDto.Files[0].LogFiles from the request. I might be formatting the keys in the request wrong but I have tried multiple different key formats at this point.
I test with other simple and complex types, they can use square brackets and post successfully(Except for IFormFile). Maybe it is by design in Postman. Any way, no matter what type it is. You can always post like xx.xx.xx.
Change your post data like below:
Hello,
I have api GET-method /rating (ASP.Net WebApi 2.1), which accepts objects of type ChartPageRequest:
// comments're removed for readability
public sealed class ChartPageRequest
{
public DateTime? From { get; set; }
public DateTime? To { get; set; }
public string Cursor { get; set; }
[Range(-100, 100)]
public int Take { get; set; } = 10;
}
/rating method has following signature:
[HttpGet]
[Route("rating")]
[ResponseType(typeof(ChartPage))]
[ValidateModelState]
public async Task<IHttpActionResult> GetTranslationRatingChartAsync([ModelBinder] ChartPageRequest model)
{
// body here
}
And ValidateModelState attribute is just a custom attribute which returns custom response when ModelState isn't valid. It doesn't check anything by itself except HttpActionContext.ModelState.IsValid property.
This api method works fine except one case - when client explicitly passes null value to DateTime? properties of ChartPageRequest, e.g.:
/rating?from=2016-07-08 12:01:55.604&to=null
In this case ValidateModelState attribute registers invalid ModelState with following message: The value 'null' is not valid for To.
I've found this kind of problem quite popular, but haven't found any good workarounds without creating custom model binder. So here's the questions:
Is there another approach without custom model binder?
In case there isn't, how can I take only "accepting null" job and leave DateTime parsing to default binder in my custom binder?
Do I need to accept those nulls at all? All clients are developing by my colleagues so I can force them to not send it. Is it good practice after all?
Thanks!
I didn't validate this method, but I guess you can try parse it yourself, though I think it's not a convenient way, first use a string to get the To:
public string To { get; set; }
And give another property DateTimeTo and parse To yourself:
public DateTime? DateTimeTo { get; set; }
public void ParseTo()
{
if(To.ToLower() == "null")
DateTimeTo = null;
else
DateTimeTo = Convert.ToDateTime(To);
}
And DateTimeTo is the parsed result property.
I recently ran into some Asp.Net WebApi parsing body issue, the parsing doesn't work very well in some condition.
I have Web API service deployed and and consuming in another web application. Web API method take complex object (List object) and results also complex object.
So I created local models for Input parameter and results model to match with Web API complex objects in web application. then I passed JsonConvert.SerializeObject for that parameter. But when I debug in Web API that parameter value showing null.
Web application
[Serializable]
public class PreferencesInput
{
public string ShortName { get; set; }
public string ShortNameDescription { get; set; }
.....
}
[Serializable]
public class PreferencesOuput
{
public bool Status { get; set; }
public string Error { get; set; }
}
public class HomeController : Controller
{
public ActionResult Index()
{
RunAsync().Wait();
return View();
}
private static async Task RunAsync()
{
var inputs = new List<PreferencesInput>();
var input = new PreferencesInput
{
ShortName = "REGION",
ShortNameDescription = "Geographical regions",
OptedInFlag = true
};
inputs.Add(input);
....
...
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:8585/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
HttpResponseMessage response = await client.GetAsync("preferences/updatepreferences/?id='3016523'
&optInInterestAreas=" + JsonConvert.SerializeObject(inputs) +
"&solicitationFlag=false").ConfigureAwait(false);;
if (response.IsSuccessStatusCode)
{
string results = await response.Content.ReadAsStringAsync();
var myList = JsonConvert.DeserializeObject<List<PreferencesOuput>>(results);
}
web API
[Route("preferences/updatepreferences")]
[HttpGet]
public PreferencesOuput UpdatePreferences(string id, IEnumerable<PreferencesInput> optInInterestAreas, bool solicitationFlag)
{
.....
}
Only difference is Web application Input model has less parameters than the Web API model.
What I am doing wrong here?
IEnumerable<PreferencesInput> optInInterestAreas is null
update
I can see serialization date like below before sending to Web API call, In Web API method it is showing null, rest of the parameters are showing correct.
[{"ShortName":"REGION","ShortNameDescription":"Geographical regions","ShortSubName":null,"Description":null,"OptedInFlag":true},
{"ShortName":"REGION","ShortNameDescription":"Asia Pacific","ShortSubName":"ASIA_PACIFIC","Description":null,"OptedInFlag":true},
{"ShortName":"REGION","ShortNameDescription":"Canada","ShortSubName":"CANADA","Description":null,"OptedInFlag":true}]
You could try to specify the route with parameters. Something like:
[Route("preferences/updatepreferences/{id}/{optInInterestAreas}/{solicitationFlag:bool}")]
Your optInInterestAreas parameter is null because in Web API, the parameter binding rules specify that anything other than a "simple" parameter type (string, int, etc) is assumed to be passed in the body, not the route or query string as you're doing. You could get this to work by using the [FromUri] attribute on that parameter or by defining a custom type converter, but I would highly recommend changing your API as it does not follow generally accepted best practices.
By convention, GET is assumed to be side-effect-free, but I'm guessing something called UpdatePreferences almost certainly changes data. I would consider using a different verb and passing the updated preferences in the body. POST is better, but if you want it to be truly RESTful, you should ensure that the URI uniquely identifies the resource and use PUT.
I would start by changing your input model to something like this:
public class PreferencesInput
{
public IList<InterestArea> InterestAreas { get; set; }
public bool SolicitationFlag { get; set; }
}
public class InterestArea
{
public string ShortName { get; set; }
public string ShortNameDescription { get; set; }
...
}
Then define your API action like this:
[Route("preferences/{id}")]
[HttpPut]
public PreferencesOuput UpdatePreferences(string id, PreferencesInput preferences)
{
...
}
As you can see, the URI now uniquely identifies the thing, and the verb specifies what you want to "do"; in this case, completely replace whatever is at that URI (if anything) with the thing you are passing.
Side-note:
On the MVC side, calling Wait() in your Index action is blocking a thread while waiting for your async method to complete. That's a serious invitation for deadlocks. Async only works properly if you go "all the way" with it. In this case it's incredibly easy - just change the Index action to:
public async Task<ActionResult> Index()
{
await RunAsync();
return View();
}
I'm trying to setup Facebook Notification API.
I have an APi Controller with RealtimeUpdate() - Get, will be used just for verification of endpoint.
As is written in Fb Docs:
Firstly, Facebook servers will make a single HTTP GET to your callback
URL when you try to add or modify a subscription. A query string will
be appended to your callback URL with the following parameters:
hub.mode - The string "subscribe" is passed in this parameter
hub.challenge - A random string
hub.verify_token - The verify_token value you specified when you created the subscription
From here I have a problem - I have no idea how to handle this dots in query params names. I google a lot, and did not find the solution.
Can somebody please say to me how to get data from this hub.* values?
Thank you!
Update your method signature using the FromUri attributes, like this:
public string Get(
[FromUri(Name="hub.mode")]string mode,
[FromUri(Name="hub.challenge")]string challenge,
[FromUri(Name="hub.verify_token")]string verifyToken
)
{
/* method body */
}
The parameters will be bound from the query string using the specified names.
Slightly different form Steve's answer.
In case you need to have a normal controller instead of an Api one (if you are inheriting from Controller rather tha ApiController), the follow worked for me:
namespace Name
{
public class Hub
{
public string Mode { get; set; }
public string Challenge { get; set; }
// ReSharper disable once InconsistentNaming
public string Verify_Token { get; set; }
}
public class FacebookWebHooksController : Controller
{
[System.Web.Http.HttpGet, ActionName("Callback")]
[AllowAnonymous]
public ContentResult CallbackGet(Hub hub)
{
if (hub.Mode == "subscribe" && hub.Verify_Token == "YOUR_TOKEN")
return Content(hub.Challenge, "text/plain", Encoding.UTF8);
return Content(string.Empty, "text/plain", Encoding.UTF8);
}
}
[HttpPost]
[AllowAnonymous]
public ActionResult Callback()
{
Request.InputStream.Seek(0, SeekOrigin.Begin);
var jsonData = new StreamReader(Request.InputStream).ReadToEnd();
}
}
The Model Binder has some illegal characters, of which I believe '.' is a special character, used primarily to bind complex objects. When all else fails, you can look at Request.QueryString and Request.Form directly, just like in ASP.NET WebForms.
You can also try using a complex object that has a Property named hub with subproperties mode, challenge, and verify_token. This might just do the trick.
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.