Swagger - Web API - Optional query parameters - c#

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

Related

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.

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

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.

Optional Parameters in Web Api Attribute Routing

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

How do I accept an array as an ASP.NET MVC controller action parameter?

I have an ASP.net MVC controller called Designs that has an action with the following signature:
public ActionResult Multiple(int[] ids)
However, when I try to navigate to this action using the url:
http://localhost:54119/Designs/Multiple?ids=24041,24117
The ids parameter is always null. Is there any way to get MVC to convert the ?ids= URL query parameter into an array for the action? I've seen talk of using an action filter but as far as I can tell that will only work for POSTs where the array is passed in the request data rather than in the URL itself.
The default model binder expects this url:
http://localhost:54119/Designs/Multiple?ids=24041&ids=24117
in order to successfully bind to:
public ActionResult Multiple(int[] ids)
{
...
}
And if you want this to work with comma separated values you could write a custom model binder:
public class IntArrayModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (value == null || string.IsNullOrEmpty(value.AttemptedValue))
{
return null;
}
return value
.AttemptedValue
.Split(',')
.Select(int.Parse)
.ToArray();
}
}
and then you could apply this model binder to a particular action argument:
public ActionResult Multiple([ModelBinder(typeof(IntArrayModelBinder))] int[] ids)
{
...
}
or apply it globally to all integer array parameters in your Application_Start in Global.asax:
ModelBinders.Binders.Add(typeof(int[]), new IntArrayModelBinder());
and now your controller action might look like this:
public ActionResult Multiple(int[] ids)
{
...
}
To extend on Darin Dimitrov's answer, something you can get away with is accepting a simple string in your URL parameter and converting it to an array yourself:
public ActionResult Multiple(string ids){
int[] idsArray = ids.Split(',').Select(int.Parse).ToArray();
/* ...process results... */
}
If you get a parse error while doing this (because someone passed you a malformed array), you can cause your exception handler to return a 400 Bad Request error instead of the default, more unfriendly 404 Not Found error that MVC returns when an endpoint is not found.
You can also use this URL format, and ASP.NET MVC will do everything for you. But, remember to apply URL encoding.
?param1[0]=3344&param1[1]=2222
I don't know where Groky's URL string was coming from, but I had the same problem with some javascript calling my controller/action. It would build up a URL of null, 1, or many "IDs" from a multiple-select list (which is unique to the solution I'm going to share).
I copy/pasted Darin's custom model binder and decorated my action/parameter, but it didn't work. I still got null valued int[] ids. Even in the "safe" case where I actually did have many IDs.
I ended up changing the javascript to produce an ASP.NET MVC friendly parameter array like
?ids=1&ids=2
I had to do some silly stuff, though
ids || [] #=> if null, get an empty array
[ids || []] #=> if a single item, wrap it in an array
[].concat.apply([], ...) #=> in case I wrapped an array, flatten it
So, the full block was
ids = [].concat.apply([], [ids || []])
id_parameter = 'ids=' + ids.join('&ids=')
It's messy, but it's the first time I had to hack like this in javascript.
.Net Core Answer
For those coming here in recent times, you can do this in .Net Core with:
http://localhost:54119/Designs/Multiple?ids=24041&ids=24117
and:
public ActionResult Multiple([FromQuery] int[] ids)
{
...
}

ASP.NET MVC How to correctly map a parameter in url-routing

I have the following route
routes.MapRoute(
"Segnalazioni_CercaSegnalazioni",
"Segnalazioni/CercaSegnalazioni/{flag}",
new { controller = "Segnalazioni", action = "CercaSegnalazioni", flag = 7 }
);
that maps to the following methon of the class SegnalazioniController:
public ActionResult CercaSegnalazioni(int flag)
{
ViewData["collezioneSegnalazioni"] = Models.Segnalazioni.Recupera(flag);
System.Xml.Linq.XElement x = (System.Xml.Linq.XElement)ViewData["collezioneSegnalazioni"];
return View("Index");
}
How come the link http://localhost:1387/Segnalazioni/CercaSegnalazioni/1 gives me the error
The parameters dictionary contains a null entry for parameter 'flag' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult CercaSegnalazioni(Int32)' in 'RecuperoPagatiMvc.Controllers.SegnalazioniController'. To make a parameter optional its type should be either a reference type or a Nullable type.
Nome parametro: parameters
Post all your routes. It sounds like your URL is being handled by a different route than this one. Remember, the order you list your routes does matter. Therefore, if you have another route BEFORE this one that this URL can map to, it will.
MvcContrib contains route debugger. Use it and you'll see which route is called for this url. Here are some instructions how to enable it

Categories

Resources