Practical.CleanArchitecture
Full-stack .Net 8 Clean Architecture (Microservices, Modular Monolith, Monolith), Blazor, Angular 18, React 18, Vue 3, BFF with YARP, Domain-Driven Design, CQRS, SOLID, Asp.Net Core Identity Custom Storage, OpenID Connect, Entity Framework Core, OpenTelemetry, SignalR, Hosted Services, Health Checks, Rate Limiting, Cloud Services (Azure, AWS, GCP).
Top Related Projects
Clean Architecture Solution Template: A starting point for Clean Architecture with ASP.NET Core
:cyclone: Clean Architecture with .NET6, C#10 and React+Redux. Use cases as central organizing structure, completely testable, decoupled from frameworks
Web Application ASP.NET 8 using Clean Architecture, DDD, CQRS, Event Sourcing and a lot of good practices
Cross-platform .NET sample microservices and container based application that runs on Linux Windows and macOS. Powered by .NET 7, Docker Containers and Azure Kubernetes Services. Supports Visual Studio, VS for Mac and CLI based environments with Docker CLI, dotnet CLI, VS Code or any other code editor. Moved to https://github.com/dotnet/eShop.
Full Modular Monolith application with Domain-Driven Design approach.
Quick Overview
Practical.CleanArchitecture is a comprehensive example of implementing Clean Architecture principles in a .NET Core application. It demonstrates how to structure a large-scale application with separation of concerns, maintainability, and testability in mind, showcasing best practices for modern .NET development.
Pros
- Provides a real-world example of Clean Architecture implementation
- Includes multiple projects demonstrating various aspects of the architecture
- Incorporates modern .NET technologies and practices
- Well-organized codebase with clear separation of concerns
Cons
- May be overwhelming for beginners due to its complexity
- Requires a solid understanding of Clean Architecture principles
- Some implementations might be opinionated and not suit all project needs
- Limited documentation for explaining architectural decisions
Code Examples
- Domain Entity Example:
public class Product : EntityBase, IAggregateRoot
{
public string Name { get; set; }
public string Code { get; set; }
public string Description { get; set; }
public ProductStatus Status { get; set; }
public bool IsDeleted { get; set; }
}
This code defines a Product entity in the domain layer, implementing core business logic.
- Application Service Example:
public class ProductService : IProductService
{
private readonly IProductRepository _productRepository;
public ProductService(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public async Task<ProductDto> GetProductAsync(Guid id)
{
var product = await _productRepository.GetByIdAsync(id);
return product.ToDto();
}
}
This example shows an application service implementing business use cases and orchestrating domain objects.
- API Controller Example:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
{
_productService = productService;
}
[HttpGet("{id}")]
public async Task<ActionResult<ProductDto>> GetProduct(Guid id)
{
var product = await _productService.GetProductAsync(id);
return Ok(product);
}
}
This code demonstrates an API controller in the presentation layer, handling HTTP requests and responses.
Getting Started
-
Clone the repository:
git clone https://github.com/phongnguyend/Practical.CleanArchitecture.git
-
Open the solution in Visual Studio or your preferred IDE.
-
Restore NuGet packages and build the solution.
-
Set up the database connection string in the
appsettings.json
file. -
Run database migrations:
dotnet ef database update
-
Run the application and explore the implemented Clean Architecture patterns.
Competitor Comparisons
Clean Architecture Solution Template: A starting point for Clean Architecture with ASP.NET Core
Pros of CleanArchitecture
- More comprehensive documentation and explanations of clean architecture principles
- Includes a sample API project, showcasing how to implement clean architecture in web services
- Provides a more opinionated structure, which can be beneficial for developers new to clean architecture
Cons of CleanArchitecture
- Less flexibility in project structure compared to Practical.CleanArchitecture
- Fewer real-world examples and use cases demonstrated
- Limited coverage of additional architectural patterns like CQRS
Code Comparison
CleanArchitecture:
public class DeleteTodoItemCommand : IRequest
{
public int Id { get; set; }
}
public class DeleteTodoItemCommandHandler : IRequestHandler<DeleteTodoItemCommand>
{
private readonly IApplicationDbContext _context;
public DeleteTodoItemCommandHandler(IApplicationDbContext context)
{
_context = context;
}
}
Practical.CleanArchitecture:
public class DeleteProductCommand : IRequest
{
public Guid Id { get; set; }
}
public class DeleteProductCommandHandler : IRequestHandler<DeleteProductCommand>
{
private readonly IProductRepository _productRepository;
public DeleteProductCommandHandler(IProductRepository productRepository)
{
_productRepository = productRepository;
}
}
Both repositories demonstrate similar command handling structures, but Practical.CleanArchitecture uses a repository pattern, while CleanArchitecture directly injects the database context.
:cyclone: Clean Architecture with .NET6, C#10 and React+Redux. Use cases as central organizing structure, completely testable, decoupled from frameworks
Pros of clean-architecture-manga
- More comprehensive documentation, including detailed explanations of clean architecture principles
- Includes a fully implemented example application (Manga) demonstrating real-world usage
- Provides Docker support for easier deployment and testing
Cons of clean-architecture-manga
- More complex project structure, which may be overwhelming for beginners
- Focuses primarily on a single example application, potentially limiting its applicability to diverse scenarios
- Heavier reliance on specific technologies and frameworks, potentially reducing flexibility
Code Comparison
clean-architecture-manga:
public sealed class RegisterCustomerUseCase : IRegisterCustomerUseCase
{
private readonly ICustomerRepository _customerRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly ICustomerFactory _customerFactory;
}
Practical.CleanArchitecture:
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, Result<ProductDto>>
{
private readonly IProductRepository _productRepository;
private readonly IMapper _mapper;
}
Both repositories implement clean architecture principles, but clean-architecture-manga tends to use more explicit naming conventions and a more granular approach to dependency injection. Practical.CleanArchitecture, on the other hand, appears to favor a slightly more concise style and makes use of the Mediator pattern for handling commands.
Web Application ASP.NET 8 using Clean Architecture, DDD, CQRS, Event Sourcing and a lot of good practices
Pros of EquinoxProject
- More comprehensive documentation and wiki
- Includes a sample SPA application using Angular
- Implements Event Sourcing and CQRS patterns
Cons of EquinoxProject
- Less frequent updates and maintenance
- More complex architecture, potentially steeper learning curve
- Fewer examples of different technology implementations
Code Comparison
EquinoxProject (Domain Event):
public class CustomerRegisteredEvent : Event
{
public CustomerRegisteredEvent(Guid id, string name, string email, DateTime birthDate)
{
Id = id;
Name = name;
Email = email;
BirthDate = birthDate;
}
// Properties...
}
Practical.CleanArchitecture (Domain Event):
public class ProductCreated : IDomainEvent
{
public Product Product { get; }
public ProductCreated(Product product)
{
Product = product;
}
}
Both projects implement Clean Architecture principles, but EquinoxProject offers a more feature-rich implementation with additional patterns like Event Sourcing and CQRS. It also provides a sample SPA application, which can be beneficial for developers looking to implement a full-stack solution.
Practical.CleanArchitecture, on the other hand, offers a more straightforward approach to Clean Architecture, making it potentially easier to understand and adapt for simpler projects. It also appears to have more recent updates and maintenance.
The code comparison shows different approaches to implementing domain events, with EquinoxProject using a more detailed event structure and Practical.CleanArchitecture opting for a simpler approach.
Cross-platform .NET sample microservices and container based application that runs on Linux Windows and macOS. Powered by .NET 7, Docker Containers and Azure Kubernetes Services. Supports Visual Studio, VS for Mac and CLI based environments with Docker CLI, dotnet CLI, VS Code or any other code editor. Moved to https://github.com/dotnet/eShop.
Pros of eShopOnContainers
- More comprehensive, showcasing a full-fledged microservices architecture
- Includes containerization and orchestration with Docker and Kubernetes
- Demonstrates integration with cloud services (Azure) and various technologies
Cons of eShopOnContainers
- Higher complexity, potentially overwhelming for beginners
- Larger codebase, which may be harder to navigate and understand
- More opinionated in terms of technology choices and architecture
Code Comparison
eShopOnContainers (API Controller):
[Route("api/v1/[controller]")]
[Authorize]
[ApiController]
public class BasketController : ControllerBase
{
private readonly IBasketRepository _repository;
// ...
}
Practical.CleanArchitecture (API Controller):
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
// ...
}
Both repositories follow clean architecture principles, but eShopOnContainers is more focused on microservices and cloud-native development, while Practical.CleanArchitecture provides a simpler, more straightforward implementation of clean architecture. eShopOnContainers is better suited for large-scale, distributed applications, while Practical.CleanArchitecture may be more appropriate for smaller projects or as a learning resource for clean architecture concepts.
Full Modular Monolith application with Domain-Driven Design approach.
Pros of modular-monolith-with-ddd
- More comprehensive implementation of Domain-Driven Design (DDD) principles
- Includes detailed documentation and architectural decision records (ADRs)
- Demonstrates advanced patterns like CQRS and Event Sourcing
Cons of modular-monolith-with-ddd
- Higher complexity, potentially steeper learning curve for beginners
- Less focus on practical, day-to-day application scenarios
- Primarily C# focused, with less cross-language applicability
Code Comparison
modular-monolith-with-ddd:
public class MeetingGroup : Entity, IAggregateRoot
{
public MeetingGroupId Id { get; private set; }
public string Name { get; private set; }
public string Description { get; private set; }
public MeetingGroupLocation Location { get; private set; }
// ...
}
Practical.CleanArchitecture:
public class Product : BaseEntity
{
public string Code { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal UnitPrice { get; set; }
// ...
}
The modular-monolith-with-ddd example shows a more complex domain model with value objects and encapsulation, while Practical.CleanArchitecture demonstrates a simpler, more straightforward approach to entity modeling.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
:warning: Warning
The code samples contain multiple ways and patterns to do things and not always be considered best practices or recommended for all situations.
Database Centric vs Domain Centric Architecture
Hexagonal Architecture
Onion Architecture
The Clean Architecture
Classic Three-layer Architecture
Modern Four-layer Architecture
Layer Dependencies
Layer Examples
Testing Pyramid
Vertical Slice Architecture (Modular Monolith)
Solution Structure
How to Run:
Update Configuration
Database
-
Update Connection Strings:
Project Configuration File Configuration Key ClassifiedAds.Migrator appsettings.json ConnectionStrings:ClassifiedAds ClassifiedAds.BackgroundServer appsettings.json ConnectionStrings:ClassifiedAds ClassifiedAds.IdentityServer appsettings.json ConnectionStrings:ClassifiedAds ClassifiedAds.WebAPI appsettings.json ConnectionStrings:ClassifiedAds ClassifiedAds.WebMVC appsettings.json ConnectionStrings:ClassifiedAds -
Run Migration:
- Option 1: Using dotnet cli:
- Install dotnet-ef cli:
dotnet tool install --global dotnet-ef --version="5.0"
- Navigate to ClassifiedAds.Migrator and run these commands:
dotnet ef migrations add Init --context AdsDbContext -o Migrations/AdsDb dotnet ef migrations add Init --context ConfigurationDbContext -o Migrations/ConfigurationDb dotnet ef migrations add Init --context PersistedGrantDbContext -o Migrations/PersistedGrantDb dotnet ef database update --context AdsDbContext dotnet ef database update --context ConfigurationDbContext dotnet ef database update --context PersistedGrantDbContext
- Install dotnet-ef cli:
- Option 2: Using Package Manager Console:
- Set ClassifiedAds.Migrator as StartUp Project
- Open Package Manager Console, select ClassifiedAds.Migrator as Default Project
- Run these commands:
Add-Migration -Context AdsDbContext Init -OutputDir Migrations/AdsDb Add-Migration -Context ConfigurationDbContext Init -OutputDir Migrations/ConfigurationDb Add-Migration -Context PersistedGrantDbContext Init -OutputDir Migrations/PersistedGrantDb Update-Database -Context AdsDbContext Update-Database -Context ConfigurationDbContext Update-Database -Context PersistedGrantDbContext
- Option 1: Using dotnet cli:
Additional Configuration Sources
-
Open ClassifiedAds.WebMVC/appsettings.json and jump to ConfigurationSources section.
"ConfigurationSources": { "SqlServer": { "IsEnabled": false, "ConnectionString": "Server=127.0.0.1;Database=ClassifiedAds;User Id=sa;Password=sqladmin123!@#", "SqlQuery": "select [Key], [Value] from ConfigurationEntries" }, "AzureKeyVault": { "IsEnabled": false, "VaultName": "https://xxx.vault.azure.net/" } },
-
Get from Sql Server database:
"ConfigurationSources": { "SqlServer": { "IsEnabled": true, "ConnectionString": "Server=127.0.0.1;Database=ClassifiedAds;User Id=sa;Password=sqladmin123!@#", "SqlQuery": "select [Key], [Value] from ConfigurationEntries" }, },
-
Get from Azure Key Vault:
"ConfigurationSources": { "AzureKeyVault": { "IsEnabled": true, "VaultName": "https://xxx.vault.azure.net/" } },
-
Use Both:
"ConfigurationSources": { "SqlServer": { "IsEnabled": true, "ConnectionString": "Server=127.0.0.1;Database=ClassifiedAds;User Id=sa;Password=sqladmin123!@#", "SqlQuery": "select [Key], [Value] from ConfigurationEntries" }, "AzureKeyVault": { "IsEnabled": true, "VaultName": "https://xxx.vault.azure.net/" } },
Storage
-
Open ClassifiedAds.WebMVC/appsettings.json, ClassifiedAds.WebAPI/appsettings.json and jump to Storage section.
"Storage": { "Provider": "Local", },
-
Use Local Files:
"Storage": { "Provider": "Local", "Local": { "Path": "E:\\files" }, },
-
Use Azure Blob:
"Storage": { "Provider": "Azure", "Azure": { "ConnectionString": "xxx", "Container": "classifiedadds" }, },
-
Use Amazon S3:
"Storage": { "Provider": "Amazon", "Amazon": { "AccessKeyID": "xxx", "SecretAccessKey": "xxx", "BucketName": "classifiedadds", "RegionEndpoint": "ap-southeast-1" } },
Message Broker
-
Open below files and jump to MessageBroker section:
- ClassifiedAds.IdentityServer/appsettings.json
- ClassifiedAds.WebMVC/appsettings.json
- ClassifiedAds.WebAPI/appsettings.json
- ClassifiedAds.BackgroundServer/appsettings.json
"MessageBroker": { "Provider": "RabbitMQ", }
-
Use RabbitMQ
"MessageBroker": { "Provider": "RabbitMQ", "RabbitMQ": { "HostName": "localhost", "UserName": "guest", "Password": "guest", "ExchangeName": "amq.direct", "RoutingKeys": { "FileUploadedEvent": "classifiedadds_fileuploaded", "FileDeletedEvent": "classifiedadds_filedeleted", "EmailMessageCreatedEvent": "classifiedadds_emailcreated", "SmsMessageCreatedEvent": "classifiedadds_smscreated" }, "QueueNames": { "FileUploadedEvent": "classifiedadds_fileuploaded", "FileDeletedEvent": "classifiedadds_filedeleted", "EmailMessageCreatedEvent": "classifiedadds_emailcreated", "SmsMessageCreatedEvent": "classifiedadds_smscreated" } } }
-
Use Kafka:
"MessageBroker": { "Provider": "Kafka", "Kafka": { "BootstrapServers": "localhost:9092", "Topics": { "FileUploadedEvent": "classifiedadds_fileuploaded", "FileDeletedEvent": "classifiedadds_filedeleted", "EmailMessageCreatedEvent": "classifiedadds_emailcreated", "SmsMessageCreatedEvent": "classifiedadds_smscreated" }, } }
-
Use Azure Queue Storage:
"MessageBroker": { "Provider": "AzureQueue", "AzureQueue": { "ConnectionString": "xxx", "QueueNames": { "FileUploadedEvent": "classifiedadds-fileuploaded", "FileDeletedEvent": "classifiedadds-filedeleted", "EmailMessageCreatedEvent": "classifiedadds-emailcreated", "SmsMessageCreatedEvent": "classifiedadds-smscreated" } } }
-
Use Azure Service Bus:
"MessageBroker": { "Provider": "AzureServiceBus", "AzureServiceBus": { "ConnectionString": "xxx", "QueueNames": { "FileUploadedEvent": "classifiedadds_fileuploaded", "FileDeletedEvent": "classifiedadds_filedeleted", "EmailMessageCreatedEvent": "classifiedadds_emailcreated", "SmsMessageCreatedEvent": "classifiedadds_smscreated" } } }
-
Use Azure Event Grid:
"MessageBroker": { "Provider": "AzureEventGrid", "AzureEventGrid": { "DomainEndpoint": "https://xxx.xxx-1.eventgrid.azure.net/api/events", "DomainKey": "xxxx", "Topics": { "FileUploadedEvent": "classifiedadds_fileuploaded", "FileDeletedEvent": "classifiedadds_filedeleted" "EmailMessageCreatedEvent": "classifiedadds_emailcreated", "SmsMessageCreatedEvent": "classifiedadds_smscreated" } } }
-
Use Azure Event Hubs:
"MessageBroker": { "Provider": "AzureEventHub", "AzureEventHub": { "ConnectionString": "Endpoint=sb://xxx.servicebus.windows.net/;SharedAccessKeyName=xxx;SharedAccessKey=xxx", "Hubs": { "FileUploadedEvent": "classifiedadds_fileuploaded", "FileDeletedEvent": "classifiedadds_filedeleted", "EmailMessageCreatedEvent": "classifiedadds_emailcreated", "SmsMessageCreatedEvent": "classifiedadds_smscreated" }, "StorageConnectionString": "DefaultEndpointsProtocol=https;AccountName=xxx;AccountKey=xxx;EndpointSuffix=core.windows.net", "StorageContainerNames": { "FileUploadedEvent": "eventhub-fileuploaded", "FileDeletedEvent": "eventhub-filedeleted", "EmailMessageCreatedEvent": "eventhub-emailcreated", "SmsMessageCreatedEvent": "eventhub-smscreated" } } }
Logging
- Open and jump to Logging section of below files:
- ClassifiedAds.WebAPI/appsettings.json
- ClassifiedAds.WebMVC/appsettings.json
- ClassifiedAds.IdentityServer/appsettings.json
- ClassifiedAds.BackgroundServer/appsettings.json
"Logging": { "LogLevel": { "Default": "Warning" }, "File": { "MinimumLogEventLevel": "Information" }, "Elasticsearch": { "IsEnabled": false, "Host": "http://localhost:9200", "IndexFormat": "classifiedads", "MinimumLogEventLevel": "Information" }, "EventLog": { "IsEnabled": false, "LogName": "Application", "SourceName": "ClassifiedAds.WebAPI" } },
- Write to Local file (./logs/log.txt). Always enabled.
"Logging": { "File": { "MinimumLogEventLevel": "Information" }, },
- Write to Elasticsearch:
"Logging": { "Elasticsearch": { "IsEnabled": true, "Host": "http://localhost:9200", "IndexFormat": "classifiedads", "MinimumLogEventLevel": "Information" }, },
- Write to Windows Event Log (Windows only):
"Logging": { "EventLog": { "IsEnabled": true, "LogName": "Application", "SourceName": "ClassifiedAds.WebAPI" } },
- Enable all options:
"Logging": { "LogLevel": { "Default": "Warning" }, "File": { "MinimumLogEventLevel": "Information" }, "Elasticsearch": { "IsEnabled": true, "Host": "http://localhost:9200", "IndexFormat": "classifiedads", "MinimumLogEventLevel": "Information" }, "EventLog": { "IsEnabled": true, "LogName": "Application", "SourceName": "ClassifiedAds.WebAPI" } },
Caching
- Open and jump to Caching section of below files:
- ClassifiedAds.WebAPI/appsettings.json
- ClassifiedAds.WebMVC/appsettings.json
- ClassifiedAds.IdentityServer/appsettings.json
"Caching": { "InMemory": { }, "Distributed": { } },
- Configure options for In Memory Cache:
"Caching": { "InMemory": { "SizeLimit": null }, },
- Use In Memory Distributed Cache (For Local Testing):
"Caching": { "Distributed": { "Provider": "InMemory", "InMemory": { "SizeLimit": null } } },
- Use Redis Distributed Cache:
"Caching": { "Distributed": { "Provider": "Redis", "Redis": { "Configuration": "xxx.redis.cache.windows.net:6380,password=xxx,ssl=True,abortConnect=False", "InstanceName": "" } } },
- Use Sql Server Distributed Cache:
dotnet tool install --global dotnet-sql-cache --version="5.0" dotnet sql-cache create "Server=127.0.0.1;Database=ClassifiedAds;User Id=sa;Password=sqladmin123!@#" dbo CacheEntries
"Caching": { "Distributed": { "Provider": "SqlServer", "SqlServer": { "ConnectionString": "Server=127.0.0.1;Database=ClassifiedAds;User Id=sa;Password=sqladmin123!@#", "SchemaName": "dbo", "TableName": "CacheEntries" } } },
Monitoring
- Open and jump to Monitoring section of below files:
- ClassifiedAds.WebAPI/appsettings.json
- ClassifiedAds.WebMVC/appsettings.json
- ClassifiedAds.IdentityServer/appsettings.json
"Monitoring": { "MiniProfiler": { }, "AzureApplicationInsights": { } },
- Use MiniProfiler:
"Monitoring": { "MiniProfiler": { "IsEnabled": true, "SqlServerStorage": { "ConectionString": "Server=127.0.0.1;Database=ClassifiedAds;User Id=sa;Password=sqladmin123!@#;MultipleActiveResultSets=true;Encrypt=False", "ProfilersTable": "MiniProfilers", "TimingsTable": "MiniProfilerTimings", "ClientTimingsTable": "MiniProfilerClientTimings" } }, },
- Use Azure Application Insights:
"Monitoring": { "AzureApplicationInsights": { "IsEnabled": true, "InstrumentationKey": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "EnableSqlCommandTextInstrumentation": true } },
- Use AppMetrics:
"Monitoring": { "AppMetrics": { "IsEnabled": true, "MetricsOptions": { "DefaultContextLabel": "ClassifiedAds.WebAPI", "Enabled": true, "ReportingEnabled": true }, "MetricsWebTrackingOptions": { "ApdexTrackingEnabled": true, "ApdexTSeconds": 0.1, "IgnoredHttpStatusCodes": [ 404 ], "IgnoredRoutesRegexPatterns": [], "OAuth2TrackingEnabled": true }, "MetricEndpointsOptions": { "MetricsEndpointEnabled": true, "MetricsTextEndpointEnabled": true, "EnvironmentInfoEndpointEnabled": true } } },
- Use Both:
"Monitoring": { "MiniProfiler": { "IsEnabled": true, "SqlServerStorage": { "ConectionString": "Server=127.0.0.1;Database=ClassifiedAds;User Id=sa;Password=sqladmin123!@#;MultipleActiveResultSets=true;Encrypt=False", "ProfilersTable": "MiniProfilers", "TimingsTable": "MiniProfilerTimings", "ClientTimingsTable": "MiniProfilerClientTimings" } }, "AzureApplicationInsights": { "IsEnabled": true, "InstrumentationKey": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "EnableSqlCommandTextInstrumentation": true }, "AppMetrics": { "IsEnabled": true, "MetricsOptions": { "DefaultContextLabel": "ClassifiedAds.WebAPI", "Enabled": true, "ReportingEnabled": true }, "MetricsWebTrackingOptions": { "ApdexTrackingEnabled": true, "ApdexTSeconds": 0.1, "IgnoredHttpStatusCodes": [ 404 ], "IgnoredRoutesRegexPatterns": [], "OAuth2TrackingEnabled": true }, "MetricEndpointsOptions": { "MetricsEndpointEnabled": true, "MetricsTextEndpointEnabled": true, "EnvironmentInfoEndpointEnabled": true } } },
Interceptors
- Open and jump to Interceptors section of below files:
- ClassifiedAds.WebAPI/appsettings.json
- ClassifiedAds.WebMVC/appsettings.json
- ClassifiedAds.IdentityServer/appsettings.json
- ClassifiedAds.BackgroundServer/appsettings.json
"Interceptors": { "LoggingInterceptor": true, "ErrorCatchingInterceptor": false },
Security Headers
- Open ClassifiedAds.WebAPI/appsettings.json and jump to SecurityHeaders section:
"SecurityHeaders": { "Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0" },
- Open ClassifiedAds.WebMVC/appsettings.json and jump to SecurityHeaders section:
"SecurityHeaders": { "Content-Security-Policy": "form-action 'self'; frame-ancestors 'none'", "Feature-Policy": "camera 'none'", "Referrer-Policy": "strict-origin-when-cross-origin", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY", "X-XSS-Protection": "1; mode=block", "Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0" },
Cross-Origin Resource Sharing (CORS)
- Open ClassifiedAds.WebAPI/appsettings.json and jump to CORS section:
"CORS": { "AllowAnyOrigin": false, "AllowedOrigins": [ "http://localhost:4200", "http://localhost:3000", "http://localhost:8080" ] },
- Open ClassifiedAds.NotificationServer/appsettings.json and jump to CORS section:
"CORS": { "AllowedOrigins": [ "https://localhost:44364", "http://host.docker.internal:9003" ] }
External Login
- Open ClassifiedAds.IdentityServer/appsettings.json and jump to ExternalLogin section:
"ExternalLogin": { "AzureActiveDirectory": { "IsEnabled": true, "Authority": "https://login.microsoftonline.com/<Directory (tenant) ID>", "ClientId": "<Application (client) ID", "ClientSecret": "xxx" }, "Microsoft": { "IsEnabled": true, "ClientId": "<Application (client) ID", "ClientSecret": "xxx" }, "Google": { "IsEnabled": true, "ClientId": "xxx", "ClientSecret": "xxx" }, "Facebook": { "IsEnabled": true, "AppId": "xxx", "AppSecret": "xxx" } },
Sending Email
- Open ClassifiedAds.BackgroundServer/appsettings.json and jump to Notification -> Email section:
"Notification": { "Email": { "Provider": "Fake", } }
- Use SmtpClient:
"Notification": { "Email": { "Provider": "SmtpClient", "SmtpClient": { "Host": "localhost", "Port": "", "UserName": "", "Password": "", "EnableSsl": "" } } }
Sending SMS
- Open ClassifiedAds.BackgroundServer/appsettings.json and jump to Notification -> Sms section:
"Notification": { "Sms": { "Provider": "Fake", } }
- Use Twilio
"Notification": { "Sms": { "Provider": "Twilio", "Twilio": { "AccountSId": "", "AuthToken": "", "FromNumber": "" } } }
Set Startup Projects
Run or Debug the Solution
-
Web MVC Home Page: https://localhost:44364/
-
Navigate to Health Checks UI https://localhost:44364/healthchecks-ui#/healthchecks and make sure everything is green.
-
Login on Identity Server:
- Option 1: Use default created account:
- User Name: phong@gmail.com
- Password: v*7Un8b4rcN@<-RN
- Option 2: Register new account at https://localhost:44367/Account/Register
- Option 1: Use default created account:
-
Open Blazor Home Page at: https://localhost:44331
How to Build and Run Single Page Applications:
- Angular:
-
Navigate to folder: UIs/angular/
npm install ng serve
-
Update environment.ts & environment.prod.ts
export const environment = { OpenIdConnect: { Authority: "https://localhost:44367", ClientId: "ClassifiedAds.Angular" }, ResourceServer: { Endpoint: "https://localhost:44312/api/" }, CurrentUrl: "http://localhost:4200/" };
-
Go to http://localhost:4200/
-
- React:
-
Navigate to folder: UIs/reactjs/
npm install npm run dev
-
Update environment.dev.js & environment.js
const environment = { OpenIdConnect: { Authority: "https://localhost:44367", ClientId: "ClassifiedAds.React" }, ResourceServer: { Endpoint: "https://localhost:44312/api/" }, CurrentUrl: "http://localhost:3000/" }; export default environment;
-
Go to http://localhost:3000/
-
- Vue:
- Navigate to folder: UIs/vuejs/
npm install npm run serve
- Update environment.dev.js & environment.dev.js
const environment = { OpenIdConnect: { Authority: "https://localhost:44367", ClientId: "ClassifiedAds.Vue" }, ResourceServer: { Endpoint: "https://localhost:44312/api/" }, CurrentUrl: "http://localhost:8080/" }; export default environment;
- Navigate to folder: UIs/vuejs/
-
Go to http://localhost:8080/
-
Before Login, go to Identity Server https://localhost:44367/Client to make sure application clients have been registered:
How to Run on Docker Containers:
-
Add Migrations if you haven't done on previous steps:
- Install dotnet-ef cli:
dotnet tool install --global dotnet-ef --version="5.0"
- Navigate to ClassifiedAds.Migrator and run these commands:
dotnet ef migrations add Init --context AdsDbContext -o Migrations/AdsDb dotnet ef migrations add Init --context ConfigurationDbContext -o Migrations/ConfigurationDb dotnet ef migrations add Init --context PersistedGrantDbContext -o Migrations/PersistedGrantDb
- Install dotnet-ef cli:
-
Navigate to Monolith and run:
docker-compose build docker-compose up
-
Open Web MVC Home Page at: http://host.docker.internal:9003
-
Navigate to Health Checks UI http://host.docker.internal:9003/healthchecks-ui#/healthchecks and make sure everything is green.
-
Login on Identity Server:
- Use default created account: phong@gmail.com / v*7Un8b4rcN@<-RN
- Register new account at http://host.docker.internal:9000/Account/Register
-
Open Blazor Home Page at: http://host.docker.internal:9008
How to Run Integration & End to End Tests:
-
Update ClassifiedAds.IntegrationTests/appsettings.json
{ "OpenIdConnect": { "Authority": "https://localhost:44367", "ClientId": "ClassifiedAds.WebMVC", "ClientSecret": "secret", "RequireHttpsMetadata": "true" }, "WebAPI": { "Endpoint": "https://localhost:44312" }, "GraphQL": { "Endpoint": "https://localhost:44392/graphql" }, "Login": { "UserName": "phong@gmail.com", "Password": "v*7Un8b4rcN@<-RN", "Scope": "ClassifiedAds.WebAPI" } }
-
Download Chrome Driver
-
Update ClassifiedAds.EndToEndTests/appsettings.json
{ "ChromeDriverPath": "D:\\Downloads\\chromedriver_win32\\72", "Login": { "Url": "https://localhost:44364/Home/Login", "UserName": "phong@gmail.com", "Password": "v*7Un8b4rcN@<-RN" } }
Application URLs:
https://github.com/phongnguyend/Practical.CleanArchitecture/wiki/Application-URLs
Roadmap:
https://github.com/phongnguyend/Practical.CleanArchitecture/wiki/Roadmap
Licence ð
This repository is licensed under the MIT license.
Duende.IdentityServer License ð
Duende.IdentityServer is available under both a FOSS (RPL) and a commercial license.
For the production environment, it is necessary to get a specific license, if you would like more information about the licensing of Duende.IdentityServer - please check this link.
The source code under /src/IdentityServer/Duende folder uses the source code from https://github.com/DuendeSoftware/IdentityServer.Quickstart.UI which is under the terms of the following license.
EPPLus License ð
EPPlus 5 can be used under Polyform Noncommercial license or a commercial license.
For the production environment, it is necessary to get a specific license, if you would like more information about the licensing of EPPlus 5 - please check this link.
Top Related Projects
Clean Architecture Solution Template: A starting point for Clean Architecture with ASP.NET Core
:cyclone: Clean Architecture with .NET6, C#10 and React+Redux. Use cases as central organizing structure, completely testable, decoupled from frameworks
Web Application ASP.NET 8 using Clean Architecture, DDD, CQRS, Event Sourcing and a lot of good practices
Cross-platform .NET sample microservices and container based application that runs on Linux Windows and macOS. Powered by .NET 7, Docker Containers and Azure Kubernetes Services. Supports Visual Studio, VS for Mac and CLI based environments with Docker CLI, dotnet CLI, VS Code or any other code editor. Moved to https://github.com/dotnet/eShop.
Full Modular Monolith application with Domain-Driven Design approach.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot