Custom routing provides specialized need of routing to handle specific incoming requests.
In order to defining custom routes, keep in mind that the order of routes that you add to the route table is important.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// this is an advanced custom route
// you can define custom URL with custom parameter(s) point to certain action method
routes.MapRoute(
"CustomEntry", // Route name
"Custom/{entryId}", // Route pattern
new { controller = "Custom", action = "Entry" } // Default values for defined parameters above
);
// this is a basic custom route
// any custom routes take place on top before default route
routes.MapRoute(
"CustomRoute", // Route name
"Custom/{controller}/{action}/{id}", // Route pattern
new { controller = "Custom", action = "Index", id = UrlParameter.Optional } // Default values for defined parameters above
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // Route pattern
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Default values for defined parameters above
);
}
controller
and action
names are reserved. By default MVC maps {controller}
part of the URL to the class <controller>Controller
, and then looks for a method with the name <action>
without adding any suffixes.
Though it may be tempting to create a family of routes using {controller}/{action}/{parameter}
template consider that by doing this you disclose the structure of your application and make URLs somewhat brittle because changing the name of the controller changes the route and breaks the links saved by the user.
Prefer explicit route setting:
routes.MapRoute(
"CustomRoute", // Route name
"Custom/Index/{id}", // Route pattern
new { controller = "Custom", action = nameof(CustomController.Index), id = UrlParameter.Optional }
);
(you cannot use nameof
operator for controller name as it will have additional suffix Controller
) which must be omitted when setting controller name in the route.
User can add custom route, mapping an URL to a specific action in a controller. This is used for search engine optimization purpose and make URLs readable.
routes.MapRoute(
name: "AboutUsAspx", // Route name
url: "AboutUs.aspx", // URL with parameters
defaults: new { controller = "Home", action = "AboutUs", id = UrlParameter.Optional } // Parameter defaults
);
Along with classic way of route definition MVC WEB API 2 and then MVC 5 frameworks introduced Attribute routing
:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// This enables attribute routing and must go before other routes are added to the routing table.
// This makes attribute routes have higher priority
routes.MapMvcAttributeRoutes();
}
}
For routes with same prefix inside a controller, you can set a common prefix for entire action methods inside the controller using RoutePrefix
attribute.
[RoutePrefix("Custom")]
public class CustomController : Controller
{
[Route("Index")]
public ActionResult Index()
{
...
}
}
RoutePrefix
is optional and defines the part of the URL which is prefixed to all the actions of the controller.
If you have multiple routes, you may set a default route by capturing action as parameter then apply it for entire controller unless specific Route
attribute defined on certain action method(s) which overriding the default route.
[RoutePrefix("Custom")]
[Route("{action=index}")]
public class CustomController : Controller
{
public ActionResult Index()
{
...
}
public ActionResult Detail()
{
...
}
}
When you request the url yourSite/Home/Index
through a browser, the routing module will direct the request to the Index
action method of HomeController
class. How does it know to send the request to this specific class's specific method ? there comes the RouteTable.
Every application has a route table where it stores the route pattern and information about where to direct the request to. So when you create your mvc application, there is a default route already registered to the routing table. You can see that in the RouteConfig.cs
class.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
You can see that the entry has a name and a template. The template is the route pattern to be checked when a request comes in. The default template has Home
as the value of the controller url segment and Index
as the value for the action segment. That means, if you are not explicitly passing a controller name and action in your request, it will use these default values. This is the reason you get the same result when you access yourSite/Home/Index
and yourSite
You might have noticed that we have a parameter called id as the last segment of our route pattern. But in the defaults, we specify that it is optional. That is the reason we did not have to specify the id value int he url we tried.
Now, go back to the Index action method in HomeController and add a parameter to that
public ActionResult Index(int id)
{
return View();
}
Now put a visual studio breakpoint in this method. Run your project and access yourSite/Home/Index/999
in your browser. The breakpoint will be hit and you should be able to see that the value 999 is now available in the id
parameter.
Creating a second Route pattern
Let's say we would like a set it up so that the same action method will be called for a different route pattern. We can do that by adding a new route definition to the route table.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// New custom route definition added
routes.MapRoute("MySpecificRoute",
"Important/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
//Default catch all normal route definition
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
The new definition i added has a pattern Important/{id}
where id is again optional. That means when you request yourSiteName\Important
or yourSiteName\Important\888
, It will be send to the Index action of HomeController.
Order of route definition registration
The order of route registration is important. You should always register the specific route patterns before generic default route.
Suppose we want to have a route that allows an unbound number of segments like so:
We would need to add a route, normally at the end of the route table because this would probably catch all requests, like so:
routes.MapRoute("Final", "Route/{*segments}",
new { controller = "Product", action = "View" });
In the controller, an action that could handle this, could be:
public void ActionResult View(string[] segments /* <- the name of the parameter must match the name of the route parameter */)
{
// use the segments to obtain information about the product or category and produce data to the user
// ...
}
It's a good practice to encode state of Single Page Application (SPA) in url:
my-app.com/admin-spa/users/edit/id123
This allows to save and share application state.
When user puts url into browser's address bar and hits enter server must ignore client-side part of the requested url. If you serve your SPA as a rendered Razor view (result of calling controller's action) rather than a static html file, you can use a catch-all route:
public class AdminSpaController
{
[Route("~/admin-spa/{clienSidePart*}")]
ActionResult AdminSpa()
{
...
}
}
In this case server returns just SPA, and it then initializes itself according to the route. This approach is more flexible as it does not depend on url-rewrite module.
For using Attribute Routing in areas, registering areas and [RouteArea(...)]
definitions are required.
In RouteConfig.cs
:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
AreaRegistration.RegisterAllAreas();
}
}
In a sample area controller attribute routing definition :
[RouteArea("AreaName", AreaPrefix = "AreaName")]
[RoutePrefix("SampleAreaController")]
public class SampleAreaController : Controller
{
[Route("Index")]
public ActionResult Index()
{
return View();
}
}
For using Url.Action
links in Areas :
@Url.Action("Index", "SampleAreaController", new { area = "AreaName" })