---
title: Routing
description: Hướng dẫn chi tiết về Routing trong ASP.NET Core MVC — Conventional routing, Attribute routing, HTTP verb templates và cách cấu hình.
---

## Routing là gì?

ASP.NET Core controllers sử dụng **Routing middleware** để match URLs của incoming requests và map chúng đến các actions. Route templates:

- Được định nghĩa tại startup trong `Program.cs` hoặc trong attributes
- Mô tả cách URL paths được match đến actions
- Được dùng để generate URLs cho links trong responses

Actions có thể là **conventionally-routed** hoặc **attribute-routed**. Đặt route trên controller hoặc action sẽ biến nó thành attribute-routed.

---

## Thiết lập Conventional Route

### Cấu hình mặc định

ASP.NET Core MVC template tạo conventional routing code như sau:

```csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

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();
```

### Giải thích Route Template

```text
{controller=Home}/{action=Index}/{id?}
│            │           │
│            │           └── id là optional (?)
│            └── action mặc định: Index
└── controller mặc định: Home
```

| URL Path              | Controller | Action  | id                           |
| --------------------- | ---------- | ------- | ---------------------------- |
| `/Products/Details/5` | Products   | Details | 5                            |
| `/Products/Details`   | Products   | Details | `null` (model binding set 0) |
| `/Home`               | Home       | Index   | `null`                       |
| `/`                   | Home       | Index   | `null`                       |

> **Lưu ý:** Route template định nghĩa `id` là optional. Khi `id` bị omit khỏi URL, model binding set `id` thành `0`, và thường không tìm thấy entity nào trong database matching `id == 0`.

---

## Conventional Routing

### Nguyên tắc cơ bản

```mermaid
flowchart LR
    A["URL Path<br/>/Products/Details/5"] --> B["{controller=Home}/{action=Index}/{id?}"]
    B --> C["{ controller = Products<br/>action = Details<br/>id = 5 }"]
    C --> D["ProductsController.Details(int id)"]
```

Conventional routing thiết lập **quy ước** cho URL paths:

| Segment             | Maps to                                           |
| ------------------- | ------------------------------------------------- |
| `{controller=Home}` | Tên controller (suffix "Controller" được loại bỏ) |
| `{action=Index}`    | Tên action method                                 |
| `{id?}`             | Optional id — maps to model entity                |

### Ví dụ Controller

```csharp
public class ProductsController : Controller
{
    public IActionResult Details(int id)
    {
        // id = 5 khi URL là /Products/Details/5
        return View();
    }
}
```

### Lợi ích của Conventional Routing

- **URL nhất quán** cho CRUD operations
- Không cần tạo URL pattern mới cho mỗi action
- UI dễ dự đoán

> **Khuyến nghị:** Hầu hết apps nên chọn một routing scheme cơ bản và descriptive để URLs dễ đọc và có ý nghĩa.

---

## Nhiều Conventional Routes

Có thể cấu hình nhiều conventional routes bằng cách thêm nhiều `MapControllerRoute` và `MapAreaControllerRoute`:

```csharp
app.MapControllerRoute(
    name: "blog",
    pattern: "blog/{*article}",
    defaults: new { controller = "Blog", action = "Article" });

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
```

```mermaid
flowchart TD
    A["URL: /blog/aspnet-core"] --> B["Route: blog"]
    A2["URL: /Products"] --> C["Route: default"]
    B --> D["BlogController.Article"]
    C --> E["ProductsController.Index"]
```

| Route     | Pattern                                  | Khi nào match                         |
| --------- | ---------------------------------------- | ------------------------------------- |
| `blog`    | `blog/{*article}`                        | `/blog`, `/blog/abc`, `/blog/abc/def` |
| `default` | `{controller=Home}/{action=Index}/{id?}` | Mọi URL còn lại                       |

### Thứ tự ưu tiên

