managing many method routes in an api service with webapi - asp.net-mvc

This is a simple issue with managing many methods across an asp.net web api project.
We have quite a few methods like this:
ProductsApiController.GetByType(string type, string x)
ProductsApiController.GetByType(string type, int limit)
//method has a number of arguments..
ReportsApiController.GetAll(string x, string y)
The problem: We have a ton of methods with custom parameters and we have to define a custom route for each of them
routes.MapRoute(
name: "ProductRoute",
url: "ProductsApiController/{type}/{x}"
);
routes.MapRoute(
name: "ReportRoute",
url: "ReportsApiController/{x}/{y}"
);
I'm simplifying the signatures, there are many methods with a lot of params and these methods don't usually have params in common.
What is the general pattern to this? Is there a better way to do this or am I only left with this.

I'd say for your scenario, the most logical solution would be to use attribute routing.
Yes, it would still mean you have to create a lot of routes, but since they are effectively tied to a single action, it makes most sense to have the route in an attribute decorating this action.
You can get AR from Nuget:
PM> Install-Package AttributeRouting.WebApi
In your case it would look like this:
[GET("mytype/{type}/{x}")]
public MyType GetByType(string type, string x)
[GET("mytype/{type}/{limit}")]
public MyType GetByType(string type, int limit)
and so on...
which, IMHO is much more manageable
A while ago I wrote an intro post to AR - http://www.strathweb.com/2012/05/attribute-based-routing-in-asp-net-web-api/
You can also visit their excellent wiki - https://github.com/mccalltd/AttributeRouting/wiki

This is a case where passing URI parameters in the query string becomes really handy. You could define just one route and then have all the URI parameters for that action passed in through the query (e.g. ReportsApi/GetAll?x=5&y=6). You would just define a route with a path template that also includes the action ({controller}/{action}).
Otherwise, if you want all the parameters passed in within the path in different ways, I can't think of a better way. Because you have to let the routing system know where to get each parameter and what parameter name it should bind to. There isn't a convention for deciding on a parameter name the way there is for query string parameters. You could look into Filip's good suggestion of using attribute routing below. It won't reduce the number of routes you'll have to write, but at least they'll be closer to the code where you need them.

Related

How to set {city} in all the routes MVC 5

