diff --git a/.github/workflows/github-cicd.yml b/.github/workflows/github-cicd.yml index 733b2a18..e50e2157 100644 --- a/.github/workflows/github-cicd.yml +++ b/.github/workflows/github-cicd.yml @@ -8,9 +8,9 @@ on: branches: - main -env: - DOTNET_INSTALL_DIR: "./.dotnet" - NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages +#env: +#DOTNET_INSTALL_DIR: "./.dotnet" +#NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages jobs: build-and-deploy: @@ -27,9 +27,17 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: "8.0.406" - cache: true - cache-dependency-path: "**/packages.lock.json" + dotnet-version: "8.0.408" + #cache: true + #cache-dependency-path: "src/**/packages.lock.json" + - name: Cache NuGet packages + id: cache-nuget + uses: actions/cache@v4 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('src/**/packages.lock.json','tests/**/packages.lock.json') }} + restore-keys: | + ${{ runner.os }}-nuget- - name: Install dependencies run: dotnet restore --locked-mode - name: Build @@ -40,7 +48,7 @@ jobs: run: dotnet publish -c Release -o app/publish - uses: actions/upload-artifact@v4 with: - name: my-artifact + name: dotnet-app path: | app/publish app/output/test-results diff --git a/.gitignore b/.gitignore index ddbb8df2..411d7643 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ # dotenv files .env - +.vscode/ # User-specific files *.rsuser *.suo diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index bfe7a9ba..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - - { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md. - "name": ".NET Core Launch (web)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/src/Api/bin/Debug/net8.0/Api.dll", - "args": [], - "cwd": "${workspaceFolder}/src/Api", - "stopAtEntry": false, - // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser - "serverReadyAction": { - "action": "openExternally", - "pattern": "\\bNow listening on:\\s+(https?://\\S+)" - }, - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "sourceFileMap": { - "/Views": "${workspaceFolder}/Views" - } - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" - } - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index d880b8e0..00000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/template.sln", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary;ForceNoAlign" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/template.sln", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary;ForceNoAlign" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "--project", - "${workspaceFolder}/template.sln" - ], - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ecf72062..2998e6f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,4 @@ -# Use the official .NET runtime image as a base for production -FROM mcr.microsoft.com/dotnet/aspnet:8.0.12 AS base -WORKDIR /app -EXPOSE 8080 - -# Copy the published application from the workflow -FROM base AS final +FROM mcr.microsoft.com/dotnet/aspnet:8.0.15 WORKDIR /app COPY app/publish . RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* diff --git a/README-VIETNAMESE.md b/README-VIETNAMESE.md index 1f1be263..5e6a0ba4 100644 --- a/README-VIETNAMESE.md +++ b/README-VIETNAMESE.md @@ -4,10 +4,7 @@ # -![Visual Studio Code](https://img.shields.io/badge/Visual%20Studio%20Code-0078d7.svg?style=for-the-badge&logo=visual-studio-code&logoColor=white) -[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge)](LICENSE) ![GitHub Release](https://img.shields.io/github/v/release/minhsangdotcom/Clean-Architecture_The-Template?style=for-the-badge&color=orange) -![GitHub Org's stars](https://img.shields.io/github/stars/minhsangdotcom%2FClean-Architecture_The-Template?style=for-the-badge&color=pink) -![GitHub forks](https://img.shields.io/github/forks/minhsangdotcom/Clean-Architecture_The-Template?style=for-the-badge&color=%23f61d9c) +![Visual Studio Code](https://img.shields.io/badge/Visual%20Studio%20Code-0078d7.svg?logo=visual-studio-code&logoColor=white) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) ![GitHub Release](https://img.shields.io/github/v/release/minhsangdotcom/Clean-Architecture_The-Template?color=orange) ![GitHub Org's stars](https://img.shields.io/github/stars/minhsangdotcom%2FClean-Architecture_The-Template?color=pink) ![GitHub forks](https://img.shields.io/github/forks/minhsangdotcom/Clean-Architecture_The-Template?color=%23f61d9c) ![NuGet Version](https://img.shields.io/nuget/v/minhsangdotcom.TheTemplate.SharedKernel?label=SharedKernel&color=red) ![NuGet Version](https://img.shields.io/nuget/vpre/minhsangdotcom.TheTemplate.SpecificationPattern?style=flat&label=SpecificationPattern&color=red) ![NuGet Version](https://img.shields.io/nuget/vpre/minhsangdotcom.TheTemplate.ElasticsearchFluentConfig?style=flat&label=ElasticsearchFluentConfig&color=red) # Bảng nội dung @@ -21,9 +18,8 @@ - [Nhược điểm](#nhược-điểm) - [Tính năng :rocket:](#tinh-nang) - [Nhá hàng cho các tính năng :fire:](#nha-hang-cho-cac-tinh-nang) - - [Authentication](#authentication) - - [Dynamic search and sort](#dynamic-search-and-sort) - - [Cross-cutting concerns](#cross-cutting-concerns) + - [Api](#api) + - [Truy vết](#truy-vết) - [Lưu trử file media bằng Minio](#lưu-trử-file-media-bằng-minio) - [Tự động dịch message](#tự-động-dịch-message) - [Sơ lượt về Cấu trúc :mag_right:](#so-luot-ve-cau-truc) @@ -43,7 +39,7 @@ Template này được thiết kế dành cho các bạn backend làm việc với ASP.NET Core. Nó cung cấp một cách hiệu quả để xây dựng các ứng dụng enterprise một cách đơn giản bằng cách tận dụng lợi thế từ kiến trúc Clean Architecture và .NET Core framework. -Với template này, bạn sẽ có được zero configuration, không cần quan tâm đến cấu trúc, cài đặt, môi trường hoặc các thông lệ tốt nhất cho web API, vì tất cả đã được thiết lập :smiley:. +Với template này, tất cả đã được thiết lập sẵn :smiley:.
@@ -55,22 +51,18 @@ Sự hỗ trợ của bạn là động lực giúp mình mang đến những t # Định Nghĩa -Clean Architecture là một triết lý thiết kế phần mềm được giới thiệu bởi Robert C. Martin (Uncle Bob). Nó nhấn mạnh việc tách biệt các mối quan tâm và khuyến khích việc tổ chức mã thành các lớp, mỗi lớp có trách nhiệm riêng biệt. Mục tiêu chính của kiến trúc là tạo ra các hệ thống không phụ thuộc vào framework, giao diện người dùng, cơ sở dữ liệu và các cơ quan bên ngoài, đảm bảo tính linh hoạt, khả năng mở rộng và dễ dàng kiểm thử. - -Tại phần trung tâm, Clean Architecture tổ chức mã thành các vòng tròn đồng tâm, với mỗi lớp đều có mục đích cụ thể. +Kiến trúc Sạch (Clean Architecture) là một phương pháp thiết kế phần mềm do Robert C. Martin (Uncle Bob) giới thiệu, nhấn mạnh vào thuật ngữ "Tách biệt các thành phần",các tầng ngoài cùng sẽ phụ thuộc vào các tầng ở trong như hình. Tầng core sẽ không phụ thuộc vào các framework bên ngoài, cơ sở dữ liệu hay giao diện người dùng, từ đó giúp hệ thống dễ bảo trì, kiểm thử và phát triển theo thời gian. ![Alt text](Screenshots/clean-architecture.png "Cấu trúc chung của Clean Architecture") -Quy tắc phụ thuộc nói rằng các thành phần phụ thuộc hướng từ ngoài vào trong, đảm bảo rằng các tầng bên trong vẫn tách biệt với các tầng bên ngoài. - ### Lợi ích -- **_Các thành phần tách biệt_**: Mỗi một tầng chịu trách nhiệm cho một khía cạnh của ứng dụng, giúp mã dễ hiểu và bảo trì. -- **_Dễ dàng kiểm thử_**: Các business logic được tách biệt khỏi framework và UI, việc kiểm thử đơn vị trở nên đơn giản và đáng tin cậy hơn. -- **_Linh hoạt và Thích nghi_**: Khi thay đổi framework, cơ sở dữ liệu hoặc các hệ thống bên ngoài ít ảnh hưởng đến logic của phần core. -- **_Tái sử dụng_**: Các Business rules có thể được tái sử dụng trong các ứng dụng hoặc hệ thống khác mà không phải thay đổi quá nhiều code. -- **_Khả năng mở rộng_**: Cấu trúc rõ ràng hỗ trợ việc phát triển và thêm tính năng mới mà không cần tái cơ cấu lại. -- **_Không phụ thuộc vào framework_**: Không bị phụ thuộc nhiều vào framework, rất dễ dàng để thanh đổi công nghệ mới. +- **Các thành phần tách biệt**: Mỗi một tầng chịu trách nhiệm cho một khía cạnh của ứng dụng, giúp mã dễ hiểu và bảo trì. +- **Dễ dàng kiểm thử**: Các business logic được tách biệt khỏi framework và UI, việc kiểm thử đơn vị trở nên đơn giản và đáng tin cậy hơn. +- **Linh hoạt và Thích nghi**: Khi thay đổi framework, cơ sở dữ liệu hoặc các hệ thống bên ngoài ít ảnh hưởng đến logic của phần core. +- **Tái sử dụng**: Các Business rules có thể được tái sử dụng trong các ứng dụng hoặc hệ thống khác mà không phải thay đổi quá nhiều code. +- **Khả năng mở rộng**: Cấu trúc rõ ràng hỗ trợ việc phát triển và thêm tính năng mới mà không cần tái cơ cấu lại. +- **Không phụ thuộc vào framework**: Không bị phụ thuộc nhiều vào framework, rất dễ dàng để thanh đổi công nghệ mới. ### Nhược điểm @@ -86,78 +78,42 @@ Quy tắc phụ thuộc nói rằng các thành phần phụ thuộc hướng t Có gì đặc biệt khiến cho template này trở nên khác biệt so với những template khác có trên Github? -Nó không chỉ có một cấu trúc rất hiện đại dễ dàng mở rộng và duy trì, mà còn có một loại các tính năng, design pattern vô cùng cực hữa ích đặc biệt là cho .NET Core Web API, - -Giúp cho bạn làm project của mình mà ít tốn công sức nhất. - -Nào hãy cùng mình khám phá nha : - -1. [Authentication với JWT](src/Infrastructure/Services/Identity/) -1. [Authorization bằng Vai trò và quyền](#authorize) -1. [Dynamic Search](src/Contracts/Extensions/QueryExtensions/SearchExtensions.cs), [Dynamic Sort](src/Contracts/Extensions/QueryExtensions/SortExtension.cs), [Dynamic Filter](#filtering),[Offset and Cursor Pagination](#pagination) -1. [Lưu trữ media bằng AWS S3](src/Infrastructure/Services/Aws/) -1. [Tích hợp sẳn Elastic Search](src/Infrastructure/Services/Elastics/) -1. [Tích Hợp Domain Event](src/Application//Common/DomainEventHandlers/) -1. [Cross-cutting Concerns](src/Application/Common/Behaviors/) -1. [Distributed cache by Redis](src/Infrastructure/Services/DistributedCache/RedisCacheService.cs) -1. [Xử lý bất đồng bộ nhiều Request cùng lúc bằng hàng đợi (ví dụ ở nhánh feature/TicketSale)](src/Infrastructure/Services/DistributedCache/) -1. [Gửi Email](src/Infrastructure/Services/Mail/) -1. [Tích hợp Schedule jobs bằng Hangfire](src/Infrastructure/Services/Hangfires/) -1. [Specification Pattern](src/Domain/Common/Specs/), [Uit of work and Repository pattern](src/Infrastructure/UnitOfWorks/), [Cached repository with decorator design pattern](src/Infrastructure/UnitOfWorks/CachedRepositories/) -1. [Subcutaneous Test](tests/Application.SubcutaneousTests/) -1. [Tự động dịch message](src/Contracts/Common/Messages/) -1. [Mã nguồn mở và Cấp phép MIT](#license) +### Tính năng cần thiết cho mọi dự án: + +- Đăng nhập :closed_lock_with_key: +- Refresh token :arrows_counterclockwise: +- Đổi mật khẩu :repeat: +- Quên mật khẩu :unlock: +- Xem và cập nhật profile người dùng :man_with_gua_pi_mao: +- User CRUD :family: +- Role CRUD 🛡️ + +### Một số tính năng hữu ích khác: + +1. [DDD (Domain Driven Design)](/src/Domain/Aggregates/) :brain: +1. [CQRS & Mediator](/src/Application/Features/) :twisted_rightwards_arrows: +1. [Cross-cutting concern](/src/Application/Common/Behaviors/) :scissors: +1. [Mail Sender](/src/Infrastructure/Services/Mail/) :mailbox: +1. [Cached Repository](/src/Infrastructure/UnitOfWorks/CachedRepositories/) :computer: +1. [Queue](/src/Infrastructure/Services/Queue/) :walking: +1. [Logging](/src/Api/Extensions/SerialogExtension.cs) :pencil: +1. [Tracing](/src/Api/Extensions/OpenTelemetryExtensions.cs) :chart_with_upwards_trend: +1. [Automatical translatable messages](https://github.com/minhsangdotcom/the-template_shared-kernel) :globe_with_meridians: +1. [S3 AWS](/src/Infrastructure/Services/Aws/) :cloud:
# Nhá hàng cho các tính năng :fire: -### Authentication - -```json -{ - "results": { - "user": { - "firstName": "Chloe", - "lastName": "Kim", - "username": "chloe.kim", - "email": "chloe.kim@gmail.com", - "phoneNumber": "0925123123", - "dayOfBirth": "1990-09-30T17:00:00Z", - "gender": 2, - "province": null, - "district": null, - "commune": null, - "street": "132 Ham Nghi", - "avatar": null, - "status": 1, - "createdBy": "SYSTEM", - "updatedBy": null, - "updatedAt": null, - "id": "01JD936AXSDNMQ713P5XMVRQDV", - "createdAt": "2024-12-31T08:15:50Z" - }, - "tokenType": "Bearer", - "accessTokenExpiredIn": 3600, - "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIwMUpEOTM2QVhTRE5NUTcxM1A1WE1WUlFEViIsImV4cCI6MTczNzYxMjk4NH0.GMrQKpoaHcCHoKgV4WDeDPAZy_IEj7kUjh7PQRwTNG8", - "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmYW1pbHlfaWQiOiJaNmI2M3hQSFUxRUsyVkl5R0YyOGJpWUdNTlh1REFrdiIsInN1YiI6IjAxSkQ5MzZBWFNETk1RNzEzUDVYTVZSUURWIiwiZXhwIjoxNzM3Njk1Nzg0fQ.jZgUpT7hQ0icP7FIp3TUzXfl2I4-O5MWEZ78RlBdCiI" - }, - "statusCode": 200, - "message": "SUCCESS" -} -``` - -### Dynamic search and sort +### API -``` -http://localhost:8080/api/Users?PageSize=2&Search.Keyword=N%E1%BA%B5ng&Search.Targets=province.name&Sort=dayOfBirth -``` +![User Apis](/Screenshots/user-api.png) -![search and sort feature](Screenshots/search-sort.png) +![Role Apis](/Screenshots/role-api.png) -### Cross-cutting concerns +### Truy Vết -![Cross-cutting Concerns feature](Screenshots/crosscutting-concern.png) +![Tracing](/Screenshots/trace.png) ### Lưu trử file media bằng Minio @@ -167,24 +123,18 @@ http://localhost:8080/api/Users?PageSize=2&Search.Keyword=N%E1%BA%B5ng&Search.Ta ```json { - "type": "BadRequestException", - "trace": { - "traceId": "a8ad0670028620121f51850ce5b6cab5", - "spanId": "fbf21a1849fdadac" + "type": "BadRequestError", + "title": "Error has occured with password", + "status": 400, + "instance": "POST /api/v1/Users/Login", + "ErrorDetail": { + "message": "user_password_incorrect", + "en": "Password of user is incorrect", + "vi": "Mật khẩu của Người dùng không đúng" }, - "errors": [ - { - "reasons": [ - { - "message": "user_password_incorrect", - "en": "Password of user is incorrect", - "vi": "Mật khẩu của Người dùng không đúng" - } - ] - } - ], - "statusCode": 400, - "message": "One or several errors have occured" + "requestId": "0HNC1ERHD53E2:00000001", + "traceId": "fa7b365b49f1b554a9cfabd978d858c8", + "spanId": "8623dbe038a6dede" } ``` @@ -192,117 +142,104 @@ http://localhost:8080/api/Users?PageSize=2&Search.Keyword=N%E1%BA%B5ng&Search.Ta # Sơ lượt về Cấu trúc :mag_right: -**_Domain_**: Tầng Domain đóng vai trò như phần trung tâm trong Clean Architecture, các thành phần bao gồm: - -- Aggregates : Là nhóm các entity có mối liên quan với nhau, các value object, enum, interface và Specification pattern (tùy chọn) các bạn có thể đọc thêm ở [https://github.com/ardalis/Specification](https://github.com/ardalis/Specification). Nó có một số nguyên tắc bắt buộc trong quá trình tương tác giữa các root và các thành phần quan hệ của nó và còn nhiều thức khác. -- Exceptions : Tạo ra custom exception cho tầng Domain - - 📁 Domain\ - ├── 📁 Aggregates\ -    ├── 📁 AuditLogs\ -    ├── 📁 Regions\ -    ├── 📁 Roles\ -    ├── 📁 Users\ - ├── 📁 Common\ -    ├── 📁 ElasticConfigurations\ -    ├── 📁 Specs\ - ├── 📁 Exceptions - -_Nó không hề phụ thuộc vào bất kể layer nào_ - -**_Application_**: Tầng ứng dụng đóng vai trò quang trọng trong clean architecture, Nó chứa các business logic, business rule cho ứng dụng và có các thành phần cấu thành như sau: - -- Thư mục Common: - - Behaviors : Nơi chứa các cross-cutting concern có thể kể đến như : error logging, validation, performance logging... - - DomainEventHandler: Nơi implemnet các logic cho gửi event nội bộ. - - Exceptions: Chứa các exception cho tầng Application. - - Interfaces: Tạo ra các interfaces cho repositories và các services bên ngoài. - - Mapping: Chứa các mapping object. -- Thư mục Features: Gom nhóm các modules với command/queries sử dụng CQRS pattern và MediaR - - - Common : Đây là nơi mà mình đặt những thứ chung của tất cả các module lại với nhau để dễ dàng cho việc tái sử dụng như là Mapping với Automapper, Request, Response - - 📁 Application\ - ├── 📁 Common\ -    ├── 📁 Auth\ -    ├── 📁 Behaviors\ -    ├── 📁 DomainEventHandler\ -    ├── 📁 Exceptions\ -    ├── 📁 Interface\ -      ├── 📁 Registers\ -      ├── 📁 Services\ -      ├── 📁 UnitofWorks\ -    ├── 📁 Mapping\ -    ├── 📁 QueryStringProcessing\ -    ├── 📁 Security\ - ├── 📁 Features\ -    ├── 📁 AuditLogs\ -      ├── 📁 Commands\ -      ├── 📁 Queries\ -    ├── 📁 Common\ -      ├── 📁 Mapping\ -      ├── 📁 Projections\ -      ├── 📁 Validators\ -    ├── 📁 Permissions\ -      ├── 📁 Commands\ -      ├── 📁 Queries\ -    ├── 📁 Regions\ -      ├── 📁 Commands\ -      ├── 📁 Queries\ -    ├── 📁 Roles\ -      ├── 📁 Commands\ -      ├── 📁 Queries\ -    ├── 📁 Users\ -      ├── 📁 Commands\ -      ├── 📁 Queries\ - -_Chỉ phụ thuộc vào tầng Domain_ - -**_Infrastucture_** : Tầng Infrastucture là nơi chứa các kết nối với database và các server bên thứ 3, nó có chứa một số thành phần sau đây: - -- Thư mục Data: - - Configurations : Chứa các tùy chỉnh cho các entity ở tầng Domain. - - Interceptors : Nơi chứa các hành động trước và sau khi EF Core thực hiện lưu các thay đổi - - Migrations: Chứa các file migration cho các tiếp cận bằng code first trong EF. -- Services : Nơi implement các interface ở tầng Application. -- UnitOfWorks: Nơi implement các repository interface ở tầng Application. - - 📁 Infrastructure\ - ├── 📁 Constants\ - ├── 📁 Data\ -    ├── 📁 Configurations\ -      ├── 📁 Identity\ -      ├── 📁 Regions\ -      ├── :page_facing_up: AuditLogConfiguration.cs\ -      ├── :page_facing_up: DeadLetterQueueConfiguration.cs\ -    ├── 📁 Interceptors\ -    ├── 📁 Migrations\ -    ├── 📁 Seeds\ -    ├── :page_facing_up: DatabaseSettings.cs\ -    ├── :page_facing_up: DbInitializer.cs\ -    ├── :page_facing_up: DesignTimeDbContextFactory.cs\ -    ├── :page_facing_up: RegionDataSeeding.cs\ -    ├── :page_facing_up: TheDbContext.cs\ -    ├── :page_facing_up: ValidateDatabaseSetting.cs\ - ├── 📁 Services\ - ├── 📁 UnitofWork\ - -_Phụ thuộc vào tầng Application và Domain_ - -**_Api_**: Chứa các Api endpoint. - - 📁 Api\ - ├── 📁 Converters\ - ├── 📁 Endpoints\ - ├── 📁 Extensions\ - ├── 📁 Middlewares\ - ├── 📁 Resources\ - ├── 📁 Settings\ - ├── 📁 wwwroot\ - -_Phụ thuộc vào tầng Application and Infrastructure_ - -**_Contract_** : Chứa shared components cho các tầng Application, Infrastructure and API. +``` +/Domain + ├── /Aggregates/ # Domain aggregates (entities with business rules) + └── /Common/ # Shared domain logic and base types + ├── AggregateRoot.cs # Base class for aggregate roots + ├── BaseEntity.cs # Base class for entities + └── UlidToStringConverter.cs # Value converter for ULIDs +``` + +``` +/Application + ├── /Common + │ ├── /Auth/ # custom authorization & policies in .NET Core + │ ├── /Behaviors/ # MediatR pipeline behaviors (CQRS cross‑cutting) + │ ├── /DomainEventHandlers/ # handlers for raising/domain events + │ ├── /Errors/ # error types for Result‑pattern responses + │ ├── /Exceptions/ # domain/application exception definitions + │ ├── /Extensions/ # helper methods (pagination, LHS parsing, etc.) + │ ├── /Interfaces/ # application‑level contracts & abstractions + │ ├── /QueryStringProcessing/ # validation logic for query‑string params + │ └── /Security/ # security attributes (e.g. [Authorize], roles) + ├── /Features/ # CQRS + MediatR pattern modules + │ ├── AuditLogs/ # commands & queries for audit‑trail + │ ├── Common/ # shared feature utilities + │ ├── Permissions/ # manage app permissions + │ ├── QueueLogs/ # logging for background/queued jobs + │ ├── Regions/ # region‑related commands & queries + │ ├── Roles/ # role management (CRUD, assignments) + │ └── Users/ # user‑centric commands & queries + └── DependencyInjection.cs # Registration of all Application services into DI + +``` + +``` +/Infrastructure + ├── /Constants/ # application-wide constants & credential definitions + │ └── Credential.cs # strongly-typed credentials (keys, secrets, etc.) + │ + ├── /Data/ # EF Core data layer: context, migrations, seeding, configs + │ ├── /Configurations/ # IEntityTypeConfiguration<> implementations + │ ├── /Interceptors/ # DbCommand/SaveChanges interceptors (logging, auditing) + │ ├── /Migrations/ # EF Core migration files + │ ├── /Seeds/ # seed-data providers for initial data + │ ├── DatabaseSettings.cs # POCO for database connection/settings + │ ├── DbInitializer.cs # ensures DB is created & seeded on startup + │ ├── DesignTimeDbContextFactory.cs # design-time factory for `dotnet ef` commands + │ ├── RegionDataSeeding.cs # specific seed logic for Regions table + │ ├── TheDbContext.cs # your `DbContext` implementation + │ └── ValidateDatabaseSetting.cs # runtime validation of DB settings + │ + ├── /Services/ # external/infrastructure services & integrations + │ ├── /Aws/ # AWS SDK wrappers (S3, SNS, etc.) + │ ├── /Cache/ # caching implementations (Redis, MemoryCache) + │ ├── /ElasticSearch/ # Elasticsearch client & indexing/search logic + │ ├── /Hangfire/ # background-job scheduler configuration + │ ├── /Identity/ # identity provider integrations (JWT, OAuth) + │ ├── /Mail/ # SMTP, SendGrid, or other mail-sending services + │ ├── /Queue/ # Request queueing with Redis + │ ├── /Token/ # token-related services and helpers + │ ├── ActionAccessorService.cs # grabs current `HttpContext` action info + │ └── CurrentUserService.cs # resolves authenticated user details + │ + ├── /UnitOfWorks/ # Unit-of-Work & repository abstractions + │ ├── /CachedRepositories/ # repositories with built-in caching layers + │ ├── /Repositories/ # concrete repository implementations + │ ├── RepositoryExtension.cs # extension methods for IRepository + │ └── UnitOfWork.cs # coordinates multiple repository commits + │ + └── DependencyInjection.cs # registration of all Infrastructure services into DI +``` + +``` +/Api + ├── /common/ # shared helpers, configurations for API layer + │ + ├── /Converters/ # JSON/string converters for date types + │ ├── DateTimeConverter.cs # custom converter for System.DateTime + │ └── DateTimeOffsetConverter.cs # custom converter for System.DateTimeOffset + │ + ├── /Endpoints/ # minimal‑API endpoint definitions + │ + ├── /Extensions/ # extension methods (IServiceCollection, HttpContext, etc.) + │ + ├── /Middlewares/ # custom middleware (error handling, logging, auth, etc.) + │ + ├── /Resources/ # static resource files + │ └── /Translations/ # localization .resx files + │ ├── Message.en.resx # English resource strings + │ └── Message.vi.resx # Vietnamese resource strings + │ + ├── /Settings/ # POCOs bound to appsettings.json sections + │ ├── OpenApiSettings.cs # swagger/OpenAPI configuration + │ ├── OpenTelemetrySettings.cs # OTEL exporter/tracing settings + │ └── SerilogSettings.cs # Serilog sink & logging configuration + │ + └── /wwwroot/ # publicly served static content + └── /Templates/ # email/html templates, static assets +``` # Bắt đầu thôi nào @@ -325,33 +262,6 @@ Chỉnh sửa connection string của PostgreSQL (Bởi vì template này đang }, ``` -Nếu các bạn muốn sử dụng các database khác thì chỉ cần chỉnh lại một số đoạn code nhỏ ở DependencyInjection.cs trong Infrastructure. - -```csharp - services.AddDbContextPool( - (sp, options) => - { - NpgsqlDataSource npgsqlDataSource = sp.GetRequiredService(); - options - .UseNpgsql(npgsqlDataSource) - .AddInterceptors( - sp.GetRequiredService(), - sp.GetRequiredService() - ); - } - ); -``` - -Chỉ cần thay thế UseNpgsql với bất kể database nào mà bạn muốn :smile:. - -Sau đó đi tới Data, vào file DesignTimeDbContextFactory - -``` -builder.UseNpgsql(connectionString); -``` - -Thay thế như file DependencyInjection.cs ở trên :point_up_2:. - Bước tiếp theo nha :point_right:: ``` @@ -363,7 +273,7 @@ cd Dockers/MinioS3 ``` MINIO_ROOT_USER=the_template_storage -MINIO_ROOT_PASSWORD=storage@the_template1` +MINIO_ROOT_PASSWORD=storage@the_template1 ``` @@ -374,9 +284,13 @@ docker-compose up -d ``` -Đây là một cách khá hay để sử dụng AWS miễn phí với máy tính của bạn :dollar: Tui đã học được cách này lúc còn ở công ty cũ :pray: +Truy cập http://localhost:9001 và đăng nhập -_Mà nè nếu mấy fen đã có sẳn con AWS rồi thì khỏi cần làm mấy cái này nha_ +![S3 login](/Screenshots/S3-login.png) + +Tạo ra cặp key + +![S3 keys](/Screenshots/create-key-s3.PNG) Chỉnh lại setting ở your appsettings.json @@ -392,8 +306,6 @@ Chỉnh lại setting ở your appsettings.json }, ``` -Các bạn có thể tạo ra cặp access và Secret key bằng giao diện ở [http://localhost:9001](http://localhost:9001) - Bước cuối nha ``` @@ -402,7 +314,7 @@ dotnet run ``` -vào swagger ui ở "localhost:8080/docs" +vào swagger ui ở http://localhost:8080/docs Xong rồi đó :tada: :tada: :tada: :clap: @@ -410,20 +322,22 @@ Xong rồi đó :tada: :tada: :tada: :clap: ### Authorize -Để phân quyền cho nó sử dụng AuthorizeBy nha gắn nó vô trên đầu Endpoint (Controller) +Để phân quyền cho nó sử dụng RequireAuth vào minimal api, +tham số permissions là kiểu string, các quyền được phân tách bởi dấu phẩy. ```csharp - [HttpPost(Router.UserRoute.Users)] - [SwaggerOperation(Tags = [Router.UserRoute.Tags], Summary = "create User")] - [AuthorizeBy(permissions: $"{ActionPermission.create}:{ObjectPermission.user}")] - public override async Task> HandleAsync( - [FromForm] CreateUserCommand request, - CancellationToken cancellationToken = default - ) +app.MapPost(Router.UserRoute.Users, HandleAsync) + .WithOpenApi(operation => new OpenApiOperation(operation) { - CreateUserResponse user = await sender.Send(request, cancellationToken); - return this.Created201(Router.UserRoute.GetRouteName, user.Id, user); - } + Summary = "Create user 🧑", + Description = "Creates a new user and returns the created user details.", + Tags = [new OpenApiTag() { Name = Router.UserRoute.Tags }], + }) + .WithRequestValidation() + .RequireAuth( + permissions: Permission.Generate(PermissionAction.Create, PermissionResource.User) + ) + .DisableAntiforgery(); ``` **_Tạo ra role kèm theo permission_** @@ -447,37 +361,17 @@ Xong rồi đó :tada: :tada: :tada: :clap: ### Thêm một quyền mới vào ứng dụng -Vào thư mục Constants trong Infrastructure mở file Credential.cs và chú ý tới PermissionGroups +Vào thư mục Constants trong Infrastructure mở file Credential.cs và chú ý tới permissions ```csharp - public static readonly Dictionary PermissionGroups = - new() - { - { - nameof(User) + "s", - - [ - CreatePermission(ActionPermission.create, ObjectPermission.user), - CreatePermission(ActionPermission.update, ObjectPermission.user), - CreatePermission(ActionPermission.delete, ObjectPermission.user), - CreatePermission(ActionPermission.list, ObjectPermission.user), - CreatePermission(ActionPermission.detail, ObjectPermission.user), - ] - }, - { - nameof(Role) + "s", - - [ - CreatePermission(ActionPermission.create, ObjectPermission.role), - CreatePermission(ActionPermission.update, ObjectPermission.role), - CreatePermission(ActionPermission.delete, ObjectPermission.role), - CreatePermission(ActionPermission.list, ObjectPermission.role), - CreatePermission(ActionPermission.detail, ObjectPermission.role), - ] - }, -``` - -Chú ý rằng, key là tên của entity cộng thêm "s" và value là danh sách các permission cho entity đó. +public static readonly List>> permissions = + [ + Permission.CreatebasicPermissions(PermissionResource.User), + Permission.CreatebasicPermissions(PermissionResource.Role), + ]; +``` + +Chú ý rằng, key là quyền chính còn value là danh sách quyền liên quan của nó Permission được gộp từ hành động và tên entity. VD: @@ -486,27 +380,28 @@ VD: create:user ``` -Đây là nơi để tạo ra các permission từ lớp ActionPermission và ObjectPermission. +Đây là nơi để tạo ra các PermissionAction từ lớp ActionPermission và PermissionResource. ```csharp -public static class ActionPermission +public class PermissionAction { - public const string create = nameof(create); - public const string update = nameof(update); - public const string delete = nameof(delete); - public const string detail = nameof(detail); - public const string list = nameof(list); - public const string testa = nameof(testa); + public const string Create = nameof(Create); + public const string Update = nameof(Update); + public const string Delete = nameof(Delete); + public const string Detail = nameof(Detail); + public const string List = nameof(List); + public const string Test = nameof(Test); + public const string Testing = nameof(Testing); } -public static class ObjectPermission +public class PermissionResource { - public const string user = nameof(user); - public const string role = nameof(role); + public const string User = nameof(User); + public const string Role = nameof(Role); } ``` -Tạo ra permission mới sau đó thêm nó vào PermissionGroups dictionary và chạy lại ứng dụng. +Tạo ra permission mới sau đó thêm nó vào permission, tắt và chạy lại ứng dụng.
@@ -599,15 +494,10 @@ Các bạn có thể tìm hiểu thêm ỏ một số link sau đây Mình thiết kế input đầu vào dựa trên [Strapi filter](https://docs.strapi.io/dev-docs/api/rest/filters-locale-publication) -Mình đã nhúng sẳn filter tự động vào tất cả các hàm lấy danh sách ở lớp Repository +Mình đã nhúng sẳn filter tự động vào tất cả các hàm lấy danh sách chỉ cần gọi ```csharp - await unitOfWork - .Repository() - .CursorPagedListAsync( - new ListUserSpecification(), - query.ValidateQuery().ValidateFilter(typeof(ListUserResponse)) - ); +unitOfWork.DynamicReadOnlyRepository() ```