The web api seems to be only suited for the standard use-cases.
But I want to do more complex routing but can't find documentation for complex routing.
If I have more controller, the routings gets more and more complicated.
Can i define several optional parameters with dependencies?
Like this:
/api/document/{par1}/{par2}
par1 & par2 should be optional but par2 should be only matched if par1 is present.
And are recursive parameters possible?
/api/documents/characteristics/{round/green/red-dots}
/api/documents/characteristics/{square/yellow}
/api/documents/characteristics/{square/yellow/flat}
/api/documents/characteristics/{square/yellow/flat/...}
Is there a detailed documentation for the web api routing? The microsoft tutorial is too basic...
I need more information about the routing.
I have two controllers and some trouble because two routings are quite similar, so the wrong route is taken. I can use [Action]-Attribute as a workaround, but this feels not right... I also have to consider the order of the routes. This is logical but is nowhere mentioned.
Is the web api only for simple rest api's?
Edit:
I tried this:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{mandant}/documents/{id}",
defaults: new { controller = "documents", id = RouteParameter.Optional }
);
//routes.MapHttpRoute(
// name: "DefaultApiWithAction",
// routeTemplate: "api/{mandant}/documents/{id}/{action}",
// defaults: new { controller = "documents" }
// );
I have two methods:
[AcceptVerbs("get")]
public HttpResponseMessage Get(int id)
[ActionName("file")]
[AcceptVerbs("get")]
public HttpResponseMessage filedownload(int id)
Now I have the problem, the file-action is triggered even if I comment out the second route and the normal get specific document method is not triggered because multiple actions... I tried the [NoAction] attribute but this is not working...
But why will the file-method be triggered if there is no action in the route-template? (Or if the second route is active, why will the normal get-document method not be triggered if there is no action in the url....)
I my current workaround is to set a default-action for all other methods, but this is not a good solution.
You can use routing constraints to set conditions on the routing in global.asax like:
routes.MapRoute(
"ApiRoute",
"api/document/{par1}/{par2}",
new {controller="Document", action="SomeMethod"},
new {par1 = #"\d+" }
);
In the last parameter you can specify regular expression that has to be matched for specified parameter for the route to be used. In the example above par1 is used for digits only, but you can use any regular expression, like:
routes.MapRoute(
"ApiRoute",
"api/document/{par1}/{par2}",
new {controller="Document", action="SomeMethod"},
new {par1 = #"(value1|value2|value3)" }
);
Related
I have an ASP.NET Web API I wrote and have published. Now that its out there we are looking at doing some improvements, and these improvements involve changes to certain calls which means we need to version to keep existing clients working.
I have used attribute routing so far in my app. Methods are invoked by: Controller/Action via RoutePrefix and Route attributes.
When I do need to create a V2 of my classes, I only want to recreate the classes that have actually changed, and redirect other routes back to v1 classes because they haven't changed. (Otherwise I just end up with a lot of boilerplate code, or duplicate code).
What I want to do is have the following routes work for my v1 version of classes:
Controller/Action
For V2 I want any new classes to go to V2, and any classes that haven't changed I want to return the HttpControllerDescriptor from V1 class. The route would look like v2/Controller/Action but would be redirected to Controller/Action.
I've implemented a IHttpControllerSelector and return the appropriate HttpControllerDescriptors but its not making the call into the method. I believe its because the routing information doesn't match the action. (When I put in an IHttpActionSelector and trace the exception it says "multiple actions were found that match the request).
So, I'm guess I'm wondering: Is this even possible? Is this the best way to achieve what I'm trying to do?
Here is what I implemented for versioning support in asp.net web api. Important to note I did not use attribute routing but explicit routes in WebApiConfig.cs so if you want to follow this pattern you would need to switch back to explicit routes. Also I do not prefer version information in the actual route, I use a custom (ie. "version") parameter in Accept header. I also set the version per mime type as in the below example. If version number is not set by the client or if the requested version does not exist this will fall back to default controller.
Create a class and inherit from DefaultHttpControllerSelector so you can fallback to base class behavior when you wanted to.
Override SelectController method as such:
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IDictionary controllers = GetControllerMapping();
IHttpRouteData routeData = request.GetRouteData();
string controllerName = (string)routeData.Values["controller"];
HttpControllerDescriptor controllerDescriptor;
if (string.IsNullOrWhiteSpace(controllerName))
{
return base.SelectController(request);
}
if (!controllers.TryGetValue(controllerName, out controllerDescriptor))
{
return null;
}
string version = GetVersionFromAcceptHeader(request);
if (string.Equals(version, "1"))
{
return controllerDescriptor;
}
string newName = string.Concat(controllerName, "V", version);
HttpControllerDescriptor versionedControllerDescriptor;
if (controllers.TryGetValue(newName, out versionedControllerDescriptor))
{
return versionedControllerDescriptor;
}
return controllerDescriptor;
}
Register this controller selector in your webapiconfig Register method:
config.Services.Replace(typeof(IHttpControllerSelector), new YourControllerSelector(config));
I have a web api with the following routes:
routes.MapHttpRoute(
name: "DefaultGet",
routeTemplate: "api/{controller}/{id}",
defaults: new { controller = "Home", id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = RouteParameter.Optional }
);
These are two Actions inside a Controller:
public class UserController {
public HttpResponseMessage Get(int id) {}
public HttpRespnseMesseage GetDetails(int id) {}
}
The first route allows me to access the Get()-Method by "/api/User/4711" The second route allows me to access the GetDetails()-Method by "/api/User/GetDetails/4711"
This works.
But in addition, the Get()-Method can also be called by "/api/User/Get/4711" (and is listed in the automaticly generated documentation, too)
How can I make sure, that I can access the Get-Method by "/api/User/4711", but not by "/api/User/Get/4711"?
Note: I want to keep the default routes and do not want a solution that can only be achieved by removing my default routes and using route attributes instead
You're doing something quite strange: you're mixing up deafault RESTful style routing, with action based routing.
With your current configuraton, your second route will match any ULR like /api/User/XXX/4711 where XXX is anything. No matter if XXX is Get or anything else.
The only thing that you can do to avoid the second route to accept anything is to use route constraints. But, to do so, you must have a fixed set of rules. For example, if you only want to exclude Get you can do that with a regex constraint. But if you don't know the rules, of course, you cannot implement it.
Recommendation
As the OP finally decided himself, if you're using a RESTful API routing style it's much better to use routing attributes, available since Wep API v2. (In fact there was a Nuget package to support it on previous versions).
I'm very confused about the design of my RESTful services!
If I was doing this using vanilla MVC3/4 then I would simply have action methods marked [HTTPGet] etc. and I could have multiple Get's per controller. I this way I would organise controllers by their "meta group".
I've looked at the Web API MVC4 template and it gives me the automatic translation from an Http GET to the Getxxx() method - but this implies a single Get per controller and organising controllers by object, rather than function...which seems to make some sense.
I see many posts on adding named routes - but this seems to break the natural model of Get, Post, Put, Delete. If I do that - then aren't I (in essence) just going back to vanilla MVC4?
Is there any impact on having lots of controllers?
Am I thinking
about this correctly?
Shortly,
Is there any impact on having lots of controllers?
No
Am I thinking about this correctly?
Generally yes.
Default WebAPI/MVc template uses routing that relays on prefixes and naming GetXXX, PostXX.
RouteTable.Routes.MapRoute(
"WithActionApi",
"api/{controller}/{action}/{id}"
);
But you can create your own custom routing with action names instead. Then you uses in URL name of your action method and as you've wrote Attributes to set HTTP Verbs like [HttpGet]
RouteTable.Routes.MapRoute(
"DefaultApi",
"api/{controller}/{id}",
new { action="DefaultAction", id = System.Web.Http.RouteParameter.Optional }
);
[ActionName("DefaultAction")] //Map Action and you can name your method with any text
public string Get(int id)
{
return "object of id id";
}
[HttpGet]
public IEnumerable<string> ByCategoryId(int id)
{
return new string[] { "byCategory1", "byCategory2" };
}
I am looking at using WebAPI to create a Restful API. I want to create a launch url to provide URLs to the other portions/entities of the API. The obvious place to do this is ~/api/ however I cannot seem to wire up a route that will not give a 403 result. Any ideas?
EDIT: Just to be a bit clearer. I know how the default routing works in WebAPI, I.E. if I create a CustomersController that inherits from ApiController I can get there using ~/api/customers. What I want is a step before that where I could go to ~/api/ and would get a result a bit like:
[
{ Title: 'Customers', Url: '~/api/customers' }
]
I want this as my understanding is that RESTful services are metadata (think that the term) driven (basically discoverable and provide links to other resources in result). So there should be a single source url that points to all other resources in the API.
All you need to do for this is to add a new route which handles ~/api specifically.
In your project you will notice in the Application_Start method in the Global.asax a call is made to WebApiConfig.Register(GlobalConfiguration.Configuration), the WebApiConfig class is included in your project in the App_Start folder (along with various other config classes). If you look at the implementation of the Register call you will see that's where the ~/api/controller route is actually setup i.e.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
In order to make ~/api discoverable all you need to do is introduce a new route before the default which handles any calls to ~/api e.g.
config.Routes.MapHttpRoute(
name: "DiscoverableApi",
routeTemplate: "api",
defaults: new { controller = "Discoverable", }
);
Then add an ApiController to handle that call e.g.
public class DiscoverableController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "/users", "/photos", "/history" };
}
}
If you don't want to hard-code your URLs you will most likely need to look at something like Reflection to enumerable all the available ApiController's and their reachable endpoints (i.e. actions).
Take a look at the Microsoft ASP.NET Web API Help Page package from NuGet. Out of the box you can create a list of all your API endpoints which are returned as an MVC View. However you could customise it to return Json instead. You can also configure it to hang off any route you want, i.e.
api.mysite.com/help
myapi.com/api
etc
I need to create a url scheme like this
friend/{userid}/wishlist
where friend is the controller, wishlist is the view, and userid is the id of hte friend whose wishlist you would like to see.
I have setup a route like this
routes.MapRoute(
"FriendWishlist",
"friend/{userid}/wishlist",
new { controller = "WishList", action="FriendWishlist", userid = 123}
);
when i try to browse to /friend/123/wishlist i get the following error
A public action method '123' was not
found on controller
'GiffrWeb.Areas.Api.Controllers.FriendController'.
Routes in MVC are evaluated in the order they are declared. It sounds very much like you have declared your route below the default one:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"FriendWishlist",
"friend/{userid}/wishlist",
new { controller = "WishList", action="FriendWishlist", userid = 123}
);
So the MVC framework is trying to match your URL /friend/123/wishlist first to the default route. Because it's all variables and everything has a default or is optional, it's guaranteed to match. It doesn't check if the controllers and actions exist and take the relevant arguments. You have a FriendController class - check. 123 action - it goes bang.
Simplest fix - declare the route above the default one (ie just swap these two statements) and it should work OK.
I might just add that it seems a little weird to have a URL that starts with /friend/ going to a WishList controller when you obviously have a Friend controller (your error message says so).
Finally, I can't recommend highly enough that if you introduce custom routing that you also test those routes thoroughly - as you have seen, the routing engine often might not do what you think it does. I recommend either the route testing stuff in MvcContrib or Brad Wilson's blog post.