How can I make the possibility to accept the requests in different orders and with some optional parameters?
https://localhost:44314/api/courses/page=2&pageSize=6&language=test&institution=test&area=test
https://localhost:44314/api/courses/page=2&pageSize=6&institution=test&area=test
https://localhost:44314/api/courses/page=2&pageSize=6&area=test&language=test
I have tried as below:
[HttpGet]
[Route("page={page:int}&pageSize={pageSize:int}&language={language?}&institution={institution?}&area={area?}")]
public async Task<ActionResult<CourseViewModel>> ListCourses(int page, int pageSize, string language="", string institution="", string area="")
And I have the error as:
System.ArgumentException: 'An optional parameter must be at the end of the segment. In the segment
'page={page}&pageSize={pageSize}&language={language?}&institution={institution?}&area={area?}',
optional parameter 'language' is followed by '&institution='.
Parameter name: routeTemplate'
Remove the route template and the route table will use the parameters of the action for matching the route via query string in the requested URL
//GET api/courses?page=2&pageSize=6&language=test&institution=test&area=test
//GET api/courses?page=2&pageSize=6&institution=test&area=test
//GET api/courses?page=2&pageSize=6&area=test&language=test
[HttpGet]
[Route("")]
public async Task<ActionResult<CourseViewModel>> ListCourses(int page, int pageSize, string language = "", string institution = "", string area = "")
In this case the order does not matter. Once they are present to be matched.
Related
I have a route in Microsoft.AspNet.WebApi.Core.5.2.7 which incorrectly matches a query parameter with a route value.
My controller and action is:
[RoutePrefix("v0/departments/{projectId}")]
[Resource("Collections")]
public class ExampleController : V0BaseContentController
{
[Route("collections/{identifier}")]
[HttpGet]
public async Task<IHttpActionResult> QueryCollection(int projectId, string identifier)
{
var result = Request.RequestUri.Query;
return await Run(projectId, (p, v) => QueryInternal(p, v, identifier, LangCode));
}
When my query parameter (?id=1234) doesn't match the name of the route it works as expected:
http://localhost:5171/v0/departments/66/collections/projects?id=1234
In this case the values in my controller action are as follows:
projectId = 66
identifier = "projects"
Request.RequestUri.Query = "?id=1234"
Correctly routed values
If my query parameter has the name matching the route value of 'identifier' this matches the /{identifier} route. IE:
http://localhost:5171/v0/departments/66/collections/projects?identifier=1234
In this case the values in my controller action are as follows:
projectId = 66
identifier = 1234
Request.RequestUri.Query = "?identifier=1234"
In this instance how do I make sure the identifier route value is set to the value of 'projects' ?
Incorrectly matching
I have the following API controller class.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
namespace Core31Test.Controllers
{
[ApiController]
[Route("[controller]")]
public class DataController : ControllerBase
{
[HttpGet]
[Route("query/{idr:int?}")]
public string Get(int idr, [FromQuery] int id)
{
var idx = id == 0 ? idr : id;
return $"Value: {idx}";
}
[HttpGet]
[Route("query/{cityr}")]
public string GetByCity(string cityr, [FromQuery] string city)
{
var cityx = string.IsNullOrEmpty(city) ? cityr : city;
return cityx;
}
}
}
When I attempt to query by an id, both the route based path and the query string work. When I attempt to query by city, only the route based path works. The querystring path ends up taking the incorrect path.
For example:
http://localhost:51123/data/query/1
Result: value: 1
http://localhost:51123/data/query?id=1
Result: value: 1
http://localhost:51123/data/query/mycity
Result: mycity
http://localhost:51123/data/query?city=acity
Result: value: 0
In the last case, the incorrect route is being selected. Why is this happening and how can I fix it?
Edit 1
If I modify the route for the GetByCity method to be the one given below, the Get method is still selected. In this case, both methods have an optional route parameter and a querystring. Since the Get route specifies the input is an integer, I do not understand why the GetByCity method is going to that one. What I would like to know is how to make this work.
[Route("query/{cityr?}")]
In the last case, the incorrect route is being selected. Why is this happening and how can I fix it?
No, it's not. The correct and expected route is being selected. Let's take a closer look:
The route is query?city=acity
The available actions are:
query/ + optional int parameter (query/{idr:int?})
query/ + required string parameter ("query/{cityr}")
If you think about it, query/ does not satisfy query/ + required string parameter, as no value for cityr was given, so the correct route is the idr overload since that parameter is optional.
[HttpGet]
[Route("students")]
[SwaggerOperation(Tags = new[] { "Student" })]
[SwaggerResponse(HttpStatusCode.OK, Type = typeof(ResponseModel<IList<Student>>))]
[SwaggerResponseExample(HttpStatusCode.OK, typeof(StudentResponseExample))]
[SwaggerResponse(HttpStatusCode.InternalServerError)]
public IHttpActionResult SearchStudent()
{
IDictionary<string, string> searchParams = null;
searchParams = ControllerContext.GetQueryStrings();
.
.
.
}
The above API has three optional parameters which will be pass as query string
SyncDate - Long
OffSet - int
Limit - int
There is no option for user to enter these optional query parameters in swagger UI. Please guide me to implement the optional query parameters.
I am using swashbuckle and I prefer to use annotations rather than having a lengthy comment section over each API method for swagger functionalities.
I referred the following Adding Query String Params to my Swagger Specs and created the SwaggerParameterAttribute class in Filters folder of Web API and when trying to add the OperationFilter in GlobalConfiguration.Configuration
.EnableSwagger as given, it throws type or the namespace name SwaggerParametersAttributeHandler could not be found. I even added the Filters folder namespace but still the error exists.
Please guide on how to implement the optional query parameters in swagger
The way Swagger works it pulls out parameters based on your signature of Action i.e parameters to your Action, but here you are getting these value from ControllerContext which obviously Swagger will never be aware of.
So You need to change the signature of the Action and pass your parameters there.
They will be treated as optional if you make them of nullable type -
[HttpGet]
[Route("students")]
[SwaggerOperation(Tags = new[] { "Student" })]
[SwaggerResponse(HttpStatusCode.OK, Type = typeof(ResponseModel<IList<Student>>))]
[SwaggerResponseExample(HttpStatusCode.OK, typeof(StudentResponseExample))]
[SwaggerResponse(HttpStatusCode.InternalServerError)]
public IHttpActionResult SearchStudent(long? SyncDate = null,int? OffSet = null,int? Limit = null)
{
// Use the variables here
.
.
.
}
This worked for me:
[System.Web.Http.HttpGet]
[Route("api/DoStuff/{reqParam}")]
[Route("api/DoStuff/{reqParam}/{optParam1:alpha?}/{optParam2:datetime?}")]
public string Get(string reqParam, string optParam1= "", string optParam2= "")
It did create two sections in my Swagger UI but that works for me.
Have this Web API controller:
[RoutePrefix("api/echo")]
public class EchoController : ApiController
{
[HttpGet]
[Route("{userId}/{message}")]
public async Task<IHttpActionResult> Echo(string userId, string message, string queryString)
{
await Task.Delay(150);
return Ok(new {Action = "Echo", UserId = userId, Message = message, QueryString = queryString});
}
}
A legit route executing this API would look like this: /api/echo/johni/hello?querystring=1
As you can see, this dummy API receives 3 inputs:
userId from the route
message from the route
querystring from the query params
All 3 of them, are available as dictionary here: httpActionContext.ActionArguments.
Question is, given the HttpActionContext, how would one distinguish between the route / query parameters?
Found it.
X = httpActionContext.ActionArguments - all parameters (route / query).
Y = httpActionContext.RequestContext.RouteData.Values - route parameters only.
Which means, X - Y - query params only.
I want to handle POST of the following API-Call:
/v1/location/deviceid/appid
Additional Parameter are coming from the Post-Body.
This all works fine for me. Now I wnat to extend my code by allowing "deviceid" and/or "appid" and/or BodyData to be null:
/v1/location/deviceid
/v1/location/appid
/v1/location/
These 3 URLs should responded by the same route.
My first approach (BodyData required):
[Route("v1/location/{deviceid}/{appid}", Name = "AddNewLocation")]
public location_fromuser Post(string deviceid = null, string appid = null, [FromBody] location_fromuser BodyData)
{
return repository.AddNewLocation(deviceid, appid, BodyData);
}
This does not work and returns a compile error:
"optional Parameters must be at the end"
Next try:
[Route("v1/location/{deviceid}/{appid}", Name = "AddNewLocation")]
public location_fromuser Post([FromBody] location_fromuser BodyData, string deviceid = null, string appid = null)
Now my function AddNewLocation() get always an BodyData=null - even if the call send the Body.
Finally I set all 3 Parameter optional:
[Route("v1/location/{deviceid}/{appid}", Name = "AddNewLocation")]
public location_fromuser Post(string deviceid = null, string appid = null, [FromBody location_fromuser BodyData = null)
DonĀ“t work:
Optional parameter BodyData is not supported by FormatterParameterBinding.
Why do I want a solution with optional Parameters? My Controller handles just the "adding of a new Location" via a POST.
I want to send on wrong data my own exceptions or error messages. Even if the call has missing values. In this case I want to be able to decide to throw an exception or Setting Defaults by my code.
For an incoming request like /v1/location/1234, as you can imagine it would be difficult for Web API to automatically figure out if the value of the segment corresponding to '1234' is related to appid and not to deviceid.
I think you should change your route template to be like
[Route("v1/location/{deviceOrAppid?}", Name = "AddNewLocation")] and then parse the deiveOrAppid to figure out the type of id.
Also you need to make the segments in the route template itself optional otherwise the segments are considered as required. Note the ? character in this case.
For example:
[Route("v1/location/{deviceOrAppid?}", Name = "AddNewLocation")]
Another info: If you want use a Route Constraint, imagine that you want force that parameter has int datatype, then you need use this syntax:
[Route("v1/location/**{deviceOrAppid:int?}**", Name = "AddNewLocation")]
The ? character is put always before the last } character
For more information see: Optional URI Parameters and Default Values
An additional fact to complement #Kiran Chala's answer -
When we mark any parameter (appid) as optional in the action URI using ? character(for nullable value types) then we must provide default value to the parameter in the method signature as shown below:
[Route("v1/location/{deviceid}/{appid}", Name = "AddNewLocation")]
public location_fromuser Post(string deviceid, int? appid = null)
Ok, I fallen here with my internet research and I continue my way, because the accepted solution not working with dotnet core 3.1.
So here is my solution, following this doc
[HttpPost]
[Route("{name}")]
[Route("{name}/parent/{parentId}")]
public async Task<IActionResult> PostSomething(string name, Guid? parentId = null)
{
return Ok(await Task.FromResult(new List<string>()));
}
By this way many routes go to this single API function