WebApi 2 routing with ? or / - c#

I just added swagger to my api to generate some documentation...
normally, my front end code would do a "get by id" like this:
https://whatever.com/api/GetDisplayContainer/A90555CD-931E-4D9D-D51D-08D63E83FCC6
however, swaggers "try it" wants to send:
https://whatever.com/api/GetDisplayContainer?id=A90555CD-931E-4D9D-D51D-08D63E83FCC6
I want to be able to support both ways. How do I do it?
Here is an example of a controller method:
[HttpGet]
[Route("GetDisplayContainer")]
public ApiResponse<ContainerDisplayViewModel> GetDisplayContainer(Guid id)
{
return ApiResponse.Convert(ResourceService, _containerService.GetDisplayContainerById(id));
}
I don't really want to have to change my existing code to do it the "query string" way. because its a totally valid way of doing it. But it would be nice to be able to support both...
This is C# using .net core 2.1.
Thanks!

You can do two routes:
[HttpGet]
[Route("GetDisplayContainer")]
public ApiResponse<ContainerDisplayViewModel> GetDisplayContainer([FromQuery] Guid id)
{
}
and
[HttpGet]
[Route("GetDisplayContainer/{id}")]
public ApiResponse<ContainerDisplayViewModel> GetDisplayContainerRoute([FromRoute] Guid id)
{
}

If you change your route from GetDisplayContainer to GetDisplayContainer/{id} then Swagger will know that the parameter is not located in the query string and should generate your desired output.
Full code:
[HttpGet]
[Route("GetDisplayContainer/{id}")]
public ApiResponse<ContainerDisplayViewModel> GetDisplayContainer(Guid id)
{
return ApiResponse.Convert(ResourceService, _containerService.GetDisplayContainerById(id));
}

Related

ASP.NET Core Web API : route by query parameter