```mermaid
flowchart LR
    A["1️⃣ Route thêm trước<br/>→ Ưu tiên cao hơn"] --> B["2️⃣ Route thêm sau<br/>→ Ưu tiên thấp hơn"]
```

- Routes thêm **trước** có ưu tiên **cao hơn**
- Routes **cụ thể** nên đặt **trước**
- Routes **greedy** (catch-all `{*article}`) nên đặt **sau**

---

## Attribute Routing

### Giới thiệu

REST APIs nên dùng **attribute routing** để model app's functionality như một tập resources, với operations được biểu diễn bằng HTTP verbs.

```csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();  // Map attribute-routed controllers

app.Run();
```

### Cấu hình Controller với Attribute Routing

```csharp
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
    [HttpGet]                        // GET /api/products
    public IActionResult ListProducts() => Ok(_products);

    [HttpGet("{id}")]                // GET /api/products/5
    public IActionResult GetProduct(int id)
    {
        var product = _products.FirstOrDefault(p => p.Id == id);
        return product is null ? NotFound() : Ok(product);
    }

    [HttpPost]                       // POST /api/products
    public IActionResult CreateProduct([FromBody] Product product)
    {
        _products.Add(product);
        return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
    }

    [HttpPut("{id}")]                // PUT /api/products/5
    public IActionResult UpdateProduct(int id, [FromBody] Product product) { /* ... */ }

    [HttpDelete("{id}")]             // DELETE /api/products/5
    public IActionResult DeleteProduct(int id) { /* ... */ }
}
```

### HTTP Verb Templates

ASP.NET Core cung cấp các HTTP verb templates:

| Attribute      | HTTP Method | Ví dụ URL                |
| -------------- | ----------- | ------------------------ |
| `[HttpGet]`    | GET         | `GET /api/products`      |
| `[HttpPost]`   | POST        | `POST /api/products`     |
| `[HttpPut]`    | PUT         | `PUT /api/products/5`    |
| `[HttpDelete]` | DELETE      | `DELETE /api/products/5` |
| `[HttpPatch]`  | PATCH       | `PATCH /api/products/5`  |
| `[HttpHead]`   | HEAD        | `HEAD /api/products`     |

### Kết hợp Routes

```csharp
[Route("products")]
public class ProductsApiController : ControllerBase
{
    [HttpGet]           // GET /products
    public IActionResult ListProducts() => Ok(_products);

    [HttpGet("{id}")]   // GET /products/5
    public IActionResult GetProduct(int id) { /* ... */ }
}
```

| Route trên Controller | Route trên Action   | URL cuối cùng    |
| --------------------- | ------------------- | ---------------- |
| `[Route("products")]` | `[HttpGet]`         | `/products`      |
| `[Route("products")]` | `[HttpGet("{id}")]` | `/products/{id}` |

### Token Replacement

Dùng `[controller]` và `[action]` để tự động thay thế tên:

```csharp
[Route("[controller]/[action]")]
public class HomeController : Controller
{
    [Route("~/")]        // Override, không kết hợp với controller route
    [Route("/Home")]
    public IActionResult Index() => View();

    public IActionResult About() => View();
    // → /Home/About
}
```

### Route với Route Constraints

```csharp
[HttpGet("{id}")]            // GET /products/abc → Match
public IActionResult GetProduct(string id) { /* id = "abc" */ }

[HttpGet("int/{id:int}")]     // GET /products/int/abc → 404 Not Found
                               // GET /products/int/5   → Match
public IActionResult GetIntProduct(int id) { /* id = 5 */ }
```

| Constraint         | Mô tả            | Ví dụ                 |
| ------------------ | ---------------- | --------------------- |
| `:int`             | Chỉ integer      | `{id:int}`            |
| `:long`            | Chỉ long         | `{id:long}`           |
| `:float`           | Chỉ float        | `{id:float}`          |
| `:bool`            | Chỉ boolean      | `{id:bool}`           |
| `:alpha`           | Chỉ alphabet     | `{name:alpha}`        |
| `:length(min,max)` | Giới hạn độ dài  | `{name:length(3,20)}` |
| `:range(min,max)`  | Giới hạn giá trị | `{id:range(1,100)}`   |