I am developing a marketplace application which is supposed to have different products and vendors which are mapped to different cities.
The idea is that I need to store the CurrentCity in context so that I can use it to construct urls, filter data, fetch delivery areas etc.
e.g.
www.mywebsite.com/cityA/listings
www.mywebsite.com/cityB/listings
www.mywebsite.com/cityA/cart
www.mywebsite.com/cityB/cart
Something like the MoonPig website (https://www.moonpig.com/uk/Gift/Flowers/)
Currently, I am passing the city as a parameter in almost all the controller methods and also storing it in a cookie.
Alternatively, I am also thinking of creating a BaseController and possibly inject it in the OnActionExecuting(ActionExecutingContext context).
But the problem with the first approach is that all the Action methods need to have "city" as a parameter and I need to have it in context for doing something like
#Url.Action("Index", "Listings", new {city = cityName})
If I use the second approach, then I don't think I'll get the urls which have city in them.
I am ideally looking for a solution with which I can inject a city parameter as Base route / segment in the MVC RouteDictionary so that all the Urls are generated accordingly (with #url helper).
Is this possible or is there a better way to tackle this problem?
Would really appreciate if someone can show me a direction.
But the problem with the first approach is that all the Action methods need to have "city" as a parameter
This assumption is incorrect, since MVC automatically passes values from the current context when generating URLs, so there is no need to explicitly pass city as long as it is configured in the route and present in the URL. See this answer for how you can utilize this behavior for localization, which is similar to what you are doing.

Specify HTTP VERB for Url.Action?

I am using AttributeRouting: http://attributerouting.net/
If I have two actions with the same name, but the route for the GET is different than the route for the POST, why is Url.Action generating a URL that matches on my GET action, even though my action method is pointing to the one that is a post?
I have tried passing the verb as an
Url.Action("CreateEdit", new { method = "POST" })
However this returns the string /enrollments/create instead of /enrollments/createedit which corresponds to the POST.
I wouldn't expect this to work but tried it as some route configurations use this technique:
Url.Action("CreateEdit", new { HttpMethodConstraint = new HttpMethodConstraint("POST") })
However both of these don't solve the problem, and the URL is still for my GET route.
I have verified that I can post to a hardcoded URL of /enrollments/createedit so the POST action does indeed have that route, just to rule out the possibility that the POST Action's default [POST] is defaulting to the expected route.
Problem is that I usually avoid hardcoded URL's and use Url.Action or Html.BeginForm (which also exhibits the same issue) so that all URLs are derived from action routes.
Here are how I have defined my actions:
[GET("create")]
[GET("edit/{id:guid?}")]
public ActionResult CreateEdit(Guid? id)
...
[POST] //default to "createedit"
public ActionResult CreateEdit()
...
Note this question is not about making the actual request, but is generating route urls.
How do you indicate to Url.Action that it should use the route from the [POST] instead of the GET. There is no overload that take constraints or HTTP Verb? Note that Html.BeginForm exhibits that same issue when generating the action URL attribute, which is clearly a POST oriented method.
It also does not work if I simplify my action routes to(although the above are my goal routes):
[GET("create")]
public ActionResult CreateEdit()
...
[POST("createedit")] //default to "createedit"
public ActionResult CreateEdit()
...
Url.Action("CreateEdit", new { method = "POST" }) returns "/enrollments/create" obviously because method = 'POST' is not the correct way to specify the desired route for Url.Action. Is there a way to tell Url.Action or Html.BeginForm to specify that the POST route is desired? The method param of BeginForm effects the method="post" html attribute, but the action attribute is still generated with the wrong /enrollments/create URL. I've seen area="Admin" used to specify Areas in route parameters. Is there some magic property that can be added to specify the verb? If there is not, then clearly there is no way to get the right URL consistently and I will probably need to rename one of my actions if I need to maintain the desired routes. My hope was there was some route value I could add analogous to new { area = "SomeArea" }. method = "POST" was just my wild guess at what might work.
The request pipeline respects route constraints, if they are both the same route, then it works fine. So if both were [POST("creatededit")] and [GET("create"] then it would work fine because the URLs for both are the same, and then when the actual request is made the distinction is made by the pipeline due to the HttpMethodContraint.
If I use parameters to make the routes distinct, it works but only if I eliminate the [GET("create")]
If Url.Action has no known/supported/documented way to take a route property then the answer may simply be that Url.Action doesn't have any way for you to tell it the desired VERB.
If someone asks: Is there a way to specify the area for Url.Action?
The answer is simply: Url.Action("SomeAction", new { area ="SomeArea" })
The answer is not: muck around with your routes so you don't have two actions of the same name in different areas(or any number of other things that don't address the question).
The answer is not: You shouldn't have actions of the same name in different areas. (It might be a valid point, but it doesn't create a useful resource for others who are in a situation where they absolutely must generate a URL for a different area).
I don't understand what is so complicated about that.
I'm not asking how to make a POST request. I know how to do that, but I don't intend to hardcode URLs into my views. That turns into a maintenance nightmare.
I could change my routes if my boss wasn't so set on SOE. So I can't eliminate the [GET("create")] route.
I can resolve by renaming an action like so:
[GET("create")]
[GET("edit/{id:guid?}")]
public ActionResult CreateEdit(Guid? id)
...
[POST("createedit")]
public ActionResult CreateEditPost()
...
That is a bit dirty, but works. It still doesn't answer the question.
If there is a way to specify the VERB for Url.Action, I think the answer would be a useful resource. Maybe not to as many people as Url.Action("SomeAction", new { area ="SomeArea" }) but it would be documenting a feature of Url.Action that for me has been elusive. Maybe it doesn't support it. Perhaps I will need to delve into the source code.
I goofed in my original answer. I wasn't thinking it through really and probably answered too quickly. Here's what it boils down to:
Url.Action is tied pretty inherently to the controller/action style routing. When there's two matching actions (overloads where one is a GET version and the other is a POST), the URL should be the same, and so the GET or really first version is returned. See, AttributeRouting just sort of layers on by letting you customize the outward facing URL, but internally, Url.Action is simply trying to find a route that will get you to the requested action. Once it finds a match, it assumes that's good enough, and especially pre-MVC5, it should have been.
MVC5 introduced attribute routing as a first-class citizen, but from what I've seen, this edge case (where the GET and POST versions of the same action have different URLs and you want the POST version in particular) has not been covered.
That said, I see a couple of potential workarounds:
Use different action names. If your POST action is named something like CreateEditPost, then you can very easily do Url.Action("CreateEditPost") and get the right URL. Since your routes are not affected directly by the action name, it doesn't really matter what it's called.
Use named routes. If you name your routes, then you can use Url.RouteUrl instead and request exactly the route your want. For example:
[POST("createedit", RouteName = "CreateEditPost")]
public ActionResult CreateEdit()
{
...
}
Then:
#Url.RouteUrl("CreateEditPost")

MVC custom routing function

This is a more specific version of another of my questions: Restful MVC Web Api Inheritance, I hope an answer to this will help me answer that.
Im using ASP.NET web api,
I want to be able to route something like this: [{object}/{id}]/{controller}/{id}.
so i want an array of objects with optional /{id} ending with the 'api endpoint'.
I want to be able to route these:
/houses
/houses/3
suburbs/3/houses
council/5/suburbs/houses
city/8/council/suburbs/houses
ETC
TO
get(List<restRoute>parents, int id){
...
}
restRoute would be an object with a string for the object and an optional int (or guid etc) for the id
Does anyone know where i can start?
I don't want to route every single one individually.
I had also such problems with routing from the box in ASP.NET MVC. Its good way to be used as common routing, but is not so flexible for custom routs.
In WCF Web Api (ASP.NET web api in CTP version) was used attribute based routing.
I think its more flexible, but as negative point - each method should have routing attribute.
Take a look at this blog post:
http://www.strathweb.com/2012/05/attribute-based-routing-in-asp-net-web-api/
It describes how to implement attribute based routing using ASP.NET Web Api. Because such approach is more flexible for routes you can map to methods, it can be helpful for you.
You could use the {*anything} Variable Segmented URL pattern in your route and handle the splitting up and figuring out of what part of the url corresponds to what bit of data in your method:
Global.asax:
routes.MapRoute(
"Special", // name
"{*allthethings}", // parameters
new { controller = "Special", action = "Sauce" } // defaults
);
SpecialController:
public ActionResult Sauce()
{
string data = RouteData.Values["allthethings"].ToString();
string[] items = data.Split('/');
foreach (string item in items)
{
// do whatever you need to figure out which is what!
}
return View();
}
If you wanted to be a bit cleverer about it you could create your own custom RouteHandler to do the splitting. Something like David Ebb's PK routehandler would probably do the trick, with some customisation to fit your requirements in the processing of the route. You could use this to split up the "allthethings" parameter and turn it into your List<RestRoute> format before passing the request on to the Controller

ASP.NET MVC Route Key Words

What are all the available routing key words?
I've seen examples using resource, pathInfo, url, controller, action, etc, but I have no clue what is available for me to use.
I know if you just put an arbitrary word like "{blah}" in there, you are defining a parameter that will be used, but I'm guessing you won't be able to use "{resource}" as a parameter name.
{controller} and {action} are the only two that are reserved in MVC v1. MVC v2 may additionally reserve {area}, but the details on that are still being fleshed out. When I say "reserved", I mean that MVC gives special meaning to them, but from a Routing perspective they're all keywords just the same and are not terribly special.
If you want, you can take controller, action, or any other reserved keyword in as a parameter to your action method. They're treated just like any other parameter.

ASP.Net MVC - handling bad URL parameters

What's the best way to handle a visitor constructing their own URL and replacing what we expect to be an ID with anything they like?
For example:
ASP.Net MVC - handling bad URL parameters
But the user could just as easily replace the URL with:
https://stackoverflow.com/questions/foo
I've thought of making every Controller Function parameter a String, and using Integer.TryParse() on them - if that passes then I have an ID and can continue, otherwise I can redirect the user to an Unknown / not-found or index View.
Stack Overflow handles it nicely, and I'd like to too - how do you do it, or what would you suggest?
Here's an example of a route like yours, with a constraint on the number:
routes.MapRoute(
"Question",
"questions/{questionID}",
new { controller = "StackOverflow", action = "Question" },
new { questionID = #"\d+" } //Regex constraint specifying that it must be a number.
);
Here we set the questionID to have at least one number. This will also block out any urls containing anything but an integer, and also prevents the need for a nullable int.
Note: This does not take into account numbers that larger than the range of Int32 (-2147483647 - +2147483647). I leave this as an exercise to the user to resolve. :)
If the user enters the url "questions/foo", they will not hit the Question action, and fall through it, because it fails the parameter constraint. You can handle it further down in a catchall/default route if you want:
routes.MapRoute(
"Catchall",
"{*catchall}", // This is a wildcard routes
new { controller = "Home", action = "Lost" }
);
This will send the user to the Lost action in the Home controller. More information on the wildcard can be found here.
NB: The Catchall should reside as the LAST route. Placing it further up the chain will mean that this will handle all others below it, given the lazy nature of routes in ASP.NET MVC.
Here is some useful infromation that might help.
If you have a action method
public ActionResult Edit(int? id)
{}
then if someone types in
/Home/Edit/23
the parameter id will be 23.
however if someone types in
/Home/Edit/Junk
then id will be null which is pretty cool. I thought it would throw a cast error or something. It means that if id is not a null value then it is a valid integer and can be passed to your services etc. for db interaction.
Hope this provides you with some info that I have found whilst testing.
In ASP.NET MVC, you can define a filter implementing IActionFilter interface. You will be able to decorate your action with this attribute so that it will be executed on, before or after your action.
In your case, you will define it to be executed "before" your action. So that, you will be able to cancel it if there is an error in the passed parameters. The key benefit here that you only write the code which checking the passed paramaters once (i.e you define it in your filter) and use it wherever you want in your controller actions.
Read more about MVC filters here: http://haacked.com/archive/2008/08/14/aspnetmvc-filters.aspx
You can specify constraints as regular expressions or define custom constraints. Have a look at this blog post for more information:
http://weblogs.asp.net/stephenwalther/archive/2008/08/06/asp-net-mvc-tip-30-create-custom-route-constraints.aspx
You will still need to deal with the situation where id 43243 doesn't map to anything which could be dealt with as an IActionFilter or in your controller directly.
The problem with that approach is that they still might pass an integer which doesn't map to a page. Just return a 404 if they do that, just as you would with "foo". It's not something to worry about unless you have clear security implications.

Resources