- 1. Language
- 2. Badges
- 3. Table of Contents
- 2. .NET Core Clean Architecture The Template Introduction
- 3. Give a Star! ⭐
- 4. What is Clean Architecture?
- 5. Features 🚀
- 6. Demo 🔥
- 7. Structure Overview 🔎
- 8. Getting started
- 9. Technology
- 10. Support
- 11. Credits
- 12. Licence
This template is designed for backend developer working with ASP.NET Core. It provides you an efficient way to build enterprise applications effortlessly by leveraging advantages of clean architecture structre and .NET Core framework.
With this template, everything is already set up 😃.
If you find this template helpful and learn something from it, please consider giving it a ⭐.
Clean Architecture is a software design approach introduced by Robert C. Martin (Uncle Bob) that emphasizes the separation of concerns by organizing code into concentric layers. The core idea is to keep business logic independent from external frameworks, databases, and user interfaces, promoting a system that's easier to maintain, test, and evolve over time.
- Separation of Concerns: Each layer is responsible for a specific aspect of the application, making the code easier to understand and maintain.
- Testability: Since business logic is decoupled from frameworks and UI, unit testing becomes simpler and more reliable.
- Flexibility and Adaptability: Changes to the framework, database, or external systems have minimal impact on the core logic.
- Reusability: Business rules can be reused across different applications or systems with minimal changes.
- Scalability: The clear structure supports growth and the addition of new features without significant refactoring.
- Framework Independence: Avoids being locked into a specific framework, making it easier to migrate to newer technologies.
- Complexity: The layered structure can add complexity, especially for smaller projects where simpler architectures might suffice.
- Initial Overhead: Setting up Clean Architecture requires additional effort to organize layers and follow strict design principles.
- Learning Curve: Developers unfamiliar with the principles may take time to grasp the structure and its benefits.
- Over-Engineering Risk: For small-scale applications, the additional layers might be unnecessary and lead to over-complication.
- Performance Overhead: The abstraction and indirection between layers can introduce slight performance trade-offs, though typically negligible.
What makes this Clean Architecture template stand out from the rest?
- Login 🔐
- Refresh token 🔄
- Changing user password 🔁
- Password reset 🔓
- Retrieving and Updating user profile 👲
- User CRUD 👪
- Role CRUD 🛡️
- DDD (Domain Driven Design) 🧠
- CQRS & Mediator 🔀
- Cross-cutting concern ✂️
- Mail Sender 📫
- Cached Repository 💻
- Queue 🚶
- Logging 📝
- Tracing 📈
- Automatical translatable messages 🌐
- S3 AWS ☁️
{
"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"
},
"requestId": "0HNC1ERHD53E2:00000001",
"traceId": "fa7b365b49f1b554a9cfabd978d858c8",
"spanId": "8623dbe038a6dede"
}
/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<T>
│ └── 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
The following prerequisites are required to build and run the solution:
The first step ☝️ :
we're going to copy content of appsettings.example.json to your own appsettings.json.
Modify PostgreSQL connection string (this template is using PostgreSQL currently).
"DatabaseSettings": {
"DatabaseConnection": "Host=localhost;Username=[your_username];Password=[your_password];Database=example"
},
The next step 👉:
cd Dockers/MinioS3
change mino username and password at .env if needed and you're gonna use it for logging in Web UI Manager
MINIO_ROOT_USER=the_template_storage
MINIO_ROOT_PASSWORD=storage@the_template1
To Run Amazon S3 service for media file storage.
docker-compose up -d
Access Minio S3 Web UI at http://localhost:9001 and login
Create a pairs of key like
input the keys at your appsettings.json
"S3AwsSettings": {
"ServiceUrl": "[your_host]:9000",
"AccessKey": "[yours]",
"SecretKey": "[yours]",
"BucketName": "the-template-project",
"PublicUrl": "[your_host]:9000",
"PreSignedUrlExpirationInMinutes": 1440,
"Protocol": 1
},
The final step
cd src/Api
dotnet run
http://localhost:8080/docs is swagger UI path
Congrat! you are all set up 🎉 🎉 🎉 👏
To Achieve this, let's add RequireAuth on minimal api, permissions parameter is string and seperate each permission by comma "create:user,update:use".
app.MapPost(Router.UserRoute.Users, HandleAsync)
.WithOpenApi(operation => new OpenApiOperation(operation)
{
Summary = "Create user 🧑",
Description = "Creates a new user and returns the created user details.",
Tags = [new OpenApiTag() { Name = Router.UserRoute.Tags }],
})
.WithRequestValidation<CreateUserCommand>()
.RequireAuth(
permissions: Permission.Generate(PermissionAction.Create, PermissionResource.User)
)
.DisableAntiforgery();
Json payload is like
{
"description": "this is super admin role",
"name": "superAdmin",
"roleClaims": [
{
"claimType": "permission",
"claimValue": "create:customer"
},
{
"claimType": "permission",
"claimValue": "update:customer"
}
]
}
To get this, let's navigate to constants folder in Infrastructure layer, then open Credential.cs file and pay your attention on permissions dictionary
public static readonly List<Dictionary<string, List<string>>> permissions =
[
Permission.CreatebasicPermissions(PermissionResource.User),
Permission.CreatebasicPermissions(PermissionResource.Role),
];
Notice that, the key is primary permission and value is list of relative permissions
Permission combibes from action and entity name. For example:
create:user
Let's take a look at PermissionAction and PermissionResource class
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 Test = nameof(Test);
public const string Testing = nameof(Testing);
}
public class PermissionResource
{
public const string User = nameof(User);
public const string Role = nameof(Role);
}
Define your new one at permissions dictionary then stop and start application again
To do filter in this template, we use LHS Brackets.
LHS is the way to encode operators is the use of square brackets [] on the key name.
For example
/users?filter[dayOfBirth][$gt]="1990-10-01"
This example indicates filtering out users whose birthdays are after 1990/10/01
All support operations:
Operator | Description |
---|---|
$eq | Equal |
$eqi | Equal (case-insensitive) |
$ne | Not equal |
$nei | Not equal (case-insensitive) |
$in | Included in an array |
$notin | Not included in an array |
$lt | Less than |
$lte | Less than or equal to |
$gt | Greater than |
$gte | Greater than or equal to |
$between | Is between |
$notcontains | Does not contain |
$notcontainsi | Does not contain (case-insensitive) |
$contains | Contains |
$containsi | Contains (case-insensitive) |
$startswith | Starts with |
$endswith | Ends with |
Some Examples:
GET /api/user?filter[gender][$in][0]=1&filter[gender][$in][1]=2
GET /api/user?filter[gender][$between][0]=1&filter[gender][$between][1]=2
GET /api/user?filter[firstName][$contains]=abc
$and and $or operator:
/api/users/filter[$and][0][firstName][$containsi]="sa"&filter[$and][1][lastName][$eq]="Tran"
"filter":{
"$and": {
"firstName":"ng",
"lastName":"Tran"
}
}
/api/users/filter[$or][0][$and][0][claims][claimValue][$eq]=admin
"filter":{
"$or": {
"$and":{
"claims": : {
"claimValue": "admin"
}
}
}
}
For more examples and get better understand, you can visit
https://docs.strapi.io/dev-docs/api/rest/filters-locale-publication#filtering
https://docs.strapi.io/dev-docs/api/rest/filters-locale-publication#complex-filtering
https://docs.strapi.io/dev-docs/api/rest/filters-locale-publication#deep-filtering
'Cause I designed filter input based on Strapi filter
To Apply dynamic filter, you just call any list method at
unitOfWork.DynamicReadOnlyRepository<User>()
This template supports offset pagination and cursor pagination.
To Enable offset pagination just add this line
var response = await unitOfWork
.DynamicReadOnlyRepository<User>(true)
.PagedListAsync(
new ListUserSpecification(),
query,
ListUserMapping.Selector(),
cancellationToken: cancellationToken
);
To Enable cursor pagination just add this line
var response = await unitOfWork
.DynamicReadOnlyRepository<User>(true)
.CursorPagedListAsync(
new ListUserSpecification(),
query,
ListUserMapping.Selector(),
cancellationToken: cancellationToken
);
{
"results": {
"data": [
{
"firstName": "sang",
"lastName": "minh",
"username": "sang.minh123",
"email": "[email protected]",
"phoneNumber": "0925123320",
"dayOfBirth": "1990-01-09T17:00:00Z",
"gender": 2,
"address": "abcdef,Xã Phước Vĩnh An,Huyện Củ Chi,Thành phố Hồ Chí Minh",
"avatar": null,
"status": 1,
"createdBy": "01JD936AXSDNMQ713P5XMVRQDV",
"updatedBy": "01JD936AXSDNMQ713P5XMVRQDV",
"updatedAt": "2025-04-16T14:26:01Z",
"id": "01JRZFDA1F7ZV4P7CFS5WSHW8A",
"createdAt": "2025-04-16T14:17:54Z"
}
],
"paging": {
"pageSize": 1,
"totalPage": 3,
"hasNextPage": true,
"hasPreviousPage": false,
"before": null,
"after": "q+blUlBQci5KTSxJTXEsUbJSUDIyMDLVNTDRNTQLMTK0MjS3MjXRMzG3tDAx1DYwtzIwUNIB6/FMASk2MPQKinJzcTR0M48KMwkwd3YLNg0P9gi3cFTi5aoFAA=="
}
},
"status": 200,
"message": "Success"
}
- .NET 8
- EntityFramework core 8
- PostgreSQL
- Fluent validation
- Mediator
- XUnit, Shouldly, Respawn
- OpenTelemetry
- Serilog
- Redis
- ElasticSearch
- Docker
- Github Workflow
If you are having problems, please let me know by issue section.
❤️ Thank you guys so much ❤️ 🙏.
-
Clean architecture by Jayson Taylor
A fantastic guide to structuring projects with clean architecture principles, which helped shape the design of this template. -
Clean architecture by amantinband
A concise and practical implementation of clean architecture that provided fresh perspectives and further deepened my understanding of this powerful approach. -
Clean architecture by Ardalis
A thoughtful and thorough take on clean architecture that helped refine the modular structure and scalability of this template. -
Specification pattern
A brilliant implementation of the Specification pattern, simplifying complex query logic and promoting cleaner, more maintainable code. -
REPR Pattern
A practical implementation of the Request-Endpoint-Response (REPR) pattern, which provided a clear and structured approach to organizing API endpoints. It emphasizes simplicity and single-responsibility for each endpoint, ensuring clarity and maintainability. -
Clean testing by Jayson Taylor
An inspiring repository of testing best practices, showcasing how to write robust and meaningful tests to ensure the reliability of clean architecture-based projects.
This project is licensed with the MIT license.