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 ;-)
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).
I am working in Asp.Net Core Web API. I write a GET API. In API, the input parameters have come with the $ symbol from the URL. But in c#, if parameter come with dollar symbol means, not able to access. How to take the input values if parameters have a $ symbol in front of the parameter name?
public class DataProperties
{
[JsonProperty("skip")]
public int Skip { get; set; }
[JsonProperty("top")]
public int Top { get; set; }
}
Controller
[HttpGet("list")]
public async Task<ActionResult> ListAsync([FromQuery]DataProperties data)
{
}
URL Format
/api/v1/list?$Skip=10&$Top=10
It works fine when giving without $ symbol like
/api/v1/tickets/list?Skip=10&Top=10
Note
I am using ODataAdaptor. So when using OData Adaptor, the parameters automatically come with the $ symbol in front of the parameter name.
Because C# does not allow $ at the start of a variable name, but this is a valid key for a parameter in a query string, you can decorate your parameters with the [FromQuery] attribute, and specify the name explicitly, for example, if implementing the standard OData $skip and $top parameters:
public async Task<IActionResult> Get([FromQuery(Name = "$top")] string top, [FromQuery(Name = "$skip")] string skip)
{
// ...
}
I have an action:
[HttpGet]
[Route("foo")]
public ActionResult Foo([FromQuery] MyClass request)
{
var image = ToImage(WidgetType.MedianSalesPriceSqft, request);
return File(image.ToByteArray(), "image/png");
}
below MyClass is defined:
public class MyClass {
[DefaultValue("90210")]
public string Zip { get; set; }
[DefaultValue("5361 Doverton Dr")]
public string StreetAddress { get; set; }
}
When I hit /swagger/index.html and want to try out this API, I always have to enter the StreetAddress and Zip values, even though I have default values defined.
Swagger currently provides a schema filter that let's you provide default values for object properties as long as they are not set to be [FromQuery]. Am I missing something simple?
It looks like the problem has been fixed on the beta version:
https://www.nuget.org/packages/Swashbuckle.AspNetCore/5.0.0-rc2
Lot of changes on the beta version, I had a couple of DocumentFilter and needed to be refactored,
I had to comment some stuff out that could not figure out how to do on the beta.
I added your same action and class, it looks like this:
Once you click on the try it out the values are populated
Just if you needed my code is here:
https://github.com/heldersepu/csharp-proj/tree/master/WebApi_NetCore
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.
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 ;).