ASP.NET Web API - Distinguish between route / query params - c#

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.

Related

Query parameter incorrectly matches route values .net 4.8

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

Route based attributes taking the incorrect method when data constraints are applied

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.

Angular to C# Http passing query parameters

I think the C# HttpGet TEMPLATE is what I am missing, but here’s the details. On the Angular front end:
let params = new HttpParams()
.append('GDWarn', values[0].toString())
.append('GDLimit', values[1].toString())
.append('NDWarn', values[2].toString())
.append('NDLimit', values[3].toString())
let url = `${this.url}/CalibrationModelFile/UpdateLimits/${this.networkID}/${constID}/{params}`
Here I assume this.http.get(url, … will do some magic to recognize HttpParams is a set of QUERY parameters.
So the C# backend to receive the http request:
[HttpGet("CalibrationModelFile/UpdateLimits/{networkID:int}/{ConstituentID:int}/{values}")]
public async Task UpdateConstituentLimits(int networkID, int ConstituentID, [FromQuery] double[,] values)
I think the [FromQuery] may be right, but NOT enough. The {values] in the template probably should have something so we know it's QUERY PARMS?
Any thoughts?
Thanks in Advance, Yogi
Sending an Array as Query Parameters
If you're trying to get a set of query params and route params like this:
.../UpdateLimits/networkID/ConstituentID?values=array
you should send a request as shown in the sample:
.../UpdateLimits/1/2?values=3.0&values=4.0&values=5.0
Query Parameters are not Part of a Route
Action's arguments in C# will be:
[HttpGet("CalibrationModelFile/UpdateLimits/{networkID:int}/{ConstituentID:int}")]
public async Task UpdateConstituentLimits(int networkID, int ConstituentID, [FromQuery] double[] values)
In the above sample {values} is removed from the route because QueryParam is not a part of a route. Also, it's better to decorate route parameters with [FromRoute] attribute.
2D Array as Query Parameters
Now, if the case is a 2D array as a query param, a simple solution is converting a 2D array into a string and parse the string in the C# action as following code:
.../UpdateLimits/1/2?values=GDWarn:4.1,GDLimit:3.7,NDWarn:6.3,NDLimit:4.8
and parsing query string in the resulted action will be like this:
[HttpGet("{networkID:int}/{ConstituentID:int}")]
public IEnumerable<WeatherForecast> Get([FromRoute]int networkID,
[FromRoute]int ConstituentID, [FromQuery]string values)
{
// received string is: "GDWarn:4.1,GDLimit:3.7,NDWarn:6.3,NDLimit:4.8"
var dict = new Dictionary<string, double>();
foreach (var item in values.Split(','))
dict.Add(item.Split(':')[0], Convert.ToDouble(item.Split(':')[1]));
return (...)
}

How to have many optional routes in .Net Core

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.

Swagger - Web API - Optional query parameters

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

Categories

Resources