Database Centric vs Domain Centric Architecture Classic Three-layer Architecture Modern Four-layer Architecture Vertical Slice Architecture (Modular Monolith) Additional Configuration Sources⚠️ Warning
The code samples contain multiple ways and patterns to do things and not always be considered best practices or recommended for all situations.
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/" } },
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" } },
Open below files and jump to Messaging section:
"Messaging": { "Provider": "RabbitMQ", }
Use RabbitMQ
"Messaging": { "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:
"Messaging": { "Provider": "Kafka", "Kafka": { "BootstrapServers": "localhost:9092", "Topics": { "FileUploadedEvent": "classifiedadds_fileuploaded", "FileDeletedEvent": "classifiedadds_filedeleted", "EmailMessageCreatedEvent": "classifiedadds_emailcreated", "SmsMessageCreatedEvent": "classifiedadds_smscreated" }, } }
Use Azure Queue Storage:
"Messaging": { "Provider": "AzureQueue", "AzureQueue": { "ConnectionString": "xxx", "QueueNames": { "FileUploadedEvent": "classifiedadds-fileuploaded", "FileDeletedEvent": "classifiedadds-filedeleted", "EmailMessageCreatedEvent": "classifiedadds-emailcreated", "SmsMessageCreatedEvent": "classifiedadds-smscreated" } } }
Use Azure Service Bus:
"Messaging": { "Provider": "AzureServiceBus", "AzureServiceBus": { "ConnectionString": "xxx", "QueueNames": { "FileUploadedEvent": "classifiedadds_fileuploaded", "FileDeletedEvent": "classifiedadds_filedeleted", "EmailMessageCreatedEvent": "classifiedadds_emailcreated", "SmsMessageCreatedEvent": "classifiedadds_smscreated" } } }
"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" } },
"Logging": { "File": { "MinimumLogEventLevel": "Information" }, },
"Logging": { "Elasticsearch": { "IsEnabled": true, "Host": "http://localhost:9200", "IndexFormat": "classifiedads", "MinimumLogEventLevel": "Information" }, },
"Logging": { "EventLog": { "IsEnabled": true, "LogName": "Application", "SourceName": "ClassifiedAds.WebAPI" } },
"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": { "InMemory": { }, "Distributed": { } },
"Caching": { "InMemory": { "SizeLimit": null }, },
"Caching": { "Distributed": { "Provider": "InMemory", "InMemory": { "SizeLimit": null } } },
"Caching": { "Distributed": { "Provider": "Redis", "Redis": { "Configuration": "xxx.redis.cache.windows.net:6380,password=xxx,ssl=True,abortConnect=False", "InstanceName": "" } } },
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": { "AzureApplicationInsights": { "IsEnabled": true, "InstrumentationKey": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "EnableSqlCommandTextInstrumentation": true } },
"Monitoring": { "OpenTelemetry": { "IsEnabled": true, "ServiceName": "ClassifiedAds.WebAPI", "TraceEnabled": true, "MetricEnabled": true, "Otlp": { "IsEnabled": false, "Endpoint": "http://localhost:4317" } } },
"Monitoring": { "AzureApplicationInsights": { "IsEnabled": true, "InstrumentationKey": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "EnableSqlCommandTextInstrumentation": true }, "OpenTelemetry": { "IsEnabled": true, "ServiceName": "ClassifiedAds.WebAPI", "TraceEnabled": true, "MetricEnabled": true, "Otlp": { "IsEnabled": false, "Endpoint": "http://localhost:4317" } } },
"Interceptors": { "LoggingInterceptor": true, "ErrorCatchingInterceptor": false },
"SecurityHeaders": { "Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0" },
"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" },
"CORS": { "AllowAnyOrigin": false, "AllowedOrigins": [ "http://localhost:4200", "http://localhost:3000", "http://localhost:8080" ] },
"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" } },
"Notification": { "Email": { "Provider": "Fake", } }
"Notification": { "Email": { "Provider": "SmtpClient", "SmtpClient": { "Host": "localhost", "Port": "", "UserName": "", "Password": "", "EnableSsl": "" } } }
"Notification": { "Sms": { "Provider": "Fake", } }
"Notification": { "Sms": { "Provider": "Twilio", "Twilio": { "AccountSId": "", "AuthToken": "", "FromNumber": "" } } }
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:
Open Blazor Home Page at: https://localhost:44331
Angular:
Navigate to folder: UIs/angular/
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/
Update environment.dev.tsx & environment.tsx
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:
const environment = { OpenIdConnect: { Authority: "https://localhost:44367", ClientId: "ClassifiedAds.Vue" }, ResourceServer: { Endpoint: "https://localhost:44312/api/" }, CurrentUrl: "http://localhost:8080/" }; export default environment;
Go to http://localhost:8080/
Before Login, go to Identity Server https://localhost:44367/Client to make sure application clients have been registered:
Add Migrations if you haven't done on previous steps:
dotnet tool install --global dotnet-ef --version="8.0"
dotnet ef migrations add Init --context AdsDbContext -o Migrations/AdsDb
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:
Open Blazor Home Page at: http://host.docker.internal:9008
Update ClassifiedAds.IntegrationTests/appsettings.json
{ "OpenIdConnect": { "Authority": "https://localhost:44367", "ClientId": "ClassifiedAds.WebMVC", "ClientSecret": "secret", "RequireHttpsMetadata": "true" }, "WebAPI": { "Endpoint": "https://localhost:44312" }, "Login": { "UserName": "phong@gmail.com", "Password": "v*7Un8b4rcN@<-RN", "Scope": "ClassifiedAds.WebAPI" } }
Download Chrome Driver
Update EndToEndTests/appsettings.json
{ "ChromeDriverPath": "D:\\Downloads\\chromedriver_win32\\72", "Login": { "Url": "https://localhost:44364/Home/Login", "UserName": "phong@gmail.com", "Password": "v*7Un8b4rcN@<-RN" } }
https://github.com/phongnguyend/Practical.CleanArchitecture/wiki/Application-URLs
https://github.com/phongnguyend/Practical.CleanArchitecture/wiki/Roadmap
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.
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