AttributeRouting conflicting parameter route with string route - asp.net-web-api

I have two controllers, ItemsController and SingleItemController which both inherit from ApiController. Both have RoutePrefix items/inventory defined on the controllers, like so:
[RoutePrefix("items/inventory")]
I'm using AttributeRouting in Web API 2.2.
In SingleItemController I have the following route:
[Route("{itemReference}")]
[HttpGet]
public ItemDTO GetItem(string itemReference)
In ItemsController I have the following route:
[Route("all")]
[HttpGet]
public List<ItemDTO> GetAllItems(DateTime dateFrom, DateTime dateTo)
When I try to hit the /items/inventory/all?dateFrom=2015-09-06&dateTo=2015-09-12 route, I get the following error:
Multiple controller types were found that match the URL. This can
happen if attribute routes on multiple controllers match the requested
URL. The request has found the following matching controller types:
ItemAPI.Controllers.ItemsController
ItemAPI.Controllers.SingleItemController
So {itemReference} route is conflicting with all route. Why is this? I would think that it reserves first all route and then allows an optional string route.

This is because it can't decide whether "all" is an item reference.
I had a similar issue recently where I had a controller with an "admin" route prefix.
To get round the issue I put a constraint on the parameter to ignore the word "admin".
So in your case you could do the following to ignore the word "all":
[Route("{itemReference:regex(^(?!all))})]

Related

How to find the parameter name expected in laravel model route binding

I have a route like this
Route::get('/vcs-integrations/{vcs-provider}/authenticate','VcsIntegrationsController#authenticate');
and the method to handle the route I am using the model route binding to happen is as follows
<?php
....
use App\Models\VcsProvider;
....
class VcsIntegrationsController extends Controller
{
public function authenticate(VcsProvider $vcsProvider, Request $request)
{
...
// some logic
...
}
}
when I try to access the route I am getting 404 due to the parameter name is not matching.
So, how do I know the parameter name expected by laravel in route model binding ?
From the route parameter docs:
"Route parameters are always encased within {} braces and should consist of alphabetic characters, and may not contain a - character. Instead of using the - character, use an underscore (_)." - Laravel 7.x Docs - Routing - Route Parameters - Required Parameters
You should be able to define the parameter as {vcs_provider} in the route definition and then use $vcs_provider for the parameter name in the method signature if you would like. You can also name it not using the _ if you prefer, but just avoid the -, hyphen, when naming.
Have fun and good luck.
Laravel automatically resolves Eloquent models defined in routes or controller actions whose type-hinted variable names match a route segment name.
This means that if you want implicit binding to work, you need to name the route param same as your variable. Since your variable's name is vcsProvider, your route should be:
Route::get('/vcs-integrations/{vcsProvider}/authenticate','VcsIntegrationsController#authenticate');

How can I prioritize concrete web.api url routing above abstract url routing

I have two controllers. To Web.API, the attribute routes I've set up are the same, but one has a concrete value while the other is tokenized. Lemme 'splain.
The first I'd like to match when the Url is http://localhost/MemberProfile. So, I add a MemberProfileController and place this at the top:
[Route("api/MemberProfile/{id:guid?}", Order = 0)]
public class MemberProfileController : ApiController
I then have abstract routes I'd like to match so that the Url http://localhost/City matches the DefaultController which has this at the top:
[Route("api/{type}/{id:guid?}", Order = 1)]
public class DefaultController : ApiController
I've left Order in there to show that I've considered that options, but it only applies to actions within the same controller. I would love the concrete value of MemberProfile to match the first route while any other value matches the tokenized Route. Instead, when I hit http://localhost/MemberProfile, I get a warning that says:
Multiple controller types were found that match the URL.
Because that matches both api/MemberProfile and api/{type} I get it. It makes sense, but I would love for the concrete value to take precedence. Is there any way to accomplish this? Is there a way to use the old style of setting up the routing table in the config to give one or the other priority without losing all of the web api action matching magic based on request type and params?
Attribute routing trumps WebApiConfig routing, which means that any routes that you set up in the routing table by adding them in WebApiConfig.cs Register() method will be second priority to any routes that you set up via attribute routing. In this case, if you set up a routing table entry in the WebApiConfig that matches your tokenized routes like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{type}/{id}",
defaults: new { controller = "DefaultController", id = RouteParameter.Optional }
);
You'll have your matching for unknown {type} urls such as http://localhost/City. This will route any request to your DefaultController and Web API will pick up the routing from there using request verb and parameter matching as normal. Then, in your MemberProfileController, set it up just as shown in the question:
[Route("api/MemberProfile/{id:guid?}", Order = 0)]
public class MemberProfileController : ApiController
This will now take precedence over the route set up in the WebApiConfig.cs Register() method and http://localhost/MemberProfile will be routed to the MemberProfile controller but let everything else that matches the route format fall through to your DefaultController.

How to map different HTTP methods on the same url to different controllers?

I have my API for a small part of my application split over two controllers, because of (external) requirements on the casing of JSON data in the API (some requests should use camelCasing, while others should use PascalCasing).
Now, I have a url that I want to map with PascalCasing for GET, but camelCasing for PUT, so I tried the following:
[PascalCasing] // custom attribute, part of our code
// We configure all controllers that *don't* have this to use
// camelCasing
public class PascalCasedController : ApiController
{
[HttpGet]
[Route("url/to/my/resource/{id}")]
public IHttpActionResult(int id)
{
return Ok(GetResource(id));
}
}
public class CamelCasedController : ApiController
{
[HttpPut]
[Route("url/to/my/resource/{id}")]
public IHttpActionResult(int id, Resource resource)
{
SaveResource(id, resource);
return Ok();
}
}
The GET request works as expected, but if I try to PUT something there with Fiddler, I get the following error message:
Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
The request has found the following matching controller types:
MyProject.PascalCaseController
MyProject.CamelCaseController
I realize this is probably because WebAPI maps routes to controllers first and actions next, but if HTTP methods are considered, there really isn't any ambiguity here. Is there any way that I can tell WebAPI how to do this, without having to have the methods in the same controller?
#Tomas - There's an interface "System.Web.Http.Dispatcher.IHttpControllerSelector" exposed in System.Web.Http assembly. You can use that interface and create your own HttpControllerSelector. You can then replace the DefaultControllerSelector with your custom controller selector in the HttpConfiguration during AreaRegistration.
httpConfig.Services.Replace(typeof(IHttpControllerSelector), new CustomControllerSelector(services.GetHttpControllerSelector()));
In this custom controller selector you can write your own implementation of SelectController() method of IHttpControllerSelector in which you can call GetControllerMapping() method of IHttpControllerSelector. This will give you the list of all the controllers registered. For every controller you can check for the DeclaredMethods and check for the CustomAttributes for each of the DeclaredMethods. In your case it will be either HttpGetAttribute or HttpPutAttribute.
Check the Method type of the incoming HttpRequestMessage (GET/PUT) and compare it against the value of the CustomAttributes. If you find a match of the combination of incoming request URL and the the respective Http Verb then you take that HttpControllerDiscriptor and return it from the SelectController() method..
This will allow you to have same URL with different methods in two different controllers.

Multiple GET verb on single route with different parameter name Web Api

How can possible to call different-different action on basis of parameter name using single route.
I need following
/api/v1/user
GET
key=dfddg&secret=fafassaf&query=select id from user where user like '%ggg%'
and
/api/v1/user
GET
key=dfddg&secret=fafassaf&ids=fadfdafdsf,faffasfasfsf,asfasfasfasfas,asfasfasfasf
I have written following code
[RoutePrefix("api/v1/user")]
public class UserController : ApiController
{
[GET("")]
public String GetAllUsers(String key, String secret, String query)
{
return "GetAllUsers";
}
[GET("")]
public String GetLookupUserIds(String key, String secret, String ids)
{
return "GetLookupUserIds";
}
but first case working fine but second one throwing exception
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:14823/api/v1/user?key=rhdgsdgdsr&secret=fhdgdgdfhdfh&ids=fdfdf,dfadfff'.",
"MessageDetail": "No action was found on the controller 'User' that matches the request."
}
I believe the issue here is that a request for api/v1/user is matched by the 1st route in the route table.(Note: route matching happens first where it doesn't consider query parameters and then action matching happens) Now, the 1st route in the route table could be reflection order based on which attribute routing is adding the actions to it. (you can check how the route table GlobalConfiguration.Configuration.Routes entries look like).
Attribute routing adds routes by assigning action variable the value of the action name. Web API's action selection has logic where if it sees that the action variable is assigned, it will try to look for the best matching action among list of actions with this same name (action overloading scenario like yours).
You can try the following:
Have same action name for both methods above by using ActionName
attribute.
if 1. doesn't make sense, you could probably have different route temmplate for the actions.

Attribute Routing Constrict Route

I'm using the http://attributerouting.net/ nuget package for WebApi. Here are my two GET methods and route attributes, for list and a specific item:
[GET("api/products/{tenantid}/{channelid}?{skip=0}&{take=20}&{status=1}")]
public IEnumerable<Product> Get(short tenantId, byte channelId, int status, int skip, int take)
[GET("api/products/{tenantid}/{channelid}/{id}")]
public Story Get(short tenantId, byte channelId, long id)
But in the generated help URIs, three GET options are shown.
GET api/products/{tenantid}/{channelid}?status={status}&skip={skip}&take={take}
GET api/products/{tenantid}/{channelid}?id={id}
GET api/products/{tenantid}/{channelid}/{id}
even though "id" isn't a parameter to the first GET method. How do I eliminate the middle URI with "?id={id}" at the end? I imagine I need some sort of constraint, but I can't figure it out from the documentation site.
To fix the issue, you can name the actions differently. Example: GetAllProducts, GetProduct
The issue you are seeing is an expected behavior because ApiExplorer(which HelpPage uses) visits all routes within the route collection and for each route it checks to see which actions can be reached from that route. Now with the above attribute decorated routes, the routes in the route collection most probably would like below:
a. "api/products/{tenantid}/{channelid}", controller="Products", action = "Get" etc...
b. "api/products/{tenantid}/{channelid}/{id}", controller="Products", action = "Get"...
Now for the route 'a.', ApiExplorer checks which actions can be reached and it notices that for the controller 'Products' and action 'Get', there are 2 actions that can be reached and also it tries to see how many parameters are coming from the route path itself and if there are any parameters on the action which aren't coming from the route path, it assumes it to be coming from the query string...hence you are seeing the "?id={id}". Hope this helps.

Resources