Clean Architecture 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 structure 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 β.
4. What is Clean Architecture?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.
What makes this Clean Architecture template stand out from the rest on Github?
{ "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
+-----------------------------------------------+
| Api |
+-----------------------------------------------+
| | |
| | |
β | |
+------------------+ | |
| Infrastructure | | |
+------------------+ | |
| | |
β β β
+--------------------+ +---------------------+
| Application | -> | Contracts |
+--------------------+ +---------------------+
|
β
+---------------------------+
| Domain |
+---------------------------+
8.1. Run .NET Core Clean Architecture Project
The following prerequisites are required to build and run the solution:
The first step βοΈ :
Create a appsettings.Development.json file at root of Api layer and just copy the content of appsettings.example.json to the file then Modify configurations in your case.
Modify PostgreSQL connection string (this template is using PostgreSQL currently).
"DatabaseSettings": { "DatabaseConnection": "Host=localhost;Username=[your_username];Password=[your_password];Database=example" },
Update migrations to your own database.
cd src/Infrastructure
dotnet ef database update
The next step π:
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.
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": "http://localhost:9000", "AccessKey": "***", "SecretKey": "***", "BucketName": "the-template-project", "PublicUrl": "http://localhost:9000", "PreSignedUrlExpirationInMinutes": 1440, "Protocol": 1 },
The final step
http://localhost:8080/docs is swagger UI path
The default admin account username:chloe.kim, password: Admin@123
Congrats! you are all set up π π π π
To Achieve this, let's add RequireAuth on minimal api, permissions parameter is string and Each permission is separated by comma like "create:user,update:user".
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();8.2.2. Create role with permissions:
Json payload is like
{ "description": "this is super admin role", "name": "superAdmin", "roleClaims": [ { "claimType": "permission", "claimValue": "create:customer" }, { "claimType": "permission", "claimValue": "update:customer" } ] }8.2.3. How to add new permissions in your app
To get this, let's navigate to constants folder in Infrastructure layer, then open Credential.cs file and pay your attention on permissions list
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 combines from action and entity name. For example:
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 list 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
GET api/v1/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 withSome Examples:
GET /api/v1/users?filter[gender][$in][0]=1&filter[gender][$in][1]=2
GET /api/v1/users?filter[gender][$between][0]=1&filter[gender][$between][1]=2
GET /api/v1/users?filter[firstName][$contains]=abc
$and and $or operator:
GET /api/v1/users/filter[$and][0][firstName][$containsi]="sa"&filter[$and][1][lastName][$eq]="Tran"
{ "filter": { "$and": { "firstName": "sa", "lastName": "Tran" } } }
GET /api/v1/users/filter[$or][0][$and][0][claims][claimValue][$eq]=admin&filter[$or][1][lastName][$eq]=Tran
{ "filter": { "$or": { "$and":{ "claims": { "claimValue": "admin" } }, "lastName": "Tran" } } }
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": "sang.minh123@gmail.com", "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" }
If you are having problems, please let me know at issue section.
This project is licensed with the MIT license.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4