ASP.NET Core MVC Routing

Introduction to Routing

Routing is the process of mapping incoming HTTP requests to the appropriate MVC controller actions or Razor Pages. In ASP.NET Core MVC, routing is highly configurable and allows you to define complex URL structures that are both user-friendly and search-engine-optimized.

The core of ASP.NET Core routing is the routing middleware, which inspects the request URL and the application's defined routes to determine how to handle the request. This process involves matching the URL against a set of route patterns.

Route Templates

Route templates are strings that define the structure of URLs. They can include literal values and parameters. Parameters are enclosed in curly braces ({}) and act as placeholders for values in the URL.

For example, a route template like products/{category}/{id} would match URLs such as /products/electronics/123. The values electronics and 123 would be extracted and passed as arguments to the action method.

Default Route Template

The default route template in ASP.NET Core MVC often looks like this:

"/{controller=Home}/{action=Index}/{id?}"
  • {controller=Home}: Specifies the controller name. If omitted in the URL, it defaults to the Home controller.
  • {action=Index}: Specifies the action method name. If omitted, it defaults to the Index action.
  • {id?}: Specifies an optional parameter named id. The question mark makes it optional.

Route Constraints

You can apply constraints to route parameters to ensure that only values matching specific criteria are accepted. Constraints are typically defined as regular expressions.

Example of a route with a constraint for the id parameter:

"products/{category}/{id:int}"

This route will only match URLs where the id is an integer.

Common Constraints

  • int: The value must be an integer.
  • float: The value must be a floating-point number.
  • guid: The value must be a GUID.
  • alpha: The value must contain only alphabetic characters.
  • decimal: The value must be a decimal number.
  • max-length(n): The value must have a maximum length of n.
  • min-length(n): The value must have a minimum length of n.

Attribute Routing vs. Convention-Based Routing

ASP.NET Core MVC supports two primary routing approaches:

Convention-Based Routing

This is the traditional approach where routes are defined globally in the Startup.cs file (or Program.cs in .NET 6+ minimal APIs). It relies on conventions and a central configuration.

// In Startup.cs or Program.cs
services.AddControllersWithViews();
// ...
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

Attribute Routing

Attribute routing allows you to define routes directly on your controller actions using attributes. This offers more flexibility and can make routes more self-contained and easier to understand within the controller itself.

Example: Attribute Routing

using Microsoft.AspNetCore.Mvc;

namespace MyProject.Controllers
{
    public class OrdersController : Controller
    {
        [Route("api/orders")]
        public IActionResult GetOrders()
        {
            // ... logic to get orders
            return Ok("List of orders");
        }

        [Route("api/orders/{id:int}")]
        public IActionResult GetOrderById(int id)
        {
            // ... logic to get order by id
            return Ok($"Order details for ID: {id}");
        }
    }
}

To enable attribute routing, ensure you have:

// In Startup.cs or Program.cs
services.AddControllersWithViews();
// ...
app.UseRouting(); // Ensure UseRouting is called before UseEndpoints
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers(); // For attribute routing
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}"); // For convention-based routing
});

Route Parameters and Action Method Arguments

Route parameters are automatically mapped to the parameters of your action methods based on their names. If the parameter names match, ASP.NET Core handles the binding automatically.

If parameter names don't match, or for more complex scenarios, you can use the [FromRoute] attribute:

[Route("items/{itemId}")]
public IActionResult GetItemDetails([FromRoute] int itemId)
{
    // ...
    return Ok($"Details for item {itemId}");
}

Creating Custom Route Constraints

For more advanced scenarios, you can create custom route constraints by implementing the IRouteConstraint interface.

Example: Custom Constraint for Specific Keywords

Define a constraint:

using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Http;
using System.Linq;

public class AllowedCategoryConstraint : IRouteConstraint
{
    private readonly string[] _allowedCategories;

    public AllowedCategoryConstraint(params string[] allowedCategories)
    {
        _allowedCategories = allowedCategories.Select(c => c.ToLower()).ToArray();
    }

    public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (values.TryGetValue(routeKey, out object? value) && value != null)
        {
            return _allowedCategories.Contains(value.ToString()?.ToLower());
        }
        return false;
    }
}

Register and use the constraint:

// In Startup.cs or Program.cs
services.AddRouting(options =>
{
    options.ConstraintMap.Add("allowedcat", typeof(AllowedCategoryConstraint));
});

// In your route configuration (e.g., Program.cs)
endpoints.MapControllerRoute(
    name: "productByCategory",
    pattern: "products/{category:allowedcat}",
    defaults: new { controller = "Products", action = "ListByCategory" },
    constraints: new { category = new AllowedCategoryConstraint("electronics", "books", "clothing") }
);

Summary

Understanding and effectively utilizing ASP.NET Core MVC routing is crucial for building robust, maintainable, and user-friendly web applications. Whether you choose convention-based routing, attribute routing, or a combination of both, mastering route templates, constraints, and parameter binding will empower you to create elegant URL structures.