I am coming from a heavy Java/Spring background and trying to transition some knowledge over to ASP.NET Core 6.
In Spring, on a RestController, I am able to route the request based on the presence of a query parameter.
So a HttpRequest with the uri: /students?firstName=Kevin can be routed to a different controller method than a HttpRequest with the uri: /students.
In ASP.NET Core 6, I am unable to determine if the equivalent is possible after working through some examples and reading the documentation for Web API.
Here is what I am trying to achieve, is this possible using two methods and routing configuration that will discern which controller method to invoke based on the query parameter?
[ApiController]
[Route("Students")]
public class StudentHomeProfileController : ControllerBase
{
[HttpGet] //Route here when no parameters provided
public async Task<ActionResult<IEnumerable<Student>>> GetStudentAsync()
{
/* Code omitted */
}
[HttpGet] //Route here when firstName query param provided
public async Task<ActionResult<IEnumerable<Student>>> SearchStudentAsync([FromQuery] string firstName)
{
/* Code omitted */
}
}
While filtering by query parameters does not come with ASP.NET Core out of the box, it's not too hard to supply this functionality on your own.
When it comes to extensibility, ASP.NET has some superpowers, one of them is IActionConstraint, which
Supports conditional logic to determine whether or not an associated action is valid to be selected for the given request. (Source)
Creating an annotation to filter for query parameters is as easy as
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class QueryParameterConstraintAttribute : Attribute, IActionConstraint
{
private readonly string _parameterName;
public QueryParameterConstraintAttribute(string parameterName)
{
this._parameterName = parameterName;
}
public bool Accept(ActionConstraintContext context)
{
return context.RouteContext.HttpContext.Request.Query.Keys.Contains(this._parameterName);
}
public int Order { get; }
}
All that's left is annotating your controller method with that constraint
[HttpGet] //Route here when firstName query param provided
[QueryParameterConstraint("firstName")]
public async Task<ActionResult<IEnumerable<Student>>> SearchStudentAsync([FromQuery] string firstName)
{
/* Code omitted */
}
In a quick test I was able to confirm that it seems to work as intended, even if you add multiple of those attributes for different query parameters (if all conditions match, the route is called).
(Please note, this was tested with .NET Core 2.1. Anyway, it shuold be pretty much the same with .NET 6)
I think you are looking for something like this, you need to specify the parameter in the "HttpGet" attribute
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-6.0#attribute-routing-with-http-verb-attributes
[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
[HttpGet] // GET /api/test2
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")] // GET /api/test2/xyz
public IActionResult GetProduct(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int/{id:int}")] // GET /api/test2/int/3
public IActionResult GetIntProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int2/{id}")] // GET /api/test2/int2/3
public IActionResult GetInt2Product(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
You are trying to differentiate API calls using query params. this is not the way to do this. if you want to separate the calls you should probably use path params instead.
Read more about Routing in ASP.NET Core - https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-6.0

Optional Parameter in GET Requests for ASP.NET Core API not Working with Swagger

I'm working on an endpoint which looks like this:
GET {{url}}/v1.0/GroupsInfo/StuffToGet/1/2/hea
where the 2 integers are certain ids and the text on the end is a search term.
I want the last parameter (the search text) to be optional. I have got that working using the following, but it does not appear as mandatory in Swagger:
[Route("[action]/{catId:int}/{dogId:int}/{search?}")]
public IActionResult StuffToGet(int catId, int dogId, string search)
{
// do stuff
}
Swagger is ignoring the question mark on the end of the Search parameter in the route constraint.
Is this a known issue, or do I need to right some custom code to get Swagger to recognise that optional flag?
your variant will be working, but if you have a problem with swagger try this
[Route("[action]/{catId:int}/{dogId:int}]
[Route("[action]/{catId:int}/{dogId:int}/{search}")]
public IActionResult StuffToGet(int catId, int dogId, string search)
{
// do stuff
}
or maybe this
[Route("StuffToGet/{catId:int}/{dogId:int}/{search}")]
public IActionResult StuffToGet(int catId, int dogId, string search)
{
// do stuff
}
[Route("StaffToGet/{catId:int}/{dogId:int}]
public IActionResult StuffToGet(int catId, int dogId)
{
return StuffToGet(catId, dogId,string.empty);
}
Create two actions with similar routes, but one without the search parameter. Both actions call the same business layer code.

NSwag MSBuild "The method 'get' on path '/api/Account' is registered multiple times"

I'm trying to generate a swagger specification with NSwag.MSbuild but whenever I do it throws me this message:
The method 'get' on path '/api/Account' is registered multiple times
Now the problem is that my methods are route-less as shown below with some examples of the controller
[HttpPost]
[HttpGet]
[AllowAnonymous]
public IActionResult ExternalRegister(string provider, string returnUrl = null)
[HttpGet]
public IActionResult AddLogin(string provider, string returnUrl)
[HttpGet]
[AllowAnonymous]
public ActionResult SignUpConfig()
I understand why it does this but what I don't understand is that doing the same thing in NSwag Studio works, the command I use is $(NSwagExe_Core22) webapi2swagger is there an option so that it generates successfully like NSwag Studio?
In a WebAPI if you have more than one HttpGet or HttpPost etc you should add Route Attribute to distinguish them.
Add HttpGet["{name}"]
Turns out you don't have to specify the routes if you don't want to it has something to do with the Default Url Template:
/DefaultUrlTemplate:"{controller}/{action}/{id?}"
adding {action} solved it for me
In my case, I had already added custom [Route("")] attributes to all the paths. The problem was I had two public helper methods in the controller which NSwag identified as GET methods. Simply making them private (which they should have been anyway) made the problem go away...
In my case I had
[HttpGet, ActionName("Stuff")]
public async Task<Stuff> GetStuff(long byId, string color)
{
/* Do things one way */
}
[HttpGet, ActionName("Stuff")]
public async Task<Stuff> GetStuff(string byName, string color)
{
/* Do things another way */
}
The problem was that there were two identically named routes that take in different parameters. This is an overload situation that ASP.NET seems to be perfectly fine with but apparently blows NSwag's mind.
Because this was in legacy code, renaming the methods was not an option for me so I created a single method with optional parameters like so:
[HttpGet, ActionName("Stuff")]
public async Task<Stuff> GetStuff(string color, long? byId = null, string byName = null )
{
if (byId != null)
{
/* Do things one way */
}
else
{
/* Do things another way */
}
}
What helped me in this situation was to set the Route Attribute like this:
[Route("SignUpConfig")] ,[Route("AdLogin")]
If your controller is decorated with
[Route("[controller]")]
then you need you specify separate names
HttpGet("get1") and HttpGet("get2")
Else it will pick if decoration contains action name it it like
Route("[controller]/[action]") or from default route {controller}/{action}/{id?}

ASP.NET Core API - Dynamic Routing (Make API route behave like "one-parameter query string")

Aloha :D
I would like to create a dynamic route binding.
What I mean by this, is basically replacing the Query String with a dynamic route.
Example:
Instead of this:
POST http://localhost:5000/api/documents?templatename=individualemploymentagreement
this:
POST http://localhost:5000/api/documents/individualemploymentagreement
Note: after "http://localhost:5000/api/documents/" I want to put anything I want, but this route will always be used and what comes after should be used like a variable. Obviously, this will lead to a non-existing API Route at the moment. But is there any way to deal with this?
Note 2: The reasons I want to use this are:
- According to RESTful services "rules", query strings should be used just for queries, In this case I'm not using a query, I'm calling a generic document service, which however, treats every document slightly different when needed. So query strings are not recommended in my case.
- This service will deal with hundreds of document types, so I can't really make a different path / api for each one of them. So this is not recommended as well.
My code (In which I'm using a query string for {templateName}:
namespace DocumentGenerator.Api.Controllers
{
[Route("api/{controller}")]
[ApiController]
public class DocumentsController : ControllerBase
{
//useless details
[HttpPost]
public async Task<IActionResult> Generate([FromQuery] string templateName, [FromBody] object properties)
{
// according to {templateName} do this or that...
// useless details
}
}
}
What I would want in code:
namespace DocumentGenerator.Api.Controllers
{
[Route("api/{controller}")]
[ApiController]
public class DocumentsController : ControllerBase
{
//useless details
[HttpPost("{templateName}"]
public async Task<IActionResult> Generate([FromBody] object properties)
{
// according to {templateName} do this or that...
// useless details
}
}
}
You can specify the parameter name as a route attribute value in HttpPost :
[HttpPost("{templateName}"]
public async Task<IActionResult> Generate(string templateName, [FromBody] object properties)
{
}
or even
[HttpPost("/api/documents/{templateName}"]
public async Task<IActionResult> Generate(string templateName, [FromBody] object properties)
{
}

ASP.NET Web API 2, how to distinguish methods with similar url

My team are using ASP.NET Web API framework.
In our application, we have 2 method which look like this:
[Route("users/events"]
[HttpGet]
public UserEvent GetEventsAssociatedWithUser(string Id) { ... }
and
[Route("users/{Id}"]
[HttpGet]
public User GetUserInformation(string Id) { ... }
but whenever I want to send request to "...users/events", it keeps send it to "...users/{Id}" and use "events" as an URI parameter.
I just want to know if there is any way to solve this problems without changing the URL of any of these method?
You need to set a route order like this
[Route("users/events", RouteOrder = 1)]
Read more here: http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#order
You need to use the RouteOrder parameter
See here:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#order
Example:
[Route("users/events" RouteOrder = 1]
[HttpGet]
public UserEvent GetEventsAssociatedWithUser(string Id) { ... }

Categories