Giai Đoạn 1: Bắt Đầu với ASP.NET Core & Web API

Series: Lộ Trình Học ASP.NET Core
Thời gian cam kết: 5–7 ngày, 1–2 giờ mỗi ngày
Yêu cầu trước: Nền tảng C#, OOP cơ bản, khái niệm REST API
Giới Thiệu
Trong giai đoạn này, bạn sẽ xây dựng REST API thực sự đầu tiên với ASP.NET Core — một API CRUD để quản lý danh sách sản phẩm. Đến cuối giai đoạn, bạn sẽ hiểu cách framework hoạt động cùng nhau và sẵn sàng kết nối cơ sở dữ liệu thực trong Giai đoạn 2.
Những gì bạn sẽ xây dựng: API Sản phẩm với CRUD đầy đủ (Create, Read, Update, Delete), validation đầu vào, tài liệu Swagger và xử lý HTTP response đúng cách.
Bạn Sẽ Học Được Gì
✅ Cài đặt .NET SDK và thiết lập môi trường phát triển
✅ Hiểu cấu trúc dự án ASP.NET Core
✅ Tạo controller, action và route
✅ Bind request body, query string và route parameter
✅ Validate đầu vào với Data Annotations và trả về lỗi
✅ Trả về HTTP status code đúng cho mọi tình huống
✅ Tài liệu hóa API với Swagger / OpenAPI
✅ Quản lý cấu hình với appsettings.json và biến môi trường
Bước 1: Cài Đặt .NET SDK
ASP.NET Core yêu cầu .NET SDK (phiên bản 8 hoặc 9).
macOS
# Tùy chọn 1: Homebrew (khuyến nghị)
brew install dotnet
# Tùy chọn 2: Tải bộ cài đặt từ https://dot.netWindows
# Tùy chọn 1: winget
winget install Microsoft.DotNet.SDK.9
# Tùy chọn 2: Tải bộ cài đặt từ https://dot.netLinux (Ubuntu/Debian)
sudo apt-get update
sudo apt-get install -y dotnet-sdk-9.0Xác Nhận Cài Đặt
dotnet --version
# 9.0.x (hoặc 8.0.x)
dotnet --list-sdks
# Liệt kê tất cả SDK đã cài đặtBước 2: Chọn IDE
| IDE | Nền tảng | Chi phí | Phù hợp nhất cho |
|---|---|---|---|
| Visual Studio 2022 | Windows / macOS | Miễn phí (Community) | Phát triển .NET đầy đủ |
| VS Code + C# Dev Kit | Tất cả nền tảng | Miễn phí | Phát triển nhẹ |
| JetBrains Rider | Tất cả nền tảng | Trả phí | Người dùng đa nền tảng nâng cao |
Khuyến nghị cho người mới bắt đầu: Visual Studio 2022 trên Windows (tích hợp nhất), hoặc VS Code với extension C# Dev Kit trên macOS/Linux.
Thiết Lập VS Code
Nếu dùng VS Code, cài đặt extension này:
C# Dev KitC# Dev Kit bao gồm IntelliSense, debugging, test explorer và solution explorer.
Bước 3: Tạo Dự Án Đầu Tiên
# Tạo dự án Web API mới
dotnet new webapi -n ProductsApi --use-controllers
cd ProductsApi
# Chạy dự án
dotnet runMở trình duyệt tại http://localhost:5000/swagger — bạn sẽ thấy Swagger UI với endpoint WeatherForecast mẫu. Đây là điểm khởi đầu của bạn.
Hiểu Cấu Trúc Dự Án
ProductsApi/
├── Controllers/ # API controller (xử lý request)
│ └── WeatherForecastController.cs
├── Properties/
│ └── launchSettings.json # URL và profile môi trường dev
├── appsettings.json # Cấu hình ứng dụng
├── appsettings.Development.json # Ghi đè cấu hình chỉ cho dev
├── ProductsApi.csproj # File dự án (dependency, phiên bản SDK)
└── Program.cs # Điểm khởi đầu và cấu hình ứng dụngFile Program.cs
Đây là nơi ứng dụng của bạn bắt đầu. Mở nó — nó chứa hai phần:
// 1. ĐĂNG KÝ SERVICE — cấu hình những gì ứng dụng có thể làm
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(); // Bật MVC controller
builder.Services.AddEndpointsApiExplorer(); // Bật khám phá endpoint
builder.Services.AddSwaggerGen(); // Bật Swagger UI
// 2. MIDDLEWARE PIPELINE — cấu hình cách request chạy
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers(); // Kết nối route controller
app.Run();Hãy nghĩ theo hai giai đoạn:
- Giai đoạn Builder: đăng ký service (DI container)
- Giai đoạn Pipeline: cấu hình middleware (thứ tự xử lý request)
Bước 4: Controller Đầu Tiên
Xóa WeatherForecastController.cs và WeatherForecast.cs. Tạo controller mới:
// Controllers/ProductsController.cs
using Microsoft.AspNetCore.Mvc;
namespace ProductsApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
// Lưu trữ trong bộ nhớ tạm thời (Giai đoạn 2 sẽ thêm cơ sở dữ liệu thực)
private static readonly List<Product> _products = new()
{
new Product { Id = 1, Name = "Laptop", Price = 999.99m, Stock = 10 },
new Product { Id = 2, Name = "Mouse", Price = 29.99m, Stock = 50 },
new Product { Id = 3, Name = "Keyboard", Price = 79.99m, Stock = 30 },
};
private static int _nextId = 4;
// GET api/products
[HttpGet]
public ActionResult<IEnumerable<Product>> GetAll()
{
return Ok(_products);
}
// GET api/products/1
[HttpGet("{id}")]
public ActionResult<Product> GetById(int id)
{
var product = _products.FirstOrDefault(p => p.Id == id);
if (product is null)
return NotFound();
return Ok(product);
}
// POST api/products
[HttpPost]
public ActionResult<Product> Create([FromBody] CreateProductRequest request)
{
var product = new Product
{
Id = _nextId++,
Name = request.Name,
Price = request.Price,
Stock = request.Stock
};
_products.Add(product);
return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}
// PUT api/products/1
[HttpPut("{id}")]
public ActionResult<Product> Update(int id, [FromBody] UpdateProductRequest request)
{
var product = _products.FirstOrDefault(p => p.Id == id);
if (product is null)
return NotFound();
product.Name = request.Name;
product.Price = request.Price;
product.Stock = request.Stock;
return Ok(product);
}
// DELETE api/products/1
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
var product = _products.FirstOrDefault(p => p.Id == id);
if (product is null)
return NotFound();
_products.Remove(product);
return NoContent();
}
}Các Class Model
Tạo thư mục Models với các class sau:
// Models/Product.cs
namespace ProductsApi.Models;
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Stock { get; set; }
}// Models/CreateProductRequest.cs
namespace ProductsApi.Models;
public class CreateProductRequest
{
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Stock { get; set; }
}// Models/UpdateProductRequest.cs
namespace ProductsApi.Models;
public class UpdateProductRequest
{
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Stock { get; set; }
}Lưu ý: Bạn cần thêm
using ProductsApi.Models;ở đầu controller, hoặc cấu hình namespace toàn cục trong file dự án.
Chạy lại dự án (dotnet run) và kiểm tra các endpoint trong Swagger.
Bước 5: Hiểu Routing
ASP.NET Core có hai cơ chế routing:
Attribute Routing (dùng trong Web API)
[Route("api/[controller]")] // [controller] chuyển thành "products"
public class ProductsController : ControllerBase
{
[HttpGet] // GET /api/products
[HttpGet("{id}")] // GET /api/products/1
[HttpPost] // POST /api/products
[HttpPut("{id}")] // PUT /api/products/1
[HttpDelete("{id}")] // DELETE /api/products/1
}Route Parameter
// Route parameter từ URL: /api/products/42
[HttpGet("{id}")]
public ActionResult<Product> GetById(int id) { ... }
// Query string: /api/products?page=2&pageSize=10
[HttpGet]
public ActionResult<IEnumerable<Product>> GetAll(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10) { ... }
// Request body: POST /api/products với JSON body
[HttpPost]
public ActionResult<Product> Create([FromBody] CreateProductRequest request) { ... }
// Route + body: PUT /api/products/42 với JSON body
[HttpPut("{id}")]
public ActionResult<Product> Update(int id, [FromBody] UpdateProductRequest request) { ... }Các thuộc tính binding source ([FromRoute], [FromQuery], [FromBody], [FromHeader]) cho ASP.NET Core biết lấy mỗi tham số từ đâu. Khi dùng [ApiController], hầu hết các binding được suy luận tự động.
Bước 6: Validation Đầu Vào
Data Annotations
Thêm validation trực tiếp vào model request:
using System.ComponentModel.DataAnnotations;
public class CreateProductRequest
{
[Required]
[StringLength(100, MinimumLength = 2)]
public string Name { get; set; } = string.Empty;
[Required]
[Range(0.01, 999999.99)]
public decimal Price { get; set; }
[Required]
[Range(0, int.MaxValue)]
public int Stock { get; set; }
}Với [ApiController] trên controller, ASP.NET Core tự động validate request đến và trả về 400 Bad Request với chi tiết lỗi nếu validation thất bại. Bạn không cần kiểm tra ModelState.IsValid thủ công.
Validation Tùy Chỉnh
Đối với các quy tắc phức tạp hơn, implement IValidatableObject:
public class CreateProductRequest : IValidatableObject
{
[Required]
[StringLength(100, MinimumLength = 2)]
public string Name { get; set; } = string.Empty;
[Range(0.01, 999999.99)]
public decimal Price { get; set; }
[Range(0, int.MaxValue)]
public int Stock { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Price > 1000 && Stock > 100)
{
yield return new ValidationResult(
"Sản phẩm giá cao không được có hơn 100 đơn vị trong kho.",
new[] { nameof(Price), nameof(Stock) });
}
}
}Định Dạng Response Lỗi Validation
Khi validation thất bại, ASP.NET Core trả về mặc định:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Name": ["The Name field is required."],
"Price": ["The field Price must be between 0.01 and 999999.99."]
}
}Bước 7: HTTP Status Code — Response Đúng Cho Mọi Tình Huống
Trả về status code đúng là cơ bản cho API được thiết kế tốt:
// 200 OK — GET, PUT thành công
return Ok(product);
// 201 Created — POST thành công (với Location header)
return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
// 204 No Content — DELETE thành công (không có body)
return NoContent();
// 400 Bad Request — đầu vào không hợp lệ (tự động với [ApiController])
return BadRequest(new { error = "Thông báo lỗi tùy chỉnh" });
// 404 Not Found — resource không tồn tại
return NotFound();
// 409 Conflict — resource đã tồn tại
return Conflict(new { error = "Sản phẩm với tên này đã tồn tại." });Bảng Tham Khảo Status Code
| Tình huống | Status Code | Method |
|---|---|---|
| GET thành công | 200 OK | Ok(data) |
| POST thành công | 201 Created | CreatedAtAction(...) |
| PUT thành công | 200 OK | Ok(data) |
| DELETE thành công | 204 No Content | NoContent() |
| Validation thất bại | 400 Bad Request | Tự động |
| Chưa xác thực | 401 Unauthorized | Unauthorized() |
| Bị cấm | 403 Forbidden | Forbid() |
| Không tìm thấy | 404 Not Found | NotFound() |
| Xung đột | 409 Conflict | Conflict(data) |
| Lỗi server | 500 Internal Server Error | (exception middleware) |
Bước 8: Tài Liệu Swagger / OpenAPI
Swagger UI được bao gồm theo mặc định trong các dự án mới. Nó tự động tạo tài liệu tương tác từ code của bạn.
Nâng Cao Swagger với XML Comment
Thêm tài liệu XML vào các action:
/// <summary>
/// Lấy tất cả sản phẩm với lọc tùy chọn.
/// </summary>
/// <param name="page">Số trang (mặc định: 1)</param>
/// <param name="pageSize">Số mục mỗi trang (mặc định: 10)</param>
/// <returns>Danh sách sản phẩm</returns>
/// <response code="200">Trả về danh sách sản phẩm</response>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<Product>), StatusCodes.Status200OK)]
public ActionResult<IEnumerable<Product>> GetAll(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10)
{
var paged = _products.Skip((page - 1) * pageSize).Take(pageSize);
return Ok(paged);
}Bật XML comment trong .csproj:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>Và cấu hình Swagger sử dụng chúng trong Program.cs:
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Products API",
Version = "v1",
Description = "API mẫu để quản lý sản phẩm"
});
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
});Thêm using System.Reflection; và using Microsoft.OpenApi.Models; ở đầu file.
Bước 9: Quản Lý Cấu Hình
appsettings.json
Dùng cho các cài đặt ứng dụng thay đổi theo môi trường:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Api": {
"MaxPageSize": 100,
"DefaultPageSize": 10
}
}Đọc Cấu Hình Trong Code
// Tùy chọn 1: Trực tiếp (giá trị đơn giản)
var maxPageSize = builder.Configuration.GetValue<int>("Api:MaxPageSize");
// Tùy chọn 2: Options pattern (khuyến nghị cho cài đặt theo nhóm)
builder.Services.Configure<ApiSettings>(
builder.Configuration.GetSection("Api"));// Models/ApiSettings.cs
public class ApiSettings
{
public int MaxPageSize { get; set; } = 100;
public int DefaultPageSize { get; set; } = 10;
}Inject vào controller:
public class ProductsController : ControllerBase
{
private readonly ApiSettings _settings;
public ProductsController(IOptions<ApiSettings> settings)
{
_settings = settings.Value;
}
[HttpGet]
public ActionResult<IEnumerable<Product>> GetAll(
[FromQuery] int page = 1,
[FromQuery] int? pageSize = null)
{
var size = Math.Min(pageSize ?? _settings.DefaultPageSize, _settings.MaxPageSize);
var paged = _products.Skip((page - 1) * size).Take(size);
return Ok(paged);
}
}Biến Môi Trường
Ghi đè cấu hình với biến môi trường:
# Ghi đè "Api:MaxPageSize" trong appsettings.json
export Api__MaxPageSize=50 # Dùng dấu gạch dưới kép cho key lồng nhau
dotnet runFile Theo Môi Trường
appsettings.json # Cơ sở (tất cả môi trường)
appsettings.Development.json # Ghi đè cho dev cục bộ
appsettings.Production.json # Ghi đè cho productionMôi trường được kiểm soát bởi biến ASPNETCORE_ENVIRONMENT (Development, Staging, Production).
Bước 10: Xử Lý Exception Toàn Cục
Đừng để exception chưa xử lý rò rỉ stack trace đến client. Thêm xử lý exception toàn cục trong Program.cs:
// Program.cs — thêm trước app.MapControllers()
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
await context.Response.WriteAsJsonAsync(new
{
type = "https://tools.ietf.org/html/rfc9110#section-15.6.1",
title = "Đã xảy ra lỗi không mong đợi.",
status = 500
});
}
});
});Hoặc trong .NET 8+, sử dụng interface IExceptionHandler gọn hơn:
// Infrastructure/GlobalExceptionHandler.cs
using Microsoft.AspNetCore.Diagnostics;
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
{
_logger = logger;
}
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
_logger.LogError(exception, "Exception chưa xử lý");
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
await httpContext.Response.WriteAsJsonAsync(new
{
title = "Đã xảy ra lỗi không mong đợi.",
status = 500
}, cancellationToken);
return true;
}
}Đăng ký trong Program.cs:
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
// Trong pipeline:
app.UseExceptionHandler();Kết Hợp Tất Cả Lại
Đây là luồng hoàn chỉnh cho một POST request:
Kiểm Thử API
Sử Dụng Swagger UI
Swagger UI tại /swagger cho phép bạn kiểm thử tất cả endpoint trực tiếp trong trình duyệt. Click vào endpoint bất kỳ, nhấn "Try it out", điền giá trị và thực thi.
Sử Dụng curl
# GET tất cả sản phẩm
curl http://localhost:5000/api/products
# GET sản phẩm theo ID
curl http://localhost:5000/api/products/1
# POST tạo sản phẩm
curl -X POST http://localhost:5000/api/products \
-H "Content-Type: application/json" \
-d '{"name": "Monitor", "price": 399.99, "stock": 5}'
# PUT cập nhật sản phẩm
curl -X PUT http://localhost:5000/api/products/1 \
-H "Content-Type: application/json" \
-d '{"name": "Laptop Pro", "price": 1299.99, "stock": 8}'
# DELETE sản phẩm
curl -X DELETE http://localhost:5000/api/products/1Sử Dụng VS Code REST Client
Cài extension "REST Client" và tạo file requests.http:
### Lấy tất cả sản phẩm
GET http://localhost:5000/api/products
### Lấy sản phẩm theo ID
GET http://localhost:5000/api/products/1
### Tạo sản phẩm
POST http://localhost:5000/api/products
Content-Type: application/json
{
"name": "Monitor",
"price": 399.99,
"stock": 5
}
### Cập nhật sản phẩm
PUT http://localhost:5000/api/products/1
Content-Type: application/json
{
"name": "Laptop Pro",
"price": 1299.99,
"stock": 8
}
### Xóa sản phẩm
DELETE http://localhost:5000/api/products/1Các Khái Niệm Quan Trọng Cần Nhớ
[ApiController] — thêm validation tự động, suy luận binding và response problem details.
ControllerBase — class cơ sở cho Web API controller. Có tất cả phương thức helper (Ok, NotFound, CreatedAtAction, v.v.) nhưng không có hỗ trợ View.
ActionResult<T> — kiểu trả về hỗ trợ cả kết quả có type và bất kỳ IActionResult nào (như NotFound()).
Bài Tập Thực Hành
- Thêm Tìm Kiếm: Thêm endpoint
GET /api/products/search?query=laptoplọc theo tên. - Thêm Header Phân Trang: Trả về header
X-Total-Count,X-Page,X-Page-Sizetrong endpoint GET all. - Thêm Category: Thêm trường
Categoryvào modelProductvà bộ lọcGET /api/products?category=electronics. - Thêm Soft Delete: Thay vì xóa khỏi danh sách, thêm cờ
IsDeletedvà lọc nó ra khỏi response GET. - Response Lỗi Tùy Chỉnh: Tùy chỉnh response 404 để trả về
{ "error": "Không tìm thấy sản phẩm với ID {id}." }.
Tóm Tắt
Bạn đã xây dựng REST API hoàn chỉnh với ASP.NET Core từ đầu. Đây là những gì bạn đã thực hiện:
✅ Cài đặt .NET SDK và tạo dự án
✅ Cấu trúc dự án: Program.cs, controller, model
✅ Routing với attribute routing và HTTP method attribute
✅ Model binding: route param, query string, request body
✅ Validation đầu vào với Data Annotations
✅ HTTP status code đúng cho mọi tình huống
✅ Tài liệu Swagger / OpenAPI
✅ Cấu hình với appsettings.json và biến môi trường
✅ Xử lý exception toàn cục
Trong Giai đoạn 2, bạn sẽ thay thế danh sách in-memory bằng cơ sở dữ liệu PostgreSQL thực sự sử dụng Entity Framework Core.
Series: Lộ Trình Học ASP.NET Core
Trước: Lộ Trình Học ASP.NET Core (Tổng Quan)
Tiếp Theo: Giai Đoạn 2: Truy Cập Dữ Liệu với Entity Framework Core →
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.