β οΈ WarningThe code samples contain multiple ways and patterns to do things and not always be considered best practices or recommended for all situations.
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": "" } } }
-
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: [email protected]
- 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
-
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 start
-
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:
-
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: [email protected] / v*7Un8b4rcN@<-RN
- Register new account at http://host.docker.internal:9000/Account/Register
-
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" }, "GraphQL": { "Endpoint": "https://localhost:44392/graphql" }, "Login": { "UserName": "[email protected]", "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": "[email protected]", "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 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 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.