I have a controller called News in my WebAPI project, I have two default actions called Get that handled the following URL's:
/api/News <- this returns a list of news
/api/News/123 <- this returns a specific news item by id.
All straightforward so far and obviously the default route handles these scenarios. I next want to have a URL that looks like this:
/api/News/123/Artists <- will return all artists related to the specified news item.
Now I am fairly news to ASP.Net MVC and WebAPI so this is the first time I have had to deal with routing. I have modified my default route to look like this:
routes.MapRoute(
name: "Default",
url: "{controller}/{id}/{action}",
defaults: new { controller = "News", action = "Get", id = UrlParameter.Optional }
So here I have moved the {action} to the end of the URL and I have added a Artists method to my News controller. This still works with the first two scenarios but returns a 404 for the third scenario.
Obviously the routing isn't working for /api/News/123/Artists but I have no idea why.
I can't seem to find any examples of people using WebAPI like this which makes me think I am doing something fundamentally wrong.
Any help would be appreciated.
The issue is, that you are trying to acces Web API but mapping the ASP.NET MVC
this is a mapping you need:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{action}",
defaults: new {controller = "News", action = "Get", id = RouteParameter.Optional}
);
And it should be done in the App_Start \ WebApiConfig (if using the default template settings)
Example of the methods (in your news API controller):
// GET api/values/5
public string Get(int id)
{
return "value " + id;
}
// GET api/values/5
[HttpGet]
public string Artist(int id)
{
return "artist " + id;
}
The AttributeRouting should be a good solution. It can be installed by Nuget, and the document is here.
Some examples
public class SampleController : Controller
{
[GET("Sample")]
public ActionResult Index() { /* ... */ }
[POST("Sample")]
public ActionResult Create() { /* ... */ }
[PUT("Sample/{id}")]
public ActionResult Update(int id) { /* ... */ }
[DELETE("Sample/{id}")]
public string Destroy(int id) { /* ... */ }
[Route("Sample/Any-Method-Will-Do")]
public string Wildman() { /* ... */ }
[GET("", ActionPrecedence = 1)]
[GET("Posts")]
[GET("Posts/Index")]
public ActionResult Index() { /* ... */ }
[GET("Demographics/{state=MT}/{city=Missoula}")]
public ActionResult Index(string state, string city) { /* ... */ }
}
It works very well about custom routing.
Update
In asp.net WebApi 2, AttributeRouting is included inside by native. It has some history, the first version, asp.net WebApi 1, is weak about routing annotations.
And then, asp.net WebApi 2 is released, the AttributeRouting is included by native. So, that open project is not maintained anymore, said in GitHub page.
In microsoft blog, the section Independent Developer Profile – Tim McCall – Attribute Routing in MVC and Web API 2 said about the history too.
In routing Action is the action name on the method that you want to route to .That action name should be in the attribute used on the method.
[HttpGet]
[ActionName("CustomAction")]
public HttpResponseMessage MyNewsFeed(Some params)
{ return Request.CreateResponse(); }
Now your route should look like this
routes.MapRoute(
name: "Default",
url: "{controller}/{id}/{action}",
defaults: new { controller = "News", action = "CustomAction", id = UrlParameter.Optional
Let me know if this helps.
Related
Looking at the help Attribute Routing in ASP.NET MVC 5 it is easy to see how to constraint a parameter as below:
[Route("edit/{promoId:int?}")]
public ActionResult Edit(int? promoId) { … }
So this route will only accept promoId with int values or empty.
Some valid URLs for this route would be:
/promotions/edit/5
/promotions/edit/
But how to set a RouteAttribute to accept "/promotions/edit/promoId=5"?
You try to set it in RouteConfig.cs in your App_Start folder by pointing that kind of URL to your action.
routes.MapRoute(
name: "Edit",
url: "{controller}/{action}/promoId={promoId}",
defaults: new { controller = "Promotions", action = "Edit", promoId = UrlParameter.Optional }
);
Actually, I think the url should be in this format:
/promotions/edit?promoId=5
Note the ?. It's the beginning of the query string marker.
It should be possible to do it this way:
[Route("edit")]
public ActionResult Edit([FromUri]int promoId)
{
...
}
I am learning ASP.NET MVC and URL routing seems to be a very tough chapter in whole MVC. I read many blog post and many other SO questions but none of them helped the way to understand every aspect of routing.
I would like to have a URL likes www.sitename.com/controller/action/username/userid. How can I do it with MVC routing? A detailed answer to cover every aspect of it would be very helpful.
1. Using Traditional convention-based routing
Update your route registrations to include this new url pattern. Make sure you do this before registering the default route registration.
So in your RouteConfig file
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{userName}/{userId}",
defaults: new { controller = "Home", action = "Details" }
);
routes.MapRoute(
name: "Default2",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
This will route all the requests matching that url pattern to the corresponding controller's action method (with the 2 parameters). This means, If you have 2 controllers, both having an action method with userName and userId params, based on your controller & action method name in the request url, the corresponding method will be returned.
2. Attribute Routing
public class HomeController : Controller
{
[Route("Home/Details/{userName}/{userId}")]
public ActionResult Details(string userName,int userId)
{
return Content("Details : "+userName + userId);
}
}
Here we are registering the new route which says when the request url is Home/Details/{userName}/{userId}, return the response from Details action method of Home controller. ( This is very specific as we define specific controller name and action name)
This is an official article on ASP.NET MVC Attribute Routing which for most scenarios is sufficient. Sometimes in advanced scenarios you need to configure routes in the Global.asax/Bootstrapper.
Basically it would look like:
[RoutePrefix("Controller")]
public class Controller : Controller
{
[Route("action/{username}/{userid:int}")]
public ActionResult Action(string username, int userid){}
}
I did that from memory so it might not be exactly right, please refer to the documentation in that link and what's on MSDN.
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.
What would be the easiest way to make a page title to be the url?
Currently I have:
http://localhost:53379/Home/Where
http://localhost:53379/Home/About
http://localhost:53379/Home/What
and would like to have
http://localhost:53379/where-to-buy
http://localhost:53379/about-us
http://localhost:53379/what-are-we
I thought about adding a route to each page (there's only 9 pages) but I wonder if there's something better, for example for big sites.
routes.MapRoute(
name: "Default",
url: "where-to-buy",
defaults: new {
controller = "Home",
action = "Where",
id = UrlParameter.Optional
}
);
...
and I would like to have it in English and Local language as well, so adding more routes would not make that much sense...
If you need to fetch pages dynamically from the database, define a new route which will catch all requests. This route should be defined last.
routes.MapRoute(
name: "Dynamic",
url: "{title}",
defaults: new {
controller = "Home",
action = "Dynamic",
title = ""
}
)
Then in your controller:
public class HomeController {
public ActionResult Dynamic(string title) {
// All requests not matching an existing url will land here.
var page = _database.GetPageByTitle(title);
return View(page);
}
}
Obviously all pages need to have a title (or slug, as it's commonly referred to) defined.
If you have static actions for each page, you could use AttributeRouting. It will allow you to specify the route for each action using an attribute:
public class SampleController : Controller
{
[GET("Sample")]
public ActionResult Index() { /* ... */ }
[POST("Sample")]
public ActionResult Create() { /* ... */ }
[PUT("Sample/{id}")]
public ActionResult Update(int id) { /* ... */ }
[DELETE("Sample/{id}")]
public string Destroy(int id) { /* ... */ }
[Route("Sample/Any-Method-Will-Do")]
public string Wildman() { /* ... */ }
}
I use it on a mid-sized project and it's working pretty well. The big win is that you always know where your routes are defined.
I have actions
public virtual ActionResult Show(string userId)
and
public virtual ActionResult Show(int groupId)
In Global.asax I have
routes.MapRoute(
"Group_Default",
"{controller}/{action}/{groupId}",
MVC.Groups.Show()
);
routes.MapRoute(
"UserProfile_Default",
"{controller}/{action}/{userId}",
MVC.Profile.Show()
);
Now when I request for group/show/... it works fine. But when I call Profile/Show/... parameter is null. But if I remove UserProfile_Default then both works but profile URL contains question mark for parameter (and I want it clean like .../profile/show/5678)
It seams that somehow one route block another.
Try these instead:
routes.MapRoute(
"Group_Default",
"Group/{action}/{groupId}",
new { controller = "Group" }
);
routes.MapRoute(
"UserProfile_Default",
"Profile/{action}/{userId}",
new { controller = "Profile" }
);
For future reference, the route debugger is a really good tool to see exactly what's going on with your routing and which URLs are hitting what actions: http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx