.NET 8: Xây dựng API với ASP.NET Core
Hướng dẫn đầy đủ về xây dựng REST API chuyên nghiệp với .NET 8 và ASP.NET Core. Controller, Entity Framework Core, validation và các phương pháp tốt nhất được giải thích chi tiết.

.NET 8 là phiên bản chính thức quan trọng của framework Microsoft, mang đến những cải tiến đáng kể về hiệu suất và năng suất cho việc phát triển API. ASP.NET Core kết hợp sức mạnh của C# với kiến trúc hiện đại, module hóa, lý tưởng cho các ứng dụng doanh nghiệp. Hướng dẫn này bao gồm toàn bộ quy trình tạo REST API chuyên nghiệp, từ cấu hình ban đầu đến code sẵn sàng triển khai.
.NET 8 là phiên bản Long-Term Support (LTS) với 3 năm hỗ trợ. Các cải tiến về Minimal API và Native AOT giúp nó trở thành lựa chọn tối ưu cho microservices và ứng dụng cloud-native.
Initial Project Setup with .NET 8
Việc tạo dự án API ASP.NET Core sử dụng .NET CLI để sinh ra cấu trúc dự án tối ưu. Cấu hình các gói NuGet cần thiết sẽ chuẩn bị nền tảng cho việc phát triển.
# terminal
# Check installed .NET version
dotnet --version
# Expected: 8.0.x
# Create the API project
dotnet new webapi -n ProductApi -o ProductApi
cd ProductApi
# Add essential packages
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package FluentValidation.AspNetCore
dotnet add package Swashbuckle.AspNetCoreCác lệnh trên tạo dự án API với các dependency cần thiết cho Entity Framework Core, validation và tài liệu Swagger.
using Microsoft.EntityFrameworkCore;
using ProductApi.Data;
using ProductApi.Services;
using FluentValidation;
using FluentValidation.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
// Configure Entity Framework Core with SQL Server
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Register business services
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<ICategoryService, CategoryService>();
// Configure controllers with validation
builder.Services.AddControllers();
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
// Configure Swagger for documentation
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() { Title = "Product API", Version = "v1" });
});
var app = builder.Build();
// Middleware pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();Cấu hình này sử dụng mô hình Minimal API của .NET 8 đồng thời vẫn giữ lại controller để đảm bảo cấu trúc rõ ràng và dễ bảo trì.
Data Models and Entity Framework Core
Model đại diện cho các thực thể nghiệp vụ của ứng dụng. Entity Framework Core xử lý việc ánh xạ đối tượng-quan hệ với cấu hình fluent và các quy ước thông minh.
namespace ProductApi.Models;
public class Product
{
// Primary key with auto-increment
public int Id { get; set; }
// Required properties (non-nullable in C# 8+)
public required string Name { get; set; }
public required string Description { get; set; }
// Price with decimal precision
public decimal Price { get; set; }
// Stock with default value
public int StockQuantity { get; set; } = 0;
// Product status
public bool IsActive { get; set; } = true;
// Relationship with Category (foreign key)
public int CategoryId { get; set; }
public Category? Category { get; set; }
// Automatic tracking dates
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
}Từ khóa required từ C# 11 đảm bảo các thuộc tính quan trọng luôn được khởi tạo khi tạo đối tượng.
namespace ProductApi.Models;
public class Category
{
public int Id { get; set; }
public required string Name { get; set; }
// Slug for friendly URLs
public required string Slug { get; set; }
public string? Description { get; set; }
// Inverse navigation: list of products in this category
public ICollection<Product> Products { get; set; } = new List<Product>();
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}using Microsoft.EntityFrameworkCore;
using ProductApi.Models;
namespace ProductApi.Data;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
// DbSets for each entity
public DbSet<Product> Products => Set<Product>();
public DbSet<Category> Categories => Set<Category>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Product entity configuration
modelBuilder.Entity<Product>(entity =>
{
// Index on name for fast search
entity.HasIndex(p => p.Name);
// Price precision: 18 digits, 2 decimals
entity.Property(p => p.Price)
.HasPrecision(18, 2);
// Relationship with Category
entity.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId)
.OnDelete(DeleteBehavior.Restrict);
});
// Category entity configuration
modelBuilder.Entity<Category>(entity =>
{
// Unique slug
entity.HasIndex(c => c.Slug).IsUnique();
// Maximum name length
entity.Property(c => c.Name).HasMaxLength(100);
});
}
}Cấu hình Fluent API cung cấp quyền kiểm soát chính xác đối với schema database được tạo bởi EF Core migrations.
Migration quản lý phiên bản schema database. Chạy dotnet ef migrations add InitialCreate sau đó dotnet ef database update để áp dụng các thay đổi.
DTOs and Validation with FluentValidation
DTO (Data Transfer Object) tách biệt model domain khỏi dữ liệu được cung cấp qua API. FluentValidation cung cấp validation khai báo và dễ bảo trì.
namespace ProductApi.DTOs;
// DTO for product creation
public record CreateProductDto(
string Name,
string Description,
decimal Price,
int StockQuantity,
int CategoryId
);
// DTO for product update
public record UpdateProductDto(
string? Name,
string? Description,
decimal? Price,
int? StockQuantity,
bool? IsActive
);
// DTO for response (read)
public record ProductDto(
int Id,
string Name,
string Description,
decimal Price,
int StockQuantity,
bool IsActive,
string CategoryName,
DateTime CreatedAt
);
// DTO for list with pagination
public record ProductListDto(
int Id,
string Name,
decimal Price,
int StockQuantity,
bool IsActive,
string CategoryName
);Việc sử dụng record từ C# 9+ giúp DTO trở nên immutable và ngắn gọn, với value equality tự động.
using FluentValidation;
using ProductApi.DTOs;
namespace ProductApi.Validators;
public class CreateProductValidator : AbstractValidator<CreateProductDto>
{
public CreateProductValidator()
{
// Name is required and limited to 200 characters
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Product name is required.")
.MaximumLength(200).WithMessage("Name cannot exceed 200 characters.");
// Description required with minimum length
RuleFor(x => x.Description)
.NotEmpty().WithMessage("Description is required.")
.MinimumLength(10).WithMessage("Description must contain at least 10 characters.");
// Positive price required
RuleFor(x => x.Price)
.GreaterThan(0).WithMessage("Price must be greater than 0.")
.LessThanOrEqualTo(999999.99m).WithMessage("Maximum price is 999,999.99.");
// Non-negative stock
RuleFor(x => x.StockQuantity)
.GreaterThanOrEqualTo(0).WithMessage("Stock cannot be negative.");
// Valid category
RuleFor(x => x.CategoryId)
.GreaterThan(0).WithMessage("A valid category is required.");
}
}
public class UpdateProductValidator : AbstractValidator<UpdateProductDto>
{
public UpdateProductValidator()
{
// Conditional validation: only if value is provided
RuleFor(x => x.Name)
.MaximumLength(200)
.When(x => !string.IsNullOrEmpty(x.Name));
RuleFor(x => x.Price)
.GreaterThan(0)
.When(x => x.Price.HasValue);
RuleFor(x => x.StockQuantity)
.GreaterThanOrEqualTo(0)
.When(x => x.StockQuantity.HasValue);
}
}FluentValidation tự động tích hợp với pipeline validation của ASP.NET Core, trả về lỗi có cấu trúc với mã 400.
Sẵn sàng chinh phục phỏng vấn .NET?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Business Services and Abstraction Layer
Tầng service đóng gói logic nghiệp vụ và các thao tác database, giúp việc testing và bảo trì dễ dàng hơn.
using ProductApi.DTOs;
namespace ProductApi.Services;
public interface IProductService
{
// Retrieval with pagination
Task<(IEnumerable<ProductListDto> Items, int TotalCount)> GetAllAsync(
int page = 1,
int pageSize = 10,
string? search = null,
int? categoryId = null);
// Retrieval by ID
Task<ProductDto?> GetByIdAsync(int id);
// Creation
Task<ProductDto> CreateAsync(CreateProductDto dto);
// Update
Task<ProductDto?> UpdateAsync(int id, UpdateProductDto dto);
// Deletion
Task<bool> DeleteAsync(int id);
}using Microsoft.EntityFrameworkCore;
using ProductApi.Data;
using ProductApi.DTOs;
using ProductApi.Models;
namespace ProductApi.Services;
public class ProductService : IProductService
{
private readonly AppDbContext _context;
public ProductService(AppDbContext context)
{
_context = context;
}
public async Task<(IEnumerable<ProductListDto> Items, int TotalCount)> GetAllAsync(
int page = 1,
int pageSize = 10,
string? search = null,
int? categoryId = null)
{
// Build base query
var query = _context.Products
.Include(p => p.Category)
.AsQueryable();
// Filter by text search
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(p =>
p.Name.Contains(search) ||
p.Description.Contains(search));
}
// Filter by category
if (categoryId.HasValue)
{
query = query.Where(p => p.CategoryId == categoryId.Value);
}
// Total count before pagination
var totalCount = await query.CountAsync();
// Apply pagination
var items = await query
.OrderByDescending(p => p.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(p => new ProductListDto(
p.Id,
p.Name,
p.Price,
p.StockQuantity,
p.IsActive,
p.Category!.Name))
.ToListAsync();
return (items, totalCount);
}
public async Task<ProductDto?> GetByIdAsync(int id)
{
// Retrieve with category inclusion
var product = await _context.Products
.Include(p => p.Category)
.FirstOrDefaultAsync(p => p.Id == id);
if (product == null) return null;
// Map to DTO
return new ProductDto(
product.Id,
product.Name,
product.Description,
product.Price,
product.StockQuantity,
product.IsActive,
product.Category?.Name ?? "Uncategorized",
product.CreatedAt);
}
public async Task<ProductDto> CreateAsync(CreateProductDto dto)
{
// Create entity
var product = new Product
{
Name = dto.Name,
Description = dto.Description,
Price = dto.Price,
StockQuantity = dto.StockQuantity,
CategoryId = dto.CategoryId
};
// Add and save
_context.Products.Add(product);
await _context.SaveChangesAsync();
// Load category for response
await _context.Entry(product)
.Reference(p => p.Category)
.LoadAsync();
return new ProductDto(
product.Id,
product.Name,
product.Description,
product.Price,
product.StockQuantity,
product.IsActive,
product.Category?.Name ?? "Uncategorized",
product.CreatedAt);
}
public async Task<ProductDto?> UpdateAsync(int id, UpdateProductDto dto)
{
// Retrieve existing entity
var product = await _context.Products
.Include(p => p.Category)
.FirstOrDefaultAsync(p => p.Id == id);
if (product == null) return null;
// Conditional field updates
if (!string.IsNullOrEmpty(dto.Name))
product.Name = dto.Name;
if (!string.IsNullOrEmpty(dto.Description))
product.Description = dto.Description;
if (dto.Price.HasValue)
product.Price = dto.Price.Value;
if (dto.StockQuantity.HasValue)
product.StockQuantity = dto.StockQuantity.Value;
if (dto.IsActive.HasValue)
product.IsActive = dto.IsActive.Value;
// Update modification date
product.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
return new ProductDto(
product.Id,
product.Name,
product.Description,
product.Price,
product.StockQuantity,
product.IsActive,
product.Category?.Name ?? "Uncategorized",
product.CreatedAt);
}
public async Task<bool> DeleteAsync(int id)
{
// Direct deletion without prior loading
var result = await _context.Products
.Where(p => p.Id == id)
.ExecuteDeleteAsync();
return result > 0;
}
}Việc sử dụng ExecuteDeleteAsync (mới trong EF Core 7+) cải thiện hiệu suất bằng cách tránh việc load entity trước khi xóa.
API Controllers and REST Endpoints
Controller cung cấp các endpoint REST và điều phối các lời gọi đến service nghiệp vụ với việc xử lý mã trạng thái HTTP phù hợp.
using Microsoft.AspNetCore.Mvc;
using ProductApi.DTOs;
using ProductApi.Services;
namespace ProductApi.Controllers;
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
{
_productService = productService;
}
/// <summary>
/// Retrieves the list of products with pagination and filters.
/// </summary>
[HttpGet]
[ProducesResponseType(typeof(PaginatedResponse<ProductListDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetAll(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10,
[FromQuery] string? search = null,
[FromQuery] int? categoryId = null)
{
// Validate pagination parameters
if (page < 1) page = 1;
if (pageSize < 1 || pageSize > 100) pageSize = 10;
var (items, totalCount) = await _productService.GetAllAsync(
page, pageSize, search, categoryId);
// Standardized paginated response
var response = new PaginatedResponse<ProductListDto>
{
Items = items,
Page = page,
PageSize = pageSize,
TotalCount = totalCount,
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
};
return Ok(response);
}
/// <summary>
/// Retrieves a product by its identifier.
/// </summary>
[HttpGet("{id:int}")]
[ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int id)
{
var product = await _productService.GetByIdAsync(id);
if (product == null)
{
return NotFound(new { message = $"Product with ID {id} not found." });
}
return Ok(product);
}
/// <summary>
/// Creates a new product.
/// </summary>
[HttpPost]
[ProducesResponseType(typeof(ProductDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Create([FromBody] CreateProductDto dto)
{
// Validation is automatic via FluentValidation
var product = await _productService.CreateAsync(dto);
// Returns 201 with the created resource URL
return CreatedAtAction(
nameof(GetById),
new { id = product.Id },
product);
}
/// <summary>
/// Updates an existing product.
/// </summary>
[HttpPut("{id:int}")]
[ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Update(int id, [FromBody] UpdateProductDto dto)
{
var product = await _productService.UpdateAsync(id, dto);
if (product == null)
{
return NotFound(new { message = $"Product with ID {id} not found." });
}
return Ok(product);
}
/// <summary>
/// Deletes a product.
/// </summary>
[HttpDelete("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Delete(int id)
{
var deleted = await _productService.DeleteAsync(id);
if (!deleted)
{
return NotFound(new { message = $"Product with ID {id} not found." });
}
// 204 No Content for successful deletion
return NoContent();
}
}Thuộc tính ProducesResponseType ghi lại các mã response có thể có để tự động tạo tài liệu Swagger.
namespace ProductApi.DTOs;
public class PaginatedResponse<T>
{
public IEnumerable<T> Items { get; set; } = Enumerable.Empty<T>();
public int Page { get; set; }
public int PageSize { get; set; }
public int TotalCount { get; set; }
public int TotalPages { get; set; }
public bool HasPreviousPage => Page > 1;
public bool HasNextPage => Page < TotalPages;
}Việc sử dụng constraint như {id:int} ngăn chặn xung đột routing và tự động trả về 404 nếu định dạng không đúng.
Global Error Handling
Middleware xử lý lỗi tập trung hóa việc xử lý exception để đảm bảo response nhất quán và an toàn.
using System.Net;
using System.Text.Json;
namespace ProductApi.Middleware;
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
private readonly IHostEnvironment _env;
public ExceptionMiddleware(
RequestDelegate next,
ILogger<ExceptionMiddleware> logger,
IHostEnvironment env)
{
_next = next;
_logger = logger;
_env = env;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
// Continue pipeline
await _next(context);
}
catch (Exception ex)
{
// Log the error
_logger.LogError(ex, "An unhandled exception occurred");
// Prepare response
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
// Different response based on environment
var response = _env.IsDevelopment()
? new ErrorResponse(
StatusCode: context.Response.StatusCode,
Message: ex.Message,
Details: ex.StackTrace)
: new ErrorResponse(
StatusCode: context.Response.StatusCode,
Message: "An internal error occurred.",
Details: null);
// Serialize with camelCase options
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var json = JsonSerializer.Serialize(response, options);
await context.Response.WriteAsync(json);
}
}
}
// DTO for error responses
public record ErrorResponse(int StatusCode, string Message, string? Details);
// Extension to register middleware
public static class ExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder app)
{
return app.UseMiddleware<ExceptionMiddleware>();
}
}var app = builder.Build();
// Exception middleware must be first
app.UseExceptionMiddleware();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// ... rest of configurationConfiguration and Environment Variables
Cấu hình ngoại vi hóa cho phép điều chỉnh ứng dụng cho các môi trường khác nhau mà không cần thay đổi code.
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=ProductDb;User Id=sa;Password=YourPassword;TrustServerCertificate=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Warning"
}
},
"ApiSettings": {
"DefaultPageSize": 10,
"MaxPageSize": 100,
"ApiVersion": "1.0"
}
}namespace ProductApi.Configuration;
public class ApiSettings
{
public int DefaultPageSize { get; set; } = 10;
public int MaxPageSize { get; set; } = 100;
public string ApiVersion { get; set; } = "1.0";
}builder.Services.Configure<ApiSettings>(
builder.Configuration.GetSection("ApiSettings"));
// Usage in a service
public class ProductService : IProductService
{
private readonly ApiSettings _settings;
public ProductService(IOptions<ApiSettings> settings)
{
_settings = settings.Value;
}
}Unit Testing with xUnit
Unit test kiểm tra hành vi của service và controller một cách độc lập.
using Microsoft.EntityFrameworkCore;
using ProductApi.Data;
using ProductApi.DTOs;
using ProductApi.Models;
using ProductApi.Services;
using Xunit;
namespace ProductApi.Tests;
public class ProductServiceTests
{
private AppDbContext CreateInMemoryContext()
{
// Configure in-memory database
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new AppDbContext(options);
}
[Fact]
public async Task CreateAsync_ValidDto_ReturnsProductDto()
{
// Arrange
using var context = CreateInMemoryContext();
// Add test category
var category = new Category { Id = 1, Name = "Electronics", Slug = "electronics" };
context.Categories.Add(category);
await context.SaveChangesAsync();
var service = new ProductService(context);
var dto = new CreateProductDto(
Name: "Test Product",
Description: "Test Description",
Price: 99.99m,
StockQuantity: 10,
CategoryId: 1);
// Act
var result = await service.CreateAsync(dto);
// Assert
Assert.NotNull(result);
Assert.Equal("Test Product", result.Name);
Assert.Equal(99.99m, result.Price);
Assert.Equal("Electronics", result.CategoryName);
}
[Fact]
public async Task GetByIdAsync_NonExistent_ReturnsNull()
{
// Arrange
using var context = CreateInMemoryContext();
var service = new ProductService(context);
// Act
var result = await service.GetByIdAsync(999);
// Assert
Assert.Null(result);
}
[Fact]
public async Task UpdateAsync_ExistingProduct_UpdatesFields()
{
// Arrange
using var context = CreateInMemoryContext();
var category = new Category { Id = 1, Name = "Tech", Slug = "tech" };
var product = new Product
{
Id = 1,
Name = "Original Name",
Description = "Original Description",
Price = 50.00m,
StockQuantity = 5,
CategoryId = 1
};
context.Categories.Add(category);
context.Products.Add(product);
await context.SaveChangesAsync();
var service = new ProductService(context);
var updateDto = new UpdateProductDto(
Name: "Updated Name",
Description: null,
Price: 75.00m,
StockQuantity: null,
IsActive: null);
// Act
var result = await service.UpdateAsync(1, updateDto);
// Assert
Assert.NotNull(result);
Assert.Equal("Updated Name", result.Name);
Assert.Equal(75.00m, result.Price);
// Fields not provided remain unchanged
Assert.Equal(5, result.StockQuantity);
}
[Fact]
public async Task DeleteAsync_ExistingProduct_ReturnsTrue()
{
// Arrange
using var context = CreateInMemoryContext();
var category = new Category { Id = 1, Name = "Test", Slug = "test" };
var product = new Product
{
Id = 1,
Name = "To Delete",
Description = "Will be deleted",
Price = 10.00m,
CategoryId = 1
};
context.Categories.Add(category);
context.Products.Add(product);
await context.SaveChangesAsync();
var service = new ProductService(context);
// Act
var result = await service.DeleteAsync(1);
// Assert
Assert.True(result);
Assert.Null(await context.Products.FindAsync(1));
}
[Fact]
public async Task GetAllAsync_WithSearch_FiltersResults()
{
// Arrange
using var context = CreateInMemoryContext();
var category = new Category { Id = 1, Name = "Category", Slug = "category" };
context.Categories.Add(category);
context.Products.AddRange(
new Product { Id = 1, Name = "Apple iPhone", Description = "Phone", Price = 999, CategoryId = 1 },
new Product { Id = 2, Name = "Samsung Galaxy", Description = "Phone", Price = 899, CategoryId = 1 },
new Product { Id = 3, Name = "Apple MacBook", Description = "Laptop", Price = 1999, CategoryId = 1 }
);
await context.SaveChangesAsync();
var service = new ProductService(context);
// Act
var (items, totalCount) = await service.GetAllAsync(search: "Apple");
// Assert
Assert.Equal(2, totalCount);
Assert.All(items, p => Assert.Contains("Apple", p.Name));
}
}Chạy test bằng lệnh dotnet test từ thư mục gốc của dự án.
Conclusion
.NET 8 với ASP.NET Core cung cấp hệ sinh thái hoàn chỉnh và hiệu suất cao cho việc xây dựng REST API chuyên nghiệp. Sự kết hợp giữa Entity Framework Core cho truy cập dữ liệu, FluentValidation cho validation và dependency injection tích hợp sẵn cho phép xây dựng ứng dụng dễ bảo trì và dễ test.
Checklist cho API .NET chất lượng
- Tách biệt DTO khỏi model domain
- Triển khai service layer cho logic nghiệp vụ
- Sử dụng FluentValidation cho validation khai báo
- Cấu hình middleware xử lý lỗi toàn cục
- Ngoại vi hóa cấu hình với IOptions
- Viết unit test cho service
- Tài liệu hóa API với Swagger/OpenAPI
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Kiến trúc phân tầng (Controller -> Service -> Repository/DbContext) thúc đẩy việc tách biệt trách nhiệm và giúp ứng dụng phát triển dễ dàng hơn. Các tính năng của .NET 8 như record, thuộc tính required và ExecuteDeleteAsync hiện đại hóa code đồng thời cải thiện hiệu suất.
Thẻ
Chia sẻ
Bài viết liên quan

Câu Hỏi Phỏng Vấn C# và .NET: Hướng Dẫn Đầy Đủ 2026
17 câu hỏi phỏng vấn C# và .NET thường gặp nhất. LINQ, async/await, dependency injection, Entity Framework và các phương pháp tốt nhất với đáp án chi tiết.

Django 5: Xây Dụng REST API Với Django REST Framework
Hướng dẫn đầy đủ xây dụng REST API chuyên nghiệp với Django 5 và DRF. Serializers, ViewSets, xác thực JWT và các phương pháp tốt nhất được giải thích chi tiết.

Laravel 11: Xây dựng Ứng dụng Hoàn chỉnh từ Đầu
Hướng dẫn toàn diện xây dựng ứng dụng Laravel 11 với xác thực, REST API, Eloquent ORM và triển khai. Bài hướng dẫn thực hành dành cho lập trình viên từ cơ bản đến trung cấp.