.NET Routing
Routing is a fundamental concept in web development that determines how an application responds to client requests. In .NET web applications, routing is the mechanism that maps incoming HTTP requests to the appropriate controller actions or endpoint handlers. This guide will walk you through the routing system in ASP.NET Core applications, from basic concepts to advanced techniques.
What is Routing?
Routing is the process of matching incoming HTTP requests to specific handlers based on URL patterns. In simpler terms, routing decides which piece of code should execute when a user navigates to a particular URL in your application.
For example, when a user visits https://yourwebsite.com/products/5
:
- The routing system analyzes the URL path
/products/5
- It determines which controller and action method should handle this request
- It may extract the value
5
as a parameter to pass to the handler
Why Routing Matters
Understanding routing is crucial because it:
- Creates clean, SEO-friendly URLs
- Separates the URL structure from the physical file structure of your application
- Provides flexibility in how you organize your code
- Enables more maintainable and testable applications
- Allows for versioning and other advanced patterns
Basic Routing in ASP.NET Core
Convention-Based Routing
Convention-based routing establishes a pattern that maps URLs to controllers and actions without explicitly configuring each route. This approach is commonly used in MVC applications.
Here's how to set up basic convention-based routing in your Program.cs
file:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
This creates a default route pattern with three segments:
{controller=Home}
: The controller name (defaults to "Home" if not specified){action=Index}
: The action method name (defaults to "Index" if not specified){id?}
: An optional parameter named "id"
With this configuration, these URLs map as follows:
URL | Controller | Action | Parameters |
---|---|---|---|
/ | HomeController | Index() | none |
/Home | HomeController | Index() | none |
/Home/Index | HomeController | Index() | none |
/Home/About | HomeController | About() | none |
/Products/Details/5 | ProductsController | Details() | id = 5 |
Attribute Routing
Attribute routing uses attributes to define routes directly on controller classes or action methods. This approach offers more control and clarity, especially for complex routing requirements.
Example of attribute routing:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetProducts()
{
// Return all products
return Ok(new[] { "Product 1", "Product 2" });
}
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
// Return a specific product
return Ok($"Product {id}");
}
[HttpPost]
public IActionResult CreateProduct([FromBody] ProductModel product)
{
// Create a new product
return CreatedAtAction(nameof(GetProduct), new { id = 42 }, product);
}
}
With this configuration, the following URLs map to the corresponding actions:
GET /api/products
→GetProducts()
GET /api/products/5
→GetProduct(5)
POST /api/products
→CreateProduct(product)
Route Parameters and Constraints
Route Parameters
Route parameters are variable segments in the URL that get passed to your handler method. There are several types:
- Required parameters: Parts of the route that must be present
- Optional parameters: Parts that can be omitted (marked with
?
) - Catch-all parameters: Capture multiple segments (prefixed with
*
)
Example of different parameter types:
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
// Required parameter
[HttpGet("{id}")]
public IActionResult GetById(int id) => Ok($"Product {id}");
// Optional parameter
[HttpGet("search/{category?}")]
public IActionResult Search(string? category) =>
Ok($"Searching products in category: {category ?? "All"}");
// Catch-all parameter
[HttpGet("catalog/{**path}")]
public IActionResult BrowseCatalog(string path) =>
Ok($"Browsing catalog: {path}");
}
Route Constraints
Route constraints restrict how a parameter matches URLs. They validate the parameter value and reject the request if validation fails.
Common route constraints include:
int
: Must be an integerbool
: Must be a booleandatetime
: Must be a valid DateTimeguid
: Must be a valid GUIDalpha
: Must be alphabetical charactersregex()
: Must match a regular expression
Example of route constraints:
app.MapControllerRoute(
name: "product",
pattern: "product/{id:int}/details",
defaults: new { controller = "Products", action = "Details" });
app.MapControllerRoute(
name: "blog",
pattern: "blog/{year:int:min(2000)}/{month:int:range(1,12)}/{slug:alpha}",
defaults: new { controller = "Blog", action = "Post" });
With attribute routing:
[HttpGet("archive/{year:int:min(2000)}/{month:int:range(1,12)}")]
public IActionResult Archive(int year, int month)
{
return Ok($"Archive for {month}/{year}");
}
Advanced Routing Techniques
Route Templates and Token Replacement
Route templates can include tokens that get replaced with values. Common tokens include:
[controller]
: Replaced with the controller name (minus the "Controller" suffix)[action]
: Replaced with the action method name
Example:
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("[action]")]
public IActionResult Featured() => Ok("Featured products");
[HttpGet("[action]/{id}")]
public IActionResult Review(int id) => Ok($"Review for product {id}");
}
This creates routes:
GET /api/products/featured
GET /api/products/review/5
Route Priority and Order
When multiple routes could match a URL, ASP.NET Core uses a scoring system to determine which route takes precedence:
- Routes with more literal segments win over routes with parameters
- Routes with regular parameters win over routes with catch-all parameters
- Routes with constraints win over routes without constraints
You can explicitly set route order using the Order
property:
[HttpGet("products/{id}", Order = 1)]
public IActionResult GetProduct(int id) => Ok($"Product {id}");
[HttpGet("products/featured", Order = 0)] // Lower order value means higher priority
public IActionResult GetFeaturedProducts() => Ok("Featured products");
Area Routing
Areas help organize large applications by grouping related controllers, views, and models. Routing can incorporate areas to keep URL structures clean.
app.MapAreaControllerRoute(
name: "admin_area",
areaName: "Admin",
pattern: "Admin/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
With attribute routing:
[Area("Admin")]
[Route("admin/[controller]")]
public class ProductsController : Controller
{
[HttpGet]
public IActionResult Index() => View();
[HttpGet("{id}")]
public IActionResult Edit(int id) => View();
}
Practical Example: Building a Blog Application
Let's see how routing might work in a simple blog application:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure middleware
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
// Configure routes
app.MapControllerRoute(
name: "blog_post",
pattern: "blog/{year:int}/{month:int}/{day:int}/{slug}",
defaults: new { controller = "Blog", action = "Post" });
app.MapControllerRoute(
name: "blog_archive",
pattern: "blog/{year:int}/{month:int?}",
defaults: new { controller = "Blog", action = "Archive" });
app.MapControllerRoute(
name: "blog_category",
pattern: "category/{categoryName}",
defaults: new { controller = "Blog", action = "Category" });
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
And the corresponding controller:
public class BlogController : Controller
{
// Handles /blog/2023/10/15/my-first-post
public IActionResult Post(int year, int month, int day, string slug)
{
var post = _blogService.GetPost(year, month, day, slug);
return View(post);
}
// Handles /blog/2023/10 or /blog/2023
public IActionResult Archive(int year, int? month)
{
var posts = month.HasValue
? _blogService.GetPostsByMonth(year, month.Value)
: _blogService.GetPostsByYear(year);
return View(posts);
}
// Handles /category/technology
public IActionResult Category(string categoryName)
{
var posts = _blogService.GetPostsByCategory(categoryName);
return View(posts);
}
}
Endpoint Routing in ASP.NET Core
In newer versions of ASP.NET Core, endpoint routing provides a unified routing system across all ASP.NET Core frameworks (MVC, Razor Pages, Web API, SignalR, etc.).
The basic setup looks like this:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Use routing middleware
app.UseRouting();
// Define endpoints
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
endpoints.MapGet("/hello", () => "Hello World!");
endpoints.MapGet("/users/{id}", (int id) => $"User {id}");
});
app.Run();
The minimal API approach is even more concise:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapGet("/users", () => new[] { "Alice", "Bob", "Charlie" });
app.MapGet("/users/{id}", (int id) => $"User {id}");
app.MapPost("/users", (User user) => Results.Created($"/users/{user.Id}", user));
app.Run();
Best Practices for Routing
-
Keep URLs clean and meaningful
- Use descriptive names that reflect the resource
- Prefer
/products/5
over/get-product?id=5
-
Be consistent with URL patterns
- Use plural nouns for collections (
/products
, not/product
) - Follow REST conventions for APIs
- Maintain a consistent depth and structure
- Use plural nouns for collections (
-
Use attribute routing for APIs
- Makes the relationship between URLs and actions explicit
- Easier to document and maintain
-
Use convention-based routing for MVC applications
- Provides a consistent pattern across the application
- Requires less code duplication
-
Avoid hardcoding URLs
- Use URL helpers or named routes
- Makes refactoring easier
-
Implement proper constraints
- Prevents invalid routes from matching
- Provides better error messages
-
Order routes from most specific to least specific
- More specific routes should come first to avoid being masked
Summary
Routing is an essential component of .NET web applications that determines how URL patterns map to the code that handles requests. We've covered:
- The fundamentals of routing in ASP.NET Core
- Convention-based routing for traditional MVC applications
- Attribute routing for more explicit control
- Route parameters and constraints to make routes more flexible
- Advanced techniques like areas, token replacement, and route ordering
- Practical examples of routing in action
- Best practices for designing effective routes
Understanding routing thoroughly will help you create more maintainable, user-friendly, and SEO-optimized web applications.
Additional Resources
- Official ASP.NET Core Routing Documentation
- Routing in ASP.NET MVC
- RESTful API Design Best Practices
Exercises
- Create a simple blog application with routes for viewing posts by date, category, and author.
- Implement an API with proper REST routes for a product catalog.
- Configure area routing for an admin section of a website.
- Create a custom route constraint that validates a product SKU format.
- Compare the performance of different routing approaches using a benchmarking tool.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)