Xây dựng ứng dụng website ASP.NET Core Razor Pages theo mô hình Database First

🕒 Đăng ngày: 18/07/2025 |   Lượt xem: 124

Vì sao cần có từng lớp?

Mục tiêu chính:
- Tách biệt trách nhiệm (Separation of Concerns)
- Dễ bảo trì, dễ mở rộng, dễ kiểm thử (Maintainable, Extendable, Testable)
- Tuân thủ nguyên lý lập trình OOP và Clean Architecture.

1. Mô tả chi tiết từng lớp & Vai trò

1.1. Database First
Database đã tồn tại từ trước (Database First). Từ đó sinh ra Entity (Model) tự động bằng .edmx, Scaffold-DbContext, hoặc EF Core.
Do hệ thống đã có sẵn database, không thể dùng Code First hoặc cần khai thác dữ liệu kế thừa.

1.2. Business Logic Layer (BLL)
Nơi xử lý luật nghiệp vụ cốt lõi.

Ví dụ:

- Đơn hàng không được tạo nếu tồn kho < số lượng đặt.

- Không cho phép huỷ đơn khi đơn đã giao.

Lý do tách biệt:
- Không lẫn lộn nghiệp vụ với UI.
- Thay đổi UI hoặc DB không ảnh hưởng nghiệp vụ.

1.3. Data Access Layer (DAL)
Quản lý kết nối DB, thực thi câu lệnh SQL / EF / Dapper.
Ví dụ: CRUD, query, store procedure.

Lý do tách biệt:
- Đảm bảo code thao tác DB tập trung một nơi.
- Dễ kiểm thử, dễ thay đổi công nghệ DB.

1.4. Repository Layer
Đóng vai trò kho trung gian (Repository Pattern)
Cung cấp các method chuẩn hóa để lấy dữ liệu mà không cần quan tâm bên dưới dùng SQL, EF, Dapper…
Ví dụ:

IProductRepository.GetAll()
IProductRepository.FindById(id)

Lý do:
- Che giấu phức tạp DAL
- Giao diện (interface) rõ ràng, dễ test, dễ mock.

1.5. Services Layer
Nơi kết hợp nhiều Repository và xử lý nghiệp vụ phức tạp liên quan nhiều entity.
Ví dụ: Xử lý Order phải gọi InventoryRepository, PaymentRepository, ShippingRepository cùng lúc.

Lý do:
- Đảm bảo BLL không thao tác trực tiếp DAL
- Chuẩn hoá các tác vụ nghiệp vụ thành Service.

1.6. UI
Tầng hiển thị, chịu trách nhiệm nhận dữ liệu từ Service, hiển thị ra người dùng.

Lý do:
- UI tách biệt hoàn toàn logic, chỉ lo hiển thị.
- Thay đổi UI không ảnh hưởng Service hoặc Repository.

2. Lợi ích khi xây dựng theo kiến trúc này

Tiêu chíĐạt được
Dễ bảo trìMỗi lớp 1 nhiệm vụ rõ ràng
Dễ mở rộngThêm nghiệp vụ chỉ sửa Service
Dễ kiểm thửTest từng Service / Repository riêng
Tính tái sử dụngRepository & Service dùng lại được
Chống phụ thuộcUI không phụ thuộc trực tiếp DB

Nếu bỏ qua các tầng này thì hậu quả?

Sai sót thường gặpTác hại
UI gọi thẳng DBKhó kiểm soát logic, khó bảo trì
Không có RepositoryTrùng lặp code, khó đổi công nghệ
Không có Service LayerKhông gom được nghiệp vụ phức tạp
Lẫn lộn DAL / BLL / UICode khó đọc, rối, dễ bug, không test được

3. Các bước xây dựng ứng dụng

BƯỚC 1: TẠO SOLUTION VÀ PROJECT

Khởi tạo solution:

dotnet new sln -n DemoCleanArch
cd DemoCleanArch

Tạo từng Project:

dotnet new classlib -n Demo.BusinessLogic
dotnet new classlib -n Demo.DataAccess
dotnet new classlib -n Demo.Repository
dotnet new classlib -n Demo.Services
dotnet new razor -n Demo.WebRazorPages

Thêm vào Solution:

dotnet sln add Demo.BusinessLogic/
dotnet sln add Demo.DataAccess/
dotnet sln add Demo.Repository/
dotnet sln add Demo.Services/
dotnet sln add Demo.WebRazorPages/

BƯỚC 2: THIẾT LẬP REFERENCES

Tạo kết nối giữa các tầng:

dotnet add Demo.Repository reference Demo.DataAccess
dotnet add Demo.Services reference Demo.Repository
dotnet add Demo.Services reference Demo.BusinessLogic
dotnet add Demo.WebRazorPages reference Demo.Services

BƯỚC 3: CÀI ENTITY FRAMEWORK CORE SQLSERVER

dotnet add Demo.DataAccess package Microsoft.EntityFrameworkCore.SqlServer
dotnet add Demo.DataAccess package Microsoft.EntityFrameworkCore.Tools

BƯỚC 4: SCAFFOLD DATABASE FIRST (trong Demo.DataAccess)

cd Demo.DataAccess
dotnet ef dbcontext scaffold "Server=.;Database=YourDbName;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Models -c AppDbContext --data-annotations

BƯỚC 5: TẠO REPOSITORY

Demo.Repository/IUserRepository.cs

public interface IUserRepository
{
   Task<User> GetByIdAsync(int id);
   Task<IEnumerable<User>> GetAllAsync();
}

Demo.Repository/UserRepository.cs

public class UserRepository : IUserRepository
{
   private readonly AppDbContext _context;
   public UserRepository(AppDbContext context) => _context = context;
   public async Task<User> GetByIdAsync(int id) => await _context.Users.FindAsync(id);
   public async Task<IEnumerable<User>> GetAllAsync() => await _context.Users.ToListAsync();
}

 

BƯỚC 6: BUSINESS LOGIC

Demo.BusinessLogic/UserBusiness.cs

public class UserBusiness
{
   public bool IsAdmin(User user) => user.Role == 1;
}

 

BƯỚC 7: SERVICE LAYER

Demo.Services/UserService.cs

public class UserService
{
   private readonly IUserRepository _repo;
   private readonly UserBusiness _business;
   public UserService(IUserRepository repo, UserBusiness business)
   {
       _repo = repo;
       _business = business;
   }
   public async Task<IEnumerable<User>> GetAllAsync() => await _repo.GetAllAsync();
   public async Task<bool> IsAdminAsync(int id)
   {
       var user = await _repo.GetByIdAsync(id);
       return _business.IsAdmin(user);
   }
}

BƯỚC 8: CẤU HÌNH DEPENDENCY INJECTION CHO WEB

Trong Program.cs (Demo.WebRazorPages):

builder.Services.AddDbContext<AppDbContext>(options =>
   options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<UserBusiness>();
builder.Services.AddScoped<UserService>();

BƯỚC 9: RAZOR PAGES KẾT NỐI SERVICE

Pages/UserList.cshtml.cs

public class UserListModel : PageModel
{
   private readonly UserService _userService;
   public IList<User> Users { get; set; }
   public UserListModel(UserService userService) => _userService = userService;
   public async Task OnGetAsync() => Users = (await _userService.GetAllAsync()).ToList();
}

 

UserList.cshtml

@foreach (var user in Model.Users)
{
   <p>@user.FullName - @(user.Role == 1 ? "Admin" : "Staff")</p>
}

BƯỚC 10: CHẠY PROJECT

dotnet run --project Demo.WebRazorPages