Controller là gì?

Controller được dùng để định nghĩa và nhóm một tập hợp các actions. Một action (hay action method) là method trên controller dùng để xử lý requests. Controllers nhóm các actions tương tự lại với nhau, cho phép áp dụng các quy tắc chung như routing, cachingauthorization cho toàn bộ nhóm. Requests được map đến actions thông qua routing. Controllers được khởi tạo và hủy trên mỗi request.

Quy ước đặt tên Controller

Theo quy ước, controller classes:
  • Nằm trong thư mục Controllers ở root của project
  • Kế thừa từ Microsoft.AspNetCore.Mvc.Controller
Một class được coi là controller khi thỏa ít nhất một trong các điều kiện:
Điều kiệnVí dụ
Tên class có suffix "Controller"ProductsController
Kế thừa class có suffix "Controller"Kế thừa BaseController
Class được decorate với [Controller]@[Controller]
// ✅ Hợp lệ — tên có suffix Controller
public class ProductsController : Controller { }

// ✅ Hợp lệ — có attribute [Controller]
[Controller]
public class Products
{
    public IActionResult Index() => View();
}

// ✅ Hợp lệ — kế thừa class có suffix Controller
public class ProductsController : BaseController { }

// ❌ Không hợp lệ — có [NonController]
[NonController]
public class ProductsController : Controller { }

Controller và mô hình MVC

Trong mô hình MVC, controller chịu trách nhiệm:
  1. Xử lý request ban đầu — đảm bảo request data hợp lệ
  2. Instantiate model — tạo đối tượng model
  3. Chọn kết quả trả về — View hoặc API response
Lưu ý: Controller là một abstraction ở mức UI. Trong ứng dụng được thiết kế tốt, controller không trực tiếp thực hiện data access hay business logic. Thay vào đó, controller delegates đến các services xử lý các trách nhiệm này.

Dependency Injection trong Controller

Controllers nên tuân theo Explicit Dependencies Principle.
Loại injectionKhi nào dùng
Constructor InjectionNhiều action methods cần cùng service
Action InjectionChỉ một action method cần service đó

Constructor Injection

public class ProductsController : Controller
{
    private readonly IProductService _productService;
    private readonly ICategoryService _categoryService;

    public ProductsController(
        IProductService productService,
        ICategoryService categoryService)
    {
        _productService = productService;
        _categoryService = categoryService;
    }
}

Action Injection

public class ProductsController : Controller
{
    [FromServices]
    public IProductService ProductService { get; set; }

    public IActionResult Details(int id)
    {
        var product = ProductService.GetById(id);
        return View(product);
    }
}

Action Methods

Định nghĩa Action

Tất cả public methods trên controller đều là actions, trừ method có attribute [NonAction].
public class ProductsController : Controller
{
    // ✅ Đây là action
    public IActionResult Index() => View();

    // ✅ Đây là action (async)
    public async Task<IActionResult> Details(int id)
    {
        var product = await _productService.GetByIdAsync(id);
        return View(product);
    }

    // ❌ Đây không phải action
    [NonAction]
    public string GetProductCode(int id)
    {
        return $"PRD-{id}";
    }
}

Action Parameters và Model Binding

Parameters trên actions được bind từ request data và validated bằng model binding. Model validation xảy ra cho tất cả model-bound parameters. Property ModelState.IsValid cho biết binding và validation có thành công không.
public async Task<IActionResult> Create(Product product)
{
    if (!ModelState.IsValid)  // ← Validation check
    {
        return View(product);
    }

    await _productService.CreateAsync(product);
    return RedirectToAction(nameof(Index));
}

Action Results

Actions có thể trả về bất kỳ type nào, nhưng thường trả về IActionResult (hoặc Task<IActionResult> cho async methods).

Controller Helper Methods

Controllers kế thừa từ Controller có quyền truy cập 3 nhóm helper methods:

1. Trả về Response Body rỗng

Không có Content-Type HTTP header vì response body không có nội dung.

HTTP Status Code

public IActionResult GetProduct(int id)
{
    var product = _productService.GetById(id);

    if (product == null)
        return NotFound();        // → 404

    return Ok(product);           // → 200
}
MethodHTTP StatusMô tả
BadRequest()400Bad request
NotFound()404Không tìm thấy
Ok()200Thành công
NoContent()204Không có nội dung
StatusCode(500)500Internal server error

Redirect

public IActionResult Create(Product product)
{
    if (!ModelState.IsValid)
        return View(product);

    _productService.Create(product);
    return RedirectToAction("Index");  // → 302 + Location header

    // Hoặc
    return RedirectToAction("Details", new { id = product.Id });
}
MethodMô tả
Redirect(url)Redirect đến URL
LocalRedirect(url)Redirect đến local URL, ngăn open redirect attack
RedirectToAction(action)Redirect đến action
RedirectToRoute(route)Redirect đến route cụ thể

2. Trả về Response Body có predefined Content-Type

View

public IActionResult Index()
{
    var products = _productService.GetAll();
    return View(products);  // → Render .cshtml file
}

Formatted Response

public IActionResult GetJson()
{
    var product = _productService.GetById(1);
    return Json(product);  // → Content-Type: application/json
}
MethodContent-TypeMô tả
Json(object)application/jsonSerialize object sang JSON
File(path, type)Tùy chỉnhTrả về file
PhysicalFile(path, type)Tùy chỉnhTrả về file từ filesystem
Content(text, type)Tùy chỉnhTrả về string với content type
// Trả về XML file
return File(fileBytes, "application/xml", "products.xml");

// Trả về file download
return PhysicalFile(path, "application/octet-stream", "report.pdf");

3. Content Negotiation

Content Negotiation xảy ra khi action trả về ObjectResult hoặc non-IActionResult. Các method này tự động chọn format (JSON/XML) dựa trên Accept header của client.
public IActionResult GetProduct(int id)
{
    var product = _productService.GetById(id);
    if (product == null)
        return BadRequest(ModelState);         // Content negotiation ✅
        // return BadRequest();                // Không có content negotiation

    return Ok(product);                       // Content negotiation ✅
    // return Ok();                           // Không có content negotiation
}

// Luôn luôn content negotiation
return CreatedAtRoute("GetProduct", new { id = product.Id }, product);

Cross-Cutting Concerns

Cross-cutting concerns là các phần workflow được chia sẻ giữa nhiều parts của ứng dụng, như authentication, logging, error handling.

Ví dụ: Cross-cutting concerns thường gặp

[Authorize]                    // 🛡️ Chỉ user đã auth mới truy cập
[ResponseCache(Duration = 30)] // 💾 Cache response 30 giây
public class CartController : Controller
{
    [AllowAnonymous]           // ⚠️ Override [Authorize] cho action này
    public IActionResult Index()
    {
        return View();
    }
}
Cross-cutting concernGiải pháp
Authentication[Authorize], [AllowAnonymous]
AuthorizationPolicy-based authorization
Error Handling[ExceptionHandler] filter, middleware
Response Caching[ResponseCache] filter
LoggingAction filter hoặc middleware
LocalizationAction filter

Tài liệu tham khảo