---

## Giải quyết Ambiguous Actions

Khi hai endpoints match qua routing:

```csharp
public class ProductsController : Controller
{
    public IActionResult Edit(int id)
    {
        // Hiển thị form edit — GET /Products/Edit/5
        return View(GetProduct(id));
    }

    [HttpPost]
    public IActionResult Edit(int id, Product product)
    {
        // Xử lý form submit — POST /Products/Edit/5
        if (!ModelState.IsValid) return View(product);
        UpdateProduct(product);
        return RedirectToAction(nameof(Index));
    }
}
```

```mermaid
flowchart TD
    A["URL: /Products/Edit/5"] --> B["2 actions match?"]
    B --> C{"HTTP Method?"}
    C -->|GET| D["Edit(int id) ✓"]
    C -->|POST| E["Edit(int id, Product) ✓"]
    E --> F["Update Product"]
    D --> G["Show Edit Form"]
```

| HTTP Method | Action được chọn        | Lý do                 |
| ----------- | ----------------------- | --------------------- |
| GET         | `Edit(int id)`          | Không có `[HttpPost]` |
| POST        | `Edit(int id, Product)` | `[HttpPost]` match    |

> **Lưu ý:** Nếu routing không thể chọn candidate tốt nhất, nó sẽ throw `AmbiguousMatchException`.

---

## Route Names

Route names cung cấp tên logic cho route, dùng cho **URL generation**:

```csharp
[HttpGet("/products/{id}", Name = "Products_Details")]
public IActionResult GetProduct(int id) { /* ... */ }
```

```csharp
// URL Generation
var url = Url.Link("Products_Details", new { id = 5 });
// → "/products/5"
```

| Route Name         | Dùng cho                  |
| ------------------ | ------------------------- |
| **URL Matching**   | ❌ Không ảnh hưởng        |
| **URL Generation** | ✅ Dùng để generate links |

---

## Reserved Route Parameter Names

### Khi dùng Controllers hoặc Razor Pages

| Từ khóa      | Ghi chú  |
| ------------ | -------- |
| `action`     | Reserved |
| `area`       | Reserved |
| `controller` | Reserved |
| `handler`    | Reserved |
| `page`       | Reserved |

```csharp
// ❌ Lỗi phổ biến — dùng "page" làm route parameter
[Route("/articles/{page}")]
public IActionResult ListArticles(int page) { /* ... */ }
```

### Trong Razor View/Page Context

`page`, `using`, `namespace`, `inject`, `section`, `inherits`, `model`, `addTagHelper`, `removeTagHelper` là reserved.

---

## So sánh Conventional vs Attribute Routing

| Tiêu chí                 | Conventional Routing    | Attribute Routing                |
| ------------------------ | ----------------------- | -------------------------------- |
| **Cấu hình**             | `Program.cs`            | `[Route]` trên controller/action |
| **URL dựa trên**         | Controller/Action names | Tùy chỉnh hoàn toàn              |
| **REST APIs**            | ❌ Ít phù hợp           | ✅ Phù hợp nhất                  |
| **Web UI apps**          | ✅ Phù hợp              | Có thể dùng                      |
| **Fine-grained control** | ❌ Hạn chế              | ✅ Kiểm soát chính xác           |
| **DRY**                  | ✅ DRY                  | ⚠️ Có thể lặp lại                |

> **Khuyến nghị:** REST APIs → **Attribute Routing**. Web UI apps với CRUD → **Conventional Routing** là điểm khởi đầu tốt.

---

## Tài liệu tham khảo

- [Routing in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing)
- [Route Template Reference](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing#route-template-reference)
- [ApiController Attribute](https://learn.microsoft.com/en-us/aspnet/core/web-api/)
