In web api the default route is:
/api/locations/123?days=5
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
But what if I wanted the route to look like this /api/locations/123/events?days=5 while still being able to hit the LocationsController with a route like this /api/locations/123?state=md
Controller:
public class LocationsController : ApiController {
// GET api/locations/123/events?days=5
public IEnumerable<Event> GetEventsByDays(int idLocation, int days) {
// do stuff
}
// GET api/locations/123?state=md
public IEnumerable<Location> GetLocationsByState(string state) {
// do stuff
}
}
There is really two questions here:
Does it make sense to have a LocationsController that returns events or should there be a completely separate controller for that?
How would the route in the WebApiConfig be setup to allow for routes like this?
You're talking about two differents things :
Routing
Routing is used in ASP.NET MVC & Web Api to map URLs directly to a controller and/or an action. This is especially useful for readability, as the developer can focus on designing URLs that are human readable (for example, product support and search engine indexing). What is important here, is that there is no unique relation between a route and a controller. You can create 10 routes if you want that will map the same controller/action.
For example, your two routes can be like this
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{idLocation}/events/{days}",
defaults: new { id = RouteParameter.Optional }
);
Also note that /api/locations/123?state=md is not correct for the default route template. It's /api/locations/123. Because you have an extra parameter in the url, you will execute GetLocationsByState.
Controller
It's recommanded to have a single responsiblity per controller and to keep it as small as possible. Your business logic should be somewhere else.
Jeffrey Palermo (creator of Onion Architecture) say
if you can’t see a ASP.NET MVC action method on a screen without having to scroll, you have a problem
At the end, as you everything, you can do what you want without paying attention if it's good or bad. The difficulty is not always to setup an architecture but to maintain and to follow your own rules.
I hope this will help you.
I suppose you are not so familliar with routing, so do not hesitate to read intro and action selection.
You can have a separate controller for events, if you would want to manipulate events independent of location. Please see if this is of help.
Related
I have two web api methods with following route url's
[HTTPGET]
[Route("{Code}/{Id}")]
[HTTPGET]
[Route("{Code}/counter")]
Request /01/counter
{Id} is also a string parameter. Hence I am now getting error when calling Second api. "Multiple controllers action found for this Url" as webapi considers /01/counter valid for both routes.
I have seen few solutions with regex but can't find a working one yet. What is the good solution for this so that both Url's work as expected.
UPDATE:
I Found that the issue was occuring as the two methods were in different controllers hence webapi was having a problem in deciding which controller to choose. Once I moved them in the same controller, the problem is solved since , the route arguments are checked after controller is fixed.
If you are using WebAPI 2, you can use the RouteOrder property to define precedence when multiple actions match.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#route-order
[HttpGet]
[Route("{Code}/{Id}", RouteOrder = 2)]
[HttpGet]
[Route("{Code}/counter", RouteOrder = 1)]
If you are using MVC, you can use Order property:
https://learn.microsoft.com/en-us/previous-versions/aspnet/mt150670(v=vs.118)
[HttpGet]
[Route("{Code}/{Id}", Order = 2)]
[HttpGet]
[Route("{Code}/counter", Order = 1)]
There is no problem in your snippet code.
[HTTPGET]
[Route("{Code}/{Id}")]
[HTTPGET]
[Route("{Code}/counter")]
It seems that in your Web API routing configuration action is not specifying like this. It's by default setting provided by the template.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
In WebApi.config file use this code
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional, action = RouteParameter.Optional }
);
Hopefully it will solve your problem.
I've played with web api a bit before, and I always seem to run into the same problem where my methods do not get routed to.
My application has that application insights package and so I can see that it captures the requests I make - by looking at the requests line above my method signature, but they never actually execute and App Insights reports a failed request.
Here is my WebApiConfig
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
;
Here is my simple controller and method
public class ExampleController : ApiController
{
[HttpGet]
[ActionName("Test")]
public IHttpActionResult Test()
{
return Ok();
}
}
This is part of an MVC application, so when I launch the project, the Home/Index view is displayed in my browser. I then go to postman and create a new Get request pointing at
http://localhost:port/api/Example/Test
But this results in a 404.
I must be doing something wrong as I always run into this
The default mapping for WebAPI does not include the action as part of the route, as it, by default, expects the controller to be the main identifier for a resource and the GET/POST/PUT/DELETE verb to define which operation is run.
So, even though you are manually specifying the ActionName of "Test", there's nothing in the default handler to pattern match against it.
You could adjust your default mapping to include actions, like so:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
;
However, you might find you prefer attribute routing (I know I do), which you could apply to your controller like this:
[RoutePrefix("api/Example")]
public class ExampleController : ApiController
{
[HttpGet]
[Route("Test")]
public IHttpActionResult Test()
{
return Ok();
}
}
To enable attribute routing, you will need to add the following to your startup configuration:
config.MapHttpAttributeRoutes();
I have found a similar question that relates to standard asp.net controllers, but i have tried it and it doesn't seem to be working with the apicontroller.
Restrict route to controller namespace in ASP.NET Core
I want to allow for an API to be deprecated in the future without needing to change the url. To solve this i will put a version prefix at the start of the route. This way i can add some modifications to an endpoint without breaking any integrations with this API.
config.Routes.MapHttpRoute(
name: "V1 API",
routeTemplate: "v1/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "V2 API",
routeTemplate: "v2/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I want to keep the same controller names and in most cases everything will be the exact same, the response and request body will be the only things that change. I have tried to add a subdirectory in the controllers folder in order to add v1 and v2 to the namespace of the controller, but i get an error about there being 2 controllers having the same name.
How can i modify the routes so that it will point to the v1 namespace for the controller instead of just searching for any class within the controller. I dont want to have to add the version to the controller name as i want to make the revision updates as seamless as possible.
I think you could solve this issue by adding the route prefix to each controller.
Can you use a URL query parameter?
https://localhost:8000/{controller}/{action}/{id}?version=?
You could then use control flow within the controller to determine what logic to use, based on the version.
In my Web API scenario, this (default) route works well for almost all my controllers.
config.Routes.MapHttpRoute(
name: "DefaultRoute",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
and for a couple of my controllers, I want to have them mapped by their action name, like this:
config.Routes.MapHttpRoute(
name: "ActionRoute",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Background:
Most of my controllers are to be mapped by default to GEE, POST, PUT and DELETE.
However, I reserve some "admin" controllers that perform operations, such as data initialization, cleanup and testing stuff. These controllers may have multiple methods that are excetude via GET, hence the second route.
If I use either of these routes, they work well, however, if I set both, I get one of two failures:
Default route first:
api/books/ -> OK
api/books/someGuidId -> OK
api/admin/someAdminAction -> 500 Internal Server Error (multiple actions found)
api/test/sometestAction -> 500 Internal Server Error (multiple actions found)
Action route first:
api/books/ -> OK
api/books/someGuidId -> 404 Not Found (no action that matches "someGuidId")
api/admin/someAdminAction -> OK
api/test/sometestAction -> OK
My question is: how can I have these routes working out at the same time?
[EDIT]
api/books/someGuidId is not an important call, I can live without it (via GET) however, POST, PUT and DELETE to same url will fail, wich is not acceptable.
The order of the routs matter, so You should set more specific routs first:
config.Routes.MapHttpRoute(
name: "ActionRoute",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultRoute",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Edit:
Default route first:
api/books/
This url matchs the first pattern: api/{controller}/{id}.
Framework looks for books controller with GET (PUT,POST,DELETE) method, and finds it. This method is invoked with default id value.
api/admin/someAdminAction
This url also matchs the first pattern. (BTW, It doesn't matter how You name Your parameters in the route definition: id or superParamName, When Framework compares URL with routs it will look only for patterns and can not distinguish by param names.
The Framework will try to invoke GET (PUT,POST,DELETE) method of admin controller with someAdminAction id (Which, I suppose, is not a valid id value:)
Similar thing You get when You define Action route first:
api/books/
Matchs api/{controller}/{id}, id can be omitted, cause it was set to "optional".
api/books/someGuidId
Matchs api/{controller}/{action}/{id}, so, when Framework looks for someGuidId action method in book controller and it doesn't find it, it throws an exception. 404 Not Found (no action that matches "someGuidId")
Edit 2: I think, in case You will always pass id param for "Actions" and place "Actions" route first, You will not get any collisions. Try this.
If You need id param be optional in both cases, You can just remove it from both routs and pass it in a normal way via ?(question mark).
One of this would fix Your routing problem.
I am starting a new project using ASP.NET MVC4 and Visual Studio 2012. In terms of API design, most examples focus on basic CRUD operations via PUT, GET, POST and DELETE verbs on entities (as you would expect). I was reading the following:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
The article suggests that if I choose to map a route as
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{action}",
defaults: new { action = RouteParameter.Optional }
);
This is more of an RPC style approach; from which I inferred they are recommending two routes with two controllers to split up each operation:
Perhaps something like for parent entity CRUD:
routes.MapHttpRoute(
name: "Parent",
routeTemplate: "api/{controller}/{id}/{action}",
defaults: new { id = RouteParameter.Optional }
);
and for child entity CRUD:
routes.MapHttpRoute(
name: "Child",
routeTemplate: "api/user/{id}/{controller}",
defaults: new { id = RouteParameter.Optional }
);
From a data/crud perspective this makes total sense. However, what about when you want to perform a non-crud operation on an entity (i.e., /User/NoahBawdy/SignIn or /User/NoahBawdy/ChangePassword)? I could see these being a PUT or POST action, but does it really require it's own controller? Is this the wrong way to approach the API design for these types of operations?
Any insight greatly appreciated as always.
You raised some interesting points in your post. I was faced with a similar challenge on a project I am working on and my approach is to add an action parameter on the routing configuration.
With that change i could add any method to the controller and call it from the client. This avoids the need to specify multiple controllers for methods that conceptually belongs in the same controller.
Omar