Binding request parameters to a specific method parameter - c#

In ASP.NET MVC 2 (yes, TWO, I'm using MONO for this), I would like to know if it is at all possible to bind multiple Request parameters into an Action method parameter.
Let me give an illustration.
I'm passing 2 parameters (using whatever method I like, GET, POST, etc.):
Name
Guid
Is there a way to bind those parameters to this:
public JsonResult MyMethod(NameClass identifier)
Instead of this:
public JsonResult MyMethod(string name, string guid)
Using this?
public class NameClass
{
public string Guid { get; set; }
public string Name { get; set; }
}

Absolutely. You simply have to name your fields using dot notation as if you were going to access the property from inside the method. This means that the Guid field is named identifier.Guid and the Name field identifier.Name. It is too bad that you can't take advantage of strongly-typed user controls however ;).

Related

Passing List object and int to web api

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

.NET Core: Order of optional parameters in GET request

I have a .NET Core REST api, and I am requested to handle a GET request where the route template of optional parameter should be like …/parameterName/parameterValue/....
What I've tried so far is something like this:
[HttpGet("{manatoryParam1}/{manatoryParam2}/{manatoryParam3}/optionalParamName1/{optionalParamName1?}/optionalParamName2/{optionalParamName2?}")]
public ActionResult Get(string manatoryParam1, string manatoryParam2, string optionalParamName1, int optionalParamName2)
But I have two issues:
How can I escape the first optional parameter, because when I make the following request: url/value1/value2/optionalParamName1//optionalParamName2/value I get 404 error
What if I have a long list of optional parameters, and I want the last optional parameter only, should I enter all the previous optional parameters, or is there a another way to enter only the needed paramaters?
This looks wrong for so many reasons. The best method for dealing with this is to create a class with the properties in that you want to pass, and then perform some logic to extract the values on the server side. Like so:
public class QueryParameters
{
public string manatoryParam1 {get; set;}
public string manatoryParam2 {get; set;}
public string optionalParamName1 {get; set;}
public int optionalParamName2 {get; set;}
}
And then your controller will look like this:
[HttpGet]
public IActionResult Get(QueryParameters query)
Note: You should be returning IActionResult too - it helps with unit tests ;-)

ASP.NET Core 2 MVC App "POCO" Model binding not binding

I've got a pretty basic ASP.NET Core 2 Web App (no razor views, just MvcCore with json responses).
I'm trying to do a pretty simple GET request in Postman and my Controller Action isn't binding the query string parameters to my custom POCO.
here is a sample url which postman tries to hit: http://localhost:51459/orders?Query=iphone&MinimumPrice=22
public class OrderQuery
{
public string Query { get; set; }
public decimal? MinimumPrice { get; set; }
public decimal? MaximumPrice { get; set; }
}
[Route("orders")]
public class OrdersController : ControllerBase
{
[HttpGet("")]
public async Task<ActionResult> GetOrdersAsync(OrderQuery query)
{
// query.Query is null.
// all the properties of query are null.
}
}
Now I can step through the method (i.e. breakpoint is hit), so the route does get 'found' and 'handled'.
Secondly, I've also tried sprinkling [FromQuery] attributes on the properties in the POCO.
Lastly, I've tried changing the case in the request but I thought model biding is case insensitive.
Can anyone see what I'm doing wrong? Is there a particular middleware I should check to see if I've wired up/not wired up?
OMG # me :(
So the variable name in the method signature is query and the (string) querystring key is also query.
The model binding was getting confused with which query do I mean? The property of the OrderQuery class? Or trying to set the string to actual method variable, which it cannot do.
Solution: renamed the entire signature to: public async Task<ActionResult> GetOrdersAsync(OrderQuery orderQuery)
** Note the method signature variable name change **
Doh! :)
TL;DR; Don't name the POCO variable name to a form/querystring/route key.

Instantiate ActionDescriptor

I would like to design a model with some way of specifying a Controller Action to be used for searching. I do not want to specify the action's url as a string because I do not want the app to compile if that action does not exist.
From what I can tell, it seems like I should specify a property of type System.Web.Mvc.ActionDescriptor like so:
public class myModel {
public string id { get; set; }
...
public ActionDescriptor search_action { get; set; }
}
From this SO post I see how to get iterate through all actions on all controllers but I'm having trouble instantiating an ActionDescriptor to set the property.
I was hoping to be able to do some variation of this:
myModel m = new myModel();
m.search_action =
(ActionDescriptor)new myNamespace.Controllers.myController().myAction;
but didn't get anywhere.
Am I missing something? How do you specify a Controller Action as a property of a model? Is this even possible?
You can declare a Func delegate property with the respective Action specific type arguments in your model which you will set with your Action.
public class myModel {
public string id { get; set; }
//Specify your specific Action's type arguments here
public Func<,> search_action { get; set; }
}
First, I'm forced to ask why you need to stored the action itself as part of the model. It seems like the wrong approach; but again I lack the context and the rationale as to why you are trying to do this.
If only want to have the compile safety that the action exists, you might be better off creating an expression method based on the type of the controller and extracting the string action name from it (like this answers suggest).
If in the other hand, you actually want to able to execute this action from the model (even after understanding how bad of an idea this is) using an ActionDescriptor (which only stored information about the action) or a Func<,> (which only stores the IL, not the name, the context or the controller), will only get you half way.
And so again, I ask, why exactly you need to do this?

pass array of an object to webapi

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&param2=bling&param3=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.

Categories