Attribute Routing Constrict Route - asp.net-web-api

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.

Related

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.

AttributeRouting conflicting parameter route with string route

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))})]

Spring controller, why is the returned view ignored?

So, say I have an existing, working page Display Cashier, which displays information about a cashier in a shop. Now, I add a button to this page that looks like:
Manager
The request-mapping for this URL maps it (successfully) to a controller: HandleGetManager
the HandleGetManager controller looks like this:
#Controller
public class HandleGetManager{
private employeeBO employeeBO; //BO handles all business logic
//spring hooks
public HandleGetManager(){}
public void setemployeeBo(employeeBO employeeBO){
this.employeeBO = employeeBO;
}
//get controller
#RequestMapping(method=RequestMethod.GET)
public String getManager(#RequestParam String cashierId){
Long managerId = employeeBO.getManagerByCashierId(cashierId);
String redirectUrl = "/displayManager.ctl?managerId=" + managerId.toString();
return redirectUrl;
}
}
Here's what happens when I try it:
I hit the new button on the Display Cashier page, I expect the following to happen:
The browser sends a get request to the indicated URL
The spring request-mapping ensures that the flow of control is passed to this class.
the #RequestMapping(method=RequestMethod.GET) piece ensures that this method is evoked
The #RequestParam String cashierId instructs Spring to parse the URL and pass the cashierId value into this method as a parameter.
The EmployeeBo has been injected into the controller via spring.
The Business logic takes place, envoking the BO and the managerId var is populated with the correct value.
The method returns the name of a different view, with a new managerId URL arg appended
Now, up until this point, everything goes to plan. What I expect to happen next is:
the browsers is directed to that URL
whereupon it will send a get request to that url,
the whole process will start again in another controller, with a different URL and a different URL arg.
instead what happens is:
this controller returns the name of a different view
The browser is redirected to a half-right, half wrong URL: handleGetManager.ctl?managerId=12345
The URL argument changes, but the name of the controller does not, despite my explicitly returning it
I get an error
What am I doing wrong? Have I missed something?
Assuming you have a UrlBasedViewResolver in your MVC configuration, the String value you return is a View name. The ViewResolver will take that name and try to resolve a View for it.
What you seem to want to do is to have a 301 response with a redirect. With view names, you do that by specifying a redirect: prefix in your view name. It's described in the documentation, here.
Here's a question/answer explaining all the (default) ways you can perform a redirect:
How can I prevent Spring MVC from doing a redirect?

ASP.NET MVC Routing - Thousands of URL's

My ecommerce application stores URL's for item pages in the database. There are thousands of these URL's, which are all root level (i.e. domain-name.com/{item-page-url}).
If I add all of these URL's to the route table by using a simple for loop to call RouteCollection.MapRoute for each URL site performance degrades exponentially. Why? The reason for this is here.
How should I properly handle this situation? Adding all of the routes to the route table doesn't seem right (not to mention the performance pretty much confirms that). I've seen a few ideas about inspecting all incoming URL's and then trying to match that to the URL's in the database but don't fully understand how I'd implement that, nor am I sure if it's the best approach.
Any ideas or suggestions? This seems like it would be not so uncommon, but I haven't found a concrete way to handle it.
If you can change your route to
mycreativeshop.com/product/my-product-name then adding following route to the top of your route config file can help you.
routes.MapRoute(
"CustomRouteProduct",
"product/{id}",
new { controller = "yourcontrollername", action = "Index" }
);
and in the action map the parameter value with name of your product name
public ActionResult Index(string id)
{
//var prdName = id.Replace("-", " ");
//look up prdName in database
return View();
}
Update
Added following as a top route
routes.MapRoute(
"CustomRouteProductZX",
"{id}",
new { controller = "Content", action = "Index" }
);
and by accessing http://localhost:12025/Car-Paint I was directed to Index action of ContentController where I accessed "Car-Paint" in parameter id
But, above having above blocks patterns like http://localhost:12025/Home/ (here Home is also treated as a product)

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.

Resources