Optional Parameters in Web Api Attribute Routing - c#

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

Related

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.

C# - IF with more string variables and OR operator

I would like to check if any variable is empty or null in other words I would like to check if all parameters are sent.
I have this code but it isn't working.
[HttpGet]
[Route("{regid}/{year}")]
public HttpResponseMessage Get(string regid, int? year, string un, string ps)
{
var isRegIDNumeric = int.TryParse(regid, out _);
if (!isRegIDNumeric || year == null || (String.IsNullOrEmpty(un)) || (String.IsNullOrEmpty(ps)))
{
...
}
If I call like this, one parameter is missing, IF isn't working
localhost/test/?regid=001&year=2019&un=ws.test
I got this message:
<Error>
<Message>No HTTP resource was found that matches the request URI
'localhost/test/?regid=001&year=2019&un=ws.test'.
</Message>
<MessageDetail>No action was found on the controller 'test' that matches
the request.
</MessageDetail>
</Error>
If I call with all parameters everything is OK, whole procedure is executed.
localhost/test/?regid=001&year=2019&un=ws.test&ps=test
When I had only 2 variables ...
[HttpGet]
[Route("{regid}/{year}")]
public HttpResponseMessage Get(string regid, int? year)
{
var isRegIDNumeric = int.TryParse(regid, out _);
if (!isRegIDNumeric || year == null)
{
...
}
and called like this, one parameter is missing ...
localhost/test/?regid=001
Everything was OK, IF was regularly executed.
How to make functional IF with all 4 variables if any of them is null or empty?
In your 4 parameters example both string parameters are mandatory. That's why you get an error.
If you want them to be optional, assign them default value null. Than if statement would work.
Or add another Get() method.
I believe that this another question have the answer of yours and corroborates what David Pivotar said.

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.

Multiple optional parameters web api attribute routing

I am new to attribute routing, and I am not sure if this is even possible.
I have an attribute route, which works fine like this:
[HttpGet]
[Route("GetIssuesByFlag/{flag:int=3}")]
public IEnumerable<IssueDto> GetIssuesByFlag(int flag)
Now I want to add some extra optional parameters to narrow down my search, so I want to add 2 extra optional parameters.
What I have tried:
[HttpGet]
[Route("GetIssuesByFlag/{flag:int=3?}/{categoryId:int?}/{tagIds?}")]
public IEnumerable<IssueDto> GetIssuesByFlag(int flag , int? categoryId = null, int?[] tagIds = null)
This works fine if my call is /api/controller/1/2, but fails with 404 when it comes to /api/controller/1.
How can I achieve this?
Edit 1: Nkosi's answer below worked, however an extra modification was needed.
[HttpGet]
[Route("GetIssuesByFlag/{flag:int=3}/{tagIds?}/{categoryId:int?}")]
public IEnumerable<IssueDto> GetIssuesByFlag(int flag , List<int> tagIds, int? categoryId = null )
The list or array must be second as it is automatically null if no value is provided and cant be marked as optional with = null.
{flag:int=3?} is the problem. it is either optional {flag:int?} with the default value in the action or {flag:int=3}.
[HttpGet]
Route("GetIssuesByFlag/{flag:int=3}/{categoryId:int?}/{tagIds?}")]
public IEnumerable<IssueDto> GetIssuesByFlag(int flag , int? categoryId = null, int?[] tagIds = null)
You currently have 3 optional parameters. when you have just the 1 value routing table wont know which optional parameter you are referring to, hence the 404
Use a query string.
[HttpGet]
[Route("GetIssuesByFlag/{flag:int=3?}")]
public IEnumerable<IssueDto> GetIssuesByFlag(int flag, List<int> tagIds, int? categoryId = null)
Url: /getissuesbyflag/1?tagIds=2,5,6&categoryId=56
You really should use query strings for optional parameters and path parameters if they are required.

RedirectToAction with unnamed querystring parameters

In MVC 2 I have a RedirectToAction call which I need to pass all of the querystring parameters. Unfortunately I can only find a way to pass named querystring parameters is there a way of passing all querystring parameters regardless.
We have one named parameter, id but we just want to append all of the rest onto the end of the URL without setting them explicitly.
return RedirectToAction("index", "enquiry", new { id = enquiryCacheId.ToString()});
You cannot pass COMPLEX objects in URLs, so that kills the option of passing on Complex types in new { }.
One option what you are left with is to encode the querystring and then send that in 'id'. Say for examples, you have querystring as follows name=rami&gender=male. Then you can encode it with HttpUtility.UrlEncode(), then set it to id=[encoded string]. On the other action (retrieving side) you can get id value and then use HttpUtility.UrlDecode() to decode the string. Then finally you can use HttpUtility.ParseQueryString() to split the querystring into NameValueCollection.
If above suggestion is not what you are looking for then. you need to add all querystring parameters to new { } in the RedirectToAction(). If you want to customize it then you might need to go to ASP.Net MVC Source code #CodePlex and make your own builds (which I think not worthy for this kind of requirement).
I have an extension method that I use to modify the querystring of the current URL:
public static string ModifyQueryString(this UrlHelper helper, NameValueCollection updates, IEnumerable<string> removes)
{
var request = helper.RequestContext.HttpContext.Request;
var url = request.Url.AbsolutePath;
var query = HttpUtility.ParseQueryString(request.QueryString.ToString());
updates = updates ?? new NameValueCollection();
foreach (string key in updates.Keys)
{
query.Set(key, updates[key]);
}
removes = removes ?? new List<string>();
foreach (string param in removes)
{
query.Remove(param);
}
if (query.HasKeys())
{
return string.Format("{0}?{1}", url, query.ToString());
}
else
{
return url;
}
}
But, if you need to modify an arbitrary URL, it should be easy enough to modify. You would just need to add a parameter to accept an arbitrary URL, and then instead of getting the URL/querystring from the HttpContext, you just split the passed URL at ?. The rest of the code should work the same.

Categories

Resources