As a result of a previous question of mine, I have discovered two ways of handling REST routes in MVC3.
This is a followup question where I am trying to learn factual differences/subtleties between these two approaches. I am looking authoritative answer if possible.
Method 1: Single Route, with Action Name + Http Verb Attributes on Controller Actions
Register a single route in Global.asax using a specified action parameter.
public override void RegisterArea(AreaRegistrationContext context)
{
// actions should handle: GET, POST, PUT, DELETE
context.MapRoute("Api-SinglePost", "api/posts/{id}",
new { controller = "Posts", action = "SinglePost" });
}
Apply both ActionName and HttpVerb attributes to controller actions
[HttpGet]
[ActionName("SinglePost")]
public JsonResult Get(string id)
{
return Json(_service.Get(id));
}
[HttpDelete]
[ActionName("SinglePost")]
public JsonResult Delete(string id)
{
return Json(_service.Delete(id));
}
[HttpPost]
[ActionName("SinglePost")]
public JsonResult Create(Post post)
{
return Json(_service.Save(post));
}
[HttpPut]
[ActionName("SinglePost")]
public JsonResult Update(Post post)
{
return Json(_service.Update(post););
}
Method 2: Unique Routes + Verb Constraints, with Http Verb Attribute on Controller Actions
Register unique routes in Global.asax with HttpMethodContraint
var postsUrl = "api/posts";
routes.MapRoute("posts-get", postsUrl + "/{id}",
new { controller = "Posts", action = "Get",
new { httpMethod = new HttpMethodConstraint("GET") });
routes.MapRoute("posts-create", postsUrl,
new { controller = "Posts", action = "Create",
new { httpMethod = new HttpMethodConstraint("POST") });
routes.MapRoute("posts-update", postsUrl,
new { controller = "Posts", action = "Update",
new { httpMethod = new HttpMethodConstraint("PUT") });
routes.MapRoute("posts-delete", postsUrl + "/{id}",
new { controller = "Posts", action = "Delete",
new { httpMethod = new HttpMethodConstraint("DELETE") });
Use only an Http Verb Attribute on the Controller Actions
[HttpGet]
public JsonResult Get(string id)
{
return Json(_service.Get(id));
}
[HttpDelete]
public JsonResult Delete(string id)
{
return Json(_service.Delete(id));
}
[HttpPost]
public JsonResult Create(Post post)
{
return Json(_service.Save(post));
}
[HttpPut]
public JsonResult Update(Post post)
{
return Json(_service.Update(post););
}
Both of these methods let me have uniquely named Controller Action Methods, and allow for RESTful routes tied to the verbs... but what is inherently different about restricting the route vs. using a proxy action name?
You won't get an authoritative answer by here are my 2 cents:
I prefer method 2 because then you have all your routing in one place. You can encapsulate your routing into a method e.g. MapResourceRoutes(string controller, string uri) and have it used muptiple times throughout your API.
Also method 2 gives you clearly named routes that you can use for linking and reverse routing.
I don't know that you will ever find an authoritative answer, but I will offer my opinion and as you can tell by my points, my opinion matters ;-). My purist self thinks that the first option is more pure, however my experience has been that helper methods like Url.Action() can sometimes have trouble resolving the correct route with this approach and I have taken to the second method as it really only has implications internally as the api looks identical to the consumer.
At this point, the correct answer is use Web API.
Related
namespace EmployeeApi.Controllers
{
public class EmployeeDetailsController : ApiController
{
// GET api/employeedetails
public IEnumerable<Employee> Get()
{
}
public IEnumerable<Details> Get(int id)
{
}
public IEnumerable<Team> GetTeamMember()
{
}
public IEnumerable<Details> GetTid(int id)
{
}
}
I would like to have my webApi something like this:
1) IEnumerable<Employee> Get() -> api/employeedetails
2) IEnumerable<Details> Get(int id) -> api/employeedetails/id
3) IEnumerable<Team> GetTeamMember() -> api/employeedetails/id/teammember
4) IEnumerable<Details> GetTid(int id) -> api/employeedetails/id/teammember/tid
I tried making changes to routing, but as I am new to it, could'nt understand much.So, please can some one help me understand and guide me on how this should be done.
Thanks in advance..:)
You could do this with Attribute Routing.
I prefere to use them as they give an easy overview on how the routing is configured when reading the controllers method.
namespace EmployeeApi.Controllers
{
public class EmployeeDetailsController : ApiController
{
// GET api/employeedetails
[Route("api/employeedetails")]
[HttpGet]
public IEnumerable<Employee> Get()
{
}
// GET api/employeedetails/1
[Route("api/employeedetails/{id}")]
[HttpGet]
public IEnumerable<Details> Get(int id)
{
}
// GET api/employeedetails/id/teammember
[Route("api/employeedetails/id/teammember")]
[HttpGet]
public IEnumerable<Team> GetTeamMember()
{
}
// GET api/employeedetails/id/teammember/1
[Route("api/employeedetails/id/teammember/{tid}")]
[HttpGet]
public IEnumerable<Details> GetTid(int tid)
{
}
}
You can also use RoutePrefix on top of the controller that specifies the prefix for the controller route, in your case the "api/employeedetails". You can find more details in the "Route Prefixes" section in the link
After the list of relevant comments has grown, I'll restructure my original answer now.
If you're not able to use attribute routing as suggested in Marcus' answer (see my update statement at the bottom), you need to configure your routes (probably in the App_Start/RouteConfig.cs file). You can try the following code there:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
name: "GetEmployeeDetails",
url: "api/employeedetails",
defaults: new { controller = "EmployeeDetails", action = "GetEmployees" }
);
routes.MapRoute(
name: "GetEmployeeDetailsById",
url: "api/employeedetails/{employeeId}",
defaults: new { controller = "EmployeeDetails", action = "GetDetails", employeeId = UrlParameter.Optional }
);
routes.MapRoute(
name: "GetTeamMember",
url: "api/employeedetails/{employeeId}/teammember",
defaults: new { controller = "EmployeeDetails", action = "GetTeams", employeeId = UrlParameter.Optional }
);
routes.MapRoute(
name: "GetTeamMemberById",
url: "api/employeedetails/{employeeId}/teammember/{teamId}",
defaults: new { controller = "EmployeeDetails", action = "GetDetailsForTeam", employeeId = UrlParameter.Optional, teamId = UrlParameter.Optional }
);
}
}
There'll probably be more routes (for example a generic default route) and also routes to be ignored, but this is out if scope for this question.
These routes correspond with the following action methods within your controller class:
public class EmployeeDetailsController : Controller
{
public IEnumerable<Employee> GetEmployees()
{
// Get your list of employees here
return ...;
}
public IEnumerable<Detail> GetDetails(int employeeId = 0)
{
// Get your list of details here
return ...;
}
public IEnumerable<Team> GetTeams(int employeeId = 0)
{
// Get your list of teams here
return ...;
}
public IEnumerable<Detail> GetDetailsForTeam(int employeeId = 0, int teamId = 0)
{
// Get your list of details here
return ...;
}
}
There is a chance that you do not need the employeeId parameter for the GetDetailsForTeam() method, since maybe the teamId is sufficient to get the desired information. If that is the case you can remove the parameter from the action method and the corresponding route.
These route configurations are pretty straightforward. Each route needs a unique name, otherwise you'll end up with a runtime error. The url - well - contains the url that route is supposed to handle. And after that you can specify the controller name, the action method to be called (these are your Get methods) and their respective parameters.
A word or two regarding naming conventions: In a controller named EmployeeDetailsController I would expect every "generically named" action method to return one or many EmployeeDetails objects (or their respective ActionResults). Therefore, a simple Get() method should return one or many EmployeeDetails objects.
In case you want to return objects of different types I would choose specific names (as suggested in my code above). In your case that would be a GetEmployees() method, a GetDetails(int employeeId = 0) method, a GetTeams(int employeeId = 0) method and a GetDetailsForTeam(int employeeId = 0, int teamId = 0) method. Note the optional parameters here.
If you have these methods in place I'd start with the routing. You need to make sure that each route can be connected to exactly one action method; that's why I asked for the complete URL in one of the comments. If you keep getting the "multiple actions were found" error, you're route URLs are not configured in such a way.
Also please note that route order does matter, though in your example I don't see any conflicting routes.
UPDATE: As an alternative you could use attribute routing, where you put the desired route directly into an attribute of your action method inside the controller. But for this to work with ASP.NET MVC 4 you'd need to install the AttributeRouting NuGet package.
I would like to have 2 methods in my controller that have the same route but differ only in the HTTP method. Specifically, if my route looks like
routes.MapRoute(
name: "DataRoute",
url: "Sample/{user}/{id}/",
defaults: new { controller = "Sample", action = "--not sure--", user = "", id = "" }
);
and I have 2 methods in my controller as such:
[HttpGet]
public void ViewData(string user, string id)
[HttpPost]
public void SetData(string user, string id)
The desired behavior is to call ViewData() if I GET Sample/a/b and call SetData() if I POST to Sample/a/b, the same URL.
I know I can just create 2 separate routes, but for design reasons I want to have one route differentiated only by GET and POST. Is there a way to configure either the route or controller to do this without having to create a new route?
With attribute routing you should be able to set the same route with different methods.
[RoutePrefix("Sample")]
public class SampleController : Controller {
//eg GET Sample/a/b
[HttpGet]
[Route("{user}/{id}")]
public void ViewData(string user, string id) { ... }
//eg POST Sample/a/b
[HttpPost]
[Route("{user}/{id}")]
public void SetData(string user, string id) { ... }
}
Don't forget to enable attribute routing before convention-based routes
routes.MapMvcAttributeRoutes();
You should edit the SetData method to take some payload from the body of the POST.
public void SetData(string user, string id, MyCustomObject data) { ... }
I need to define template based routing to controller and then attribute based for actions in ASP.NET Core. Something like:
public class Foo : Controller
{
[HttpGet]
public object Get()
{
return new
{
ID = "A"
};
}
[HttpPost]
public object Create([FromBody]dynamic entity)
{
return new
{
ID = "B"
};
}
}
Route
app.UseMvc(routes =>
{
routes.MapRoute("Settings", "settings/api/foo",
new { controller = "Foo" }
);
});
And I expect this to work:
GET /settings/api/foo
POST /settings/api/foo
Unfortunately it is not a case. It looks like route attributes are ignored. What is the best way to achieve requirement?
The trick here is to route the URL to a specific controller and action. Then use action method overloading with an action method selector to switch between the GET and POST.
Change your route setup code to this:
app.UseMvc(routes =>
{
routes.MapRoute(
"Settings",
"settings/api/foo",
new {
controller = "Foo", // specific controller
action = "DoThing", // AND specific action
}
);
});
And change the controller to have both action methods (or however many you want - one for each HTTP verb) to have the same name, but using different action methods selectors:
public class FooController : Controller
{
[HttpGet] // different action method selector!
public object DoThing() // same name!
{
return new
{
ID = "A"
};
}
[HttpPost] // different action method selector!
public object DoThing([FromBody]dynamic entity) // same name!
{
return new
{
ID = "B"
};
}
}
This way MVC will route all the requests for that URL to an action called DoThing on controller Foo. Once it gets there it sees "oh my, oh my, there are two actions with the same name!" But then it sees the [HttpGet] and [HttpPost] action method selectors, and whichever one of them says it can handle the request will win.
How can I get the url from web api in my view?
Example (from the msdn-blog):
[RoutePrefix("reviews")]
public class ReviewsController : ApiController
{
// eg.: /reviews
[Route]
public IHttpActionResult Get() { ... }
// eg.: /reviews/5
[Route("{reviewId}")]
public IHttpActionResult Show(int reviewId) { ... }
// eg.: /reviews/5/edit
[Route("{reviewId}/edit")]
public IHttpActionResult Edit(int reviewId) { ... }
}
Now I want to construct "/reviews/edit" in my view, how can I do this?
I've tried creating a little extension method, but it requires me to give every route an actual "RouteName". Is there a method I can use (like in MVC) where I can just pass the controller and action?
#Url.Action("Edit", "Reviews)
The method I'm using now (with RouteName) also doesn't allow me to use integers as parameters (unless I pass a default value). If I do need to name all my routes, how can I create a route url, but pass my parameters in the "data"-portion of my request?
Current method:
public static string ResolveWebApiRoute(this UrlHelper urlHelper, string routeName, object routeValues = null)
{
var newRouteValues = new RouteValueDictionary(routeValues);
newRouteValues.Add("httproute", true);
return urlHelper.RouteUrl(routeName, newRouteValues);
}
EDIT
When I used methods like Url.RouteUrl(new { controller = ..., action = ...}), It redirects directly to that action (e.g. new { controller = "Reviews", action = "Show"} --> /reviews/show, whilest I want it to redirect to /reviews/...
Generating links to Web API routes always require a RouteName, so you should have something like below:
[Route("{reviewId}/edit", Name="EditView")]
public IHttpActionResult Edit(int reviewId) { ... }
You can then generate a link like /reviews/1/editto Web API.
Url.RouteUrl(routeName: "EditView", routeValues: new { httpRoute = true, reviewId = 1 });
or
Url.HttpRouteUrl(routeName: "EditView", routeValues: , reviewId = 1)
Note that route names need to be specified explicitly and they are no longer generated automatically like what #Karhgath is suggesting. This was a change made from RC to RTM version.
When using route attributes I was able to get the route of a WebApi2 controller from an MVC view using something like this:
Url.HttpRouteUrl("RouteName", new { })
In WebApi2 when using AttributeRouting, route names are named by default Controller.Action, but you could specify a RouteName also:
[RoutePrefix("reviews")]
public class ReviewsController : Controller
{
// The route name is defaulted to "Reviews.Index"
[Route]
public ActionResult Index() { ... }
// The route name is "ShowReviewById"
[Route("{reviewId}"), RouteName("ShowReviewById")]
public ActionResult Show(int reviewId) { ... }
// The route name is by default "Reviews.Edit"
[Route("{reviewId}/edit")]
public ActionResult Edit(int reviewId) { ... }
Then to call it in the view you only need to set the route name and send the parameters as an anonymous object:
// Outputs: /reviews/123
#Url.Action("ShowReviewById", new { reviewId = 123 })
// Outputs: /reviews/123/edit
#Url.Action("Reviews.Edit", new { reviewId = 123 })
I have multiple controllers with different actions (no "Index" actions). The actions I consider "default" actions, are named differently. I want to create default routes for their names and have the first available action (from my list of default actions) executed if only the controller name is provided in the route.
So, for example, I have the following actions which I want to consider default and want checked for their existence in a controller in this order:
List()
Draw()
ViewSingle()
The routing should somehow search for /{controller} and then take the first available action from the list above as default action, e.g.:
/ControllerA -> ControllerA.List()
/ControllerB -> ControllerB.Draw()
/ControllerC -> ControllerC.ViewSingle()
/ControllerD -> ControllerD.Draw()
/ControllerE -> ControllerE.List()
Is this possible? I tried creating additional Default actions like this but couldn't get it to work:
routes.MapRoute("Default1", "{controller}/{action}",
new { controller = UrlParameter.Optional, action = "List" }
routes.MapRoute("Default2", "{controller}/{action}",
new { controller = UrlParameter.Optional, action = "Draw" }
routes.MapRoute("Default3", "{controller}/{action}",
new { controller = UrlParameter.Optional, action = "ViewSingle" }
Help?
I think you got something wrong about the default route. Those controller and action parameters are there for convention. If URL has a controller name in it, it will route to suitable controller. Same thing is true for Index methods. It just provides a default value for that.They are already optional and that's why. I think you don't need routes here: You could try to place your default functions in Index methods and it would work.Just to be clear:
public class ControllerA:Controller
{
public ActionResult Index()
{
return List();
}
public ActionResult List()
{
//List function
}
}
public class ControllerB:Controller
{
public ActionResult Index()
{
return Draw();
}
public ActionResult Draw()
{
//Draw function
}
}
I think it would work. But if you want to go on with creating routes, you create each route like this:
routes.MapRoute("Default1", "ControllerA",
new { controller = ControllerA, action = "List" }
Notice that ControllerA is not in curly braces,it's a static text. So you create routes for each controller. Keep controller naming convention in mind.
I agree with Narsil. You should write a constant to url. -like controller name- 'cause MVC cannot resolve this routes and cannot figure out what you want to show.
eg.
routes.MapRoute(null, "ControllerA/{action}",
new { controller = "ControllerA", action = "List" }
routes.MapRoute(null, "ControllerB/{action}",
new { controller = "ControllerB", action = "Draw" }
routes.MapRoute(null, "ControllerC/{action}",
new { controller = "ControllerC", action = "ViewSingle" }