Url Rewriting and Routing is how ASP.NET MVC matches a URI to an action. MVC 5 supports a new type of routing, called attribute routing, which is also works in ASP.NET vNext. Attribute routing uses attributes to define routes. Attribute routing provides us more control over the URIs in your web application.
The MVC4 or earlier style of routing, which is called convention-based routing, is still fully supported. In fact, the major advantage of MVC5 is that, we can combine both techniques in the same application. In this article I will try to cover the basic features and options of Attribute Routing and Url Rewriting in ASP.NET MVC5.
How to Enabling Attribute Routing in MVC5
To enable attribute routing, call MapMvcAttributeRoutes during configuration. Following are the code snipped.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
}
}
In MVC5, we can combine attribute routing with convention-based routing. Following are the code snipped.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Optional URI Parameters and Default Values in Attribute Routing in MVC5
It is very easy to make a URI parameter optional by adding a question mark to the route parameter. We can also specify a default value by using the form parameter=value. Following are a code sample shows, how it can be done.
public class StudentController : Controller
{
// eg: /students
// eg: /students/1
[Route("students/{id?}")]
public ActionResult View(string id)
{
if (!String.IsNullOrEmpty(id))
{
return View("ViewStudent", GetStudent(id));
}
return View("AllStudents", GetStudents());
}
// eg: /students/category
// eg: /students/category/general
// eg: /students/category/obc
[Route("students/category/{category=general}")]
public ActionResult ViewByCategory(string category)
{
return View("ViewStudent", GetStudentByCategory(category));
}
}
In this example, both /students and /students/1 will route to the “View” action, the former will result with listing all students, and the latter will list the specific student. Both /students/category and /students/category/general will be treated the same.
Route Prefixes (Prefix in Url in MVC)
N ASP.NET MVC5 the routes in a controller all start with the same prefix. For example:
public class StudentsController : Controller
{
// eg: /students
[Route("students")]
public ActionResult Index() { ... }
// eg: /students/5
[Route("students/{studentId}")]
public ActionResult Show(int studentId) { ... }
// eg: /students/5/edit
[Route("students/{studentId}/edit")]
public ActionResult Edit(int studentId) { ... }
}
To achieve same features, we can set a common prefix for an entire controller by using the [RoutePrefix] attribute. Following are the code sample.
[RoutePrefix("students ")]
public class StudentsController : Controller
{
// eg.: /students
[Route]
public ActionResult Index() { ... }
// eg.: /students/5
[Route("{studentId}")]
public ActionResult Show(int studentId) { ... }
// eg.: /students/5/edit
[Route("{studentId}/edit")]
public ActionResult Edit(int studentId) { ... }
}
To change this Url Prefix, we must use a tilde (~) on the method attribute to override the route prefix. Following code sample shows how it can be done.
[RoutePrefix("students")]
public class StudentsController : Controller
{
// eg.: /college-students
[Route("~/college-students")]
public ActionResult ShowStudents() { ... }
//TODO: any logic
...
}
Default Route in MVC5
In some context we may want to apply the [Route] attribute on the controller level, and trying to capture the action as a parameter. That route would then be applied on all actions in the controller, unless a specific [Route] has been defined on a specific action, overriding the default set on the controller. Following are a code sample to do same.
[RoutePrefix("students")]
[Route("{action=index}")]
public class StudentsController : Controller
{
// eg.: /students
public ActionResult Index() { ... }
// eg.: /students/archive
public ActionResult Archive() { ... }
// eg.: /students/new
public ActionResult New() { ... }
// eg.: /students/edit/5
[Route("edit/{studentId:int}")]
public ActionResult Edit(int studentId) { ... }
}
Route Constraints in MVC5
Route constraints allow us to restrict how the parameters in the route template are matched. The general syntax is {parameter:constraint}. Following code sample shows, how it could be achieved:
// eg: /students/5
[Route("students/{id:int}"]
public ActionResult GetStudentById(int id) { ... }
// eg: students/ramesh
[Route("students/{name}"]
public ActionResult GetStudentByName(string name) { ... }
In above code sample, the first route will only be selected if the "id" segment of the URI is an integer. Otherwise, the second route will be chosen. It is one of the best features on new Attribute Routing. Following table lists the constraints that are supported.
Constraint | Description | Example |
---|
alpha | It Matches uppercase or lowercase Latin alphabet characters (a-z, A-Z) | {x:alpha} |
bool | It Matches a Boolean value. | {x:bool} |
datetime | It Matches a DateTime value. | {x:datetime} |
decimal | It Matches a decimal value. | {x:decimal} |
double | It Matches a 64-bit floating-point value. | {x:double} |
float | It Matches a 32-bit floating-point value | {x:float} |
guid | It Matches a GUID value. | {x:guid} |
int | It Matches a 32-bit integer value. | {x:int} |
length | It Matches a string with the specified length or within a specified range of lengths. | {x:length(6)} {x:length(1,20)} |
long | It Matches a 64-bit integer value. | {x:long} |
max | It Matches an integer with a maximum value. | {x:max(10)} |
maxlength | It Matches a string with a maximum length. | {x:maxlength(10)} |
min | It Matches an integer with a minimum value. | {x:min(10)} |
minlength | It Matches a string with a minimum length. | {x:minlength(10)} |
range | It Matches an integer within a range of values. | {x:range(10,50)} |
regex | It Matches a regular expression. | {x:regex(^\d{3}-\d{3}-\d{4}$)} |
Notice that some of the constraints, such as "min", take arguments in parentheses. We can apply multiple constraints to a parameter, separated by a colon. To Specifying that a parameter is Optional (via the '?' modifier) should be done after inline constraints. Following are the code examples:
[Route("students/{id:int:min(1)?}")]
public ActionResult GetStudentById(int id) { ... }
Custom Route Constraints for URL Rewriting
If we want to create custom route constraints, this can be achieved by implementing the IRouteConstraint interface. For example, the following constraint restricts a parameter to set of valid values:
public class ValuesConstraint : IRouteConstraint
{
private readonly string[] validOptions;
public ValuesConstraint(string options)
{
validOptions = options.Split('|');
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
object value;
if (values.TryGetValue(parameterName, out value) && value != null)
{
return validOptions.Contains(value.ToString(), StringComparer.OrdinalIgnoreCase);
}
return false;
}
}
In above code we have defined our custom Route Constraints. We need to register this Route Constraint, Following code sample shows, how to register custom Route Constraints.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
var constraintsResolver = new DefaultInlineConstraintResolver();
constraintsResolver.ConstraintMap.Add("values", typeof(ValuesConstraint));
routes.MapMvcAttributeRoutes(constraintsResolver);
}
}
Now, we can apply this Route Constraints in our MVC application. Given code sample shows the same:
public class StudentsController : Controller
{
// eg: show/active and /show/inactive but
[Route("show/{status:values(active|inactive)}")]
public ActionResult Show(string status)
{
return Content("Student is " + status);
}
}
Route Names
We can specify a name for a route, this name will help us to easily allow URI generation for it in our application. For example, for the following route:
[Route("dashboard", Name = "admindashboard")]
public ActionResult AdminDashboard() { ... }
We could generate a link using Url.RouteUrl:
<a href="@Url.RouteUrl(" admindashboard")">Main Dashboard</a>
Areas
Areas in MVC is one of the important features to be used in categories code files in large application. We can define that a controller belongs to an area by using the [RouteArea] attribute. When doing so, you can safely remove the AreaRegistration class for that area.
[RouteArea("Admin")]
[RoutePrefix("dashboard")]
[Route("{action}")]
public class AdminController : Controller
{
// eg: /admin/dashboard/login
public ActionResult Login() { ... }
// eg: /admin/dashboard/show-options
[Route("show-options")]
public ActionResult Options() { ... }
// eg: /stats
[Route("~/stats")]
public ActionResult Stats() { ... }
}
With the help of above controller, the following link generation call will result with the string "/Admin/dashboard/show-options"
Url.Action("Options", "Dashboard", new { Area = "Admin" })
MVC5 also allow us to set up our custom prefix for the area that defer from the area name, We need to use AreaPrefix named parameter, for example:
[RouteArea("BackOffice", AreaPrefix = "back-office")]
In some cases, there may be requirement to use both Areas and Route Attributes and may be Areas with convention based routes, Which is set by an AreaRegistration class, then you need to make sure that area registration happen after MVC attribute routes are configured, however before the default convention-based route is set. The reason behind this is that route registration should be ordered from the most specific (attributes) through more general (area registration) to the mist generic (the default route) to avoid generic routes from “hiding” more specific routes by matching incoming requests too early in the pipeline. Following are code snipped to do the same:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
AreaRegistration.RegisterAllAreas();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Summary:
in this article I have discussed the Route Attributes in MVC5 and how we can create different urls as par different scenarios. We have also learn to create custom Route Constraints also to uses Areas in our MVC application. I am sure, that you will find this article very useful.