DTO
Nội dung bài · 5 mục
- 1.Khái niệm
- 2.Ví dụ
- 3.Thử ngay
- 4.Lỗi hay gặp
- 5.Tóm tắt
Class Product trong hệ thống có cả giá nhập hàng, là thông tin nội bộ. Nếu
action trả thẳng Product ra ngoài thì khách hàng thấy luôn giá nhập. Nếu
action nhận thẳng Product từ body thì client tự đặt được Id. DTO tách phần
dữ liệu đi qua API ra khỏi class dùng bên trong.
Khái niệm
📦 DTO (Data Transfer Object): class chỉ chứa dữ liệu đi vào hoặc đi ra qua API, tách riêng khỏi class dùng bên trong hệ thống.
Thường mỗi tài nguyên có hai loại DTO:
| DTO | Hướng | Chứa gì |
|---|---|---|
ProductResponse |
server → client | những gì client được xem |
CreateProductRequest |
client → server | những gì client được phép gửi |
Ví dụ
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
private static readonly List<Product> Products =
new List<Product>();
[HttpPost]
public ActionResult<ProductResponse> Create(
CreateProductRequest request)
{
var product = new Product
{
Id = Products.Count + 1,
Name = request.Name,
Price = request.Price,
CostPrice = request.Price * 70 / 100,
};
Products.Add(product);
return Created(
$"/api/products/{product.Id}",
ToResponse(product));
}
private static ProductResponse ToResponse(
Product p) =>
new ProductResponse
{
Id = p.Id, Name = p.Name, Price = p.Price
};
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }
public decimal CostPrice { get; set; }
}
public class CreateProductRequest
{
public string Name { get; set; } = "";
public decimal Price { get; set; }
}
public class ProductResponse
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }
}Productlà class bên trong, cóCostPricelà giá nhập.CreateProductRequestkhông cóIdhayCostPrice, nên client không gửi được hai giá trị đó.ProductResponsekhông cóCostPrice, nên giá nhập không bao giờ lộ ra.ToResponsechép dữ liệu từProductsang DTO. Viết tay như vậy là đủ cho hầu hết dự án.Created(url, data)cũng trả 201 nhưCreatedAtAction, dùng khi chưa có action đọc riêng để trỏ tới.
Thử ngay
Chạy server. Tạo file product.json:
{ "name": "Pen", "price": 5000, "costPrice": 1, "id": 999 }Rồi gửi:
curl -i -X POST http://localhost:5000/api/products -H "Content-Type: application/json" -d @product.jsonĐoán trước khi chạy: client cố gửi id là 999 và costPrice là 1.
Response trả về id bằng bao nhiêu, và có costPrice không?
Xem kết quả
HTTP/1.1 201 Created
{"id":1,"name":"Pen","price":5000}id là 1, do server tự cấp. CreateProductRequest không có Id và
CostPrice, nên hai giá trị client gửi bị bỏ qua. Response cũng không có
costPrice.
Lỗi hay gặp
Trả thẳng class bên trong ra ngoài. Mọi property đều lộ ra, kể cả giá nhập.
// SAI — response có luôn CostPrice
using Microsoft.AspNetCore.Mvc;
public class ItemsController : ControllerBase
{
[HttpGet("{id}")]
public Product GetById(int id) =>
new Product { Id = id, CostPrice = 3500m };
}// ĐÚNG — trả qua DTO, không có CostPrice
using Microsoft.AspNetCore.Mvc;
public class ItemsController : ControllerBase
{
[HttpGet("{id}")]
public ProductResponse GetById(int id) =>
new ProductResponse { Id = id, Name = "Pen" };
}Nhận thẳng class bên trong từ body. Client gửi thêm id hay costPrice
là gán được luôn. Luôn nhận qua DTO chỉ chứa những gì client được phép gửi,
như CreateProductRequest ở trên.
Tóm tắt
- DTO là class chỉ chứa dữ liệu đi qua API, tách khỏi class bên trong.
- DTO response quyết định client được xem gì.
- DTO request quyết định client được gửi gì, chặn việc tự đặt
Idhay giá nhập. - Chép dữ liệu giữa class bên trong và DTO bằng một method nhỏ.
Tự kiểm tra
0/3 câuClass User có PasswordHash. Action GET /api/users/1 nên trả về kiểu gì?
CreateOrderRequest không có property Status. Client gửi JSON kèm "status": "Paid". Chuyện gì xảy ra?
Vì sao DTO request để tạo sản phẩm không nên có Id?