From 753a2402bf1cab62a520bae071134a90fba55f6a Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 30 Aug 2026 16:20:08 +0200 Subject: [PATCH] Configuration series: migrate the six samples to net10.0 Brings the ASP.NET Core configuration series into this repository from CodeMazeBlog/aspnet-core-configuration, one folder per article, under aspnetcore-webapi/AspNetCoreConfigurationSeries/ with an aggregate solution at that path. Each folder is built from its own branch and is the end state of its own article. The branches are a linear chain but not a strict accumulation, so two folders are deliberately NOT their predecessor plus additions: - OptionsValidation drops OptionsPattern's named options, because part 3 tells the reader to. GetTitleColor() loses its parameter and the Pages:ProductPage section goes. The two TitleColorService.cs files are meant to differ. - CustomConfigurationProvider replaces part 4's INI registration with the EF Core provider rather than extending it, so appsettings.ini is not carried forward. It was orphaned on the source branches. Common to all six: netcoreapp3.1 to net10.0, nullable reference types and implicit usings enabled, Startup.cs collapsed into minimal hosting, the checked-in wwwroot/lib Bootstrap tree dropped, and a test project. Per-article fixes: - ConfigurationBasics: nullable annotations on HomeModel.DefaultLogLevel and LoggingLevelConfiguration.Default; primary constructor on HomeController. - OptionsPattern: Random.Shared.Next(_colors.Length) in TitleColorService, which was random.Next(7) over eight colours, so "pink" was unreachable; string.Empty initialisers on TitleConfiguration; appsettings.json HomePage colour set to red to agree with the article and its screenshot. - OptionsValidation: AddOptionsWithValidateOnStart + Bind + ValidateDataAnnotations; the validator registered with TryAddEnumerable so a second validator can be added later; Validate(string? name, ...) to match the interface (CS8767); failures accumulated with ValidateOptionsResultBuilder so both broken rules are reported in one pass; [Required] and [MaxLength(60)] restored on WelcomeMessage; the monitor held and read inside GetTitleColor() rather than captured in the constructor of a singleton. - ConfigurationProviders: appsettings.ini section header corrected from [Logging:Level] to [Logging:LogLevel], which is the key the logging system actually reads; the INI file copied to the output directory; Microsoft.Extensions.Configuration.Ini 10.0.11. - CustomConfigurationProvider: Dictionary on the seeding helper (CS8619); StringComparer.OrdinalIgnoreCase on the ToDictionary branch too, which is what made the sample case-sensitive from the second run onward; the source takes a connection string instead of an Action, registered through an AddEntityConfiguration extension on ConfigurationManager; using var; EF Core 10.0.11; the connection string points at LocalDB rather than SQLEXPRESS. - SecuringDataLocally: ships with no ConnectionStrings section in appsettings.json, which is the article's subject. Program.cs still reads it, so the project needs a user secret or an environment variable to start. The folder README gives the command. The three projects that use the Secret Manager each get a fresh, distinct UserSecretsId, so their secret stores are separate. Sharing one would contradict the last article's central claim. Tests: the four folders that need no database assert their article's own argument and run anywhere. CustomConfigurationProvider.Tests and SecuringDataLocally.Tests start a SQL Server container with Testcontainers; those tests are marked [RequiresDockerFact] and skip when no daemon is reachable, with CONFIGURATION_SERIES_REQUIRE_DOCKER=1 turning the skip into a failure. --- .../AspNetCoreConfigurationSeries.sln | 214 ++++++++++++++++++ .../ConfigurationBasics.Tests.csproj | 26 +++ .../ReadingConfigurationTests.cs | 68 ++++++ .../ProjectConfigurationDemo.sln | 48 ++++ .../Controllers/HomeController.cs | 31 +++ .../Models/ErrorViewModel.cs | 8 + .../Models/HomeModel.cs | 6 + .../Models/LoggingLevelConfiguration.cs | 6 + .../ProjectConfigurationDemo/Program.cs | 13 ++ .../ProjectConfigurationDemo.csproj | 9 + .../Properties/launchSettings.json | 13 ++ .../Views/Home/Index.cshtml | 9 + .../Views/Home/Privacy.cshtml | 6 + .../Views/Shared/Error.cshtml | 25 ++ .../Views/Shared/_Layout.cshtml | 30 +++ .../Views/_ViewImports.cshtml | 3 + .../Views/_ViewStart.cshtml | 3 + .../appsettings.Development.json | 8 + .../appsettings.Production.json | 5 + .../ProjectConfigurationDemo/appsettings.json | 18 ++ .../wwwroot/css/site.css | 49 ++++ .../wwwroot/favicon.ico | Bin 0 -> 32038 bytes .../wwwroot/js/site.js | 1 + .../ConfigurationProviders.Tests.csproj | 28 +++ .../ProviderOrderTests.cs | 100 ++++++++ .../ProjectConfigurationDemo.sln | 48 ++++ .../TitleConfigurationValidation.cs | 24 ++ .../Controllers/HomeController.cs | 35 +++ .../Models/ErrorViewModel.cs | 8 + .../Models/HomeModel.cs | 6 + .../Models/LoggingLevelConfiguration.cs | 6 + .../Models/TitleConfiguration.cs | 16 ++ .../ProjectConfigurationDemo/Program.cs | 33 +++ .../ProjectConfigurationDemo.csproj | 22 ++ .../Properties/launchSettings.json | 13 ++ .../Services/ITitleColorService.cs | 6 + .../Services/TitleColorService.cs | 19 ++ .../Views/Home/Index.cshtml | 10 + .../Views/Home/Privacy.cshtml | 6 + .../Views/Shared/Error.cshtml | 25 ++ .../Views/Shared/_Layout.cshtml | 30 +++ .../Views/_ViewImports.cshtml | 3 + .../Views/_ViewStart.cshtml | 3 + .../appsettings.Development.json | 8 + .../appsettings.Production.json | 5 + .../ProjectConfigurationDemo/appsettings.ini | 7 + .../ProjectConfigurationDemo/appsettings.json | 21 ++ .../wwwroot/css/site.css | 49 ++++ .../wwwroot/favicon.ico | Bin 0 -> 32038 bytes .../wwwroot/js/site.js | 1 + .../CustomConfigurationProvider.Tests.csproj | 27 +++ .../EFConfigurationProviderTests.cs | 76 +++++++ .../RequiresDockerFactAttribute.cs | 62 +++++ .../ProjectConfigurationDemo.sln | 48 ++++ .../TitleConfigurationValidation.cs | 24 ++ .../Controllers/HomeController.cs | 35 +++ .../Models/ConfigurationDbContext.cs | 13 ++ .../Models/ConfigurationEntity.cs | 11 + .../EFConfigurationProvider.cs | 38 ++++ .../EFConfigurationSource.cs | 11 + .../Models/EntityConfigurationExtensions.cs | 16 ++ .../Models/ErrorViewModel.cs | 8 + .../Models/HomeModel.cs | 6 + .../Models/LoggingLevelConfiguration.cs | 6 + .../Models/TitleConfiguration.cs | 16 ++ .../ProjectConfigurationDemo/Program.cs | 31 +++ .../ProjectConfigurationDemo.csproj | 18 ++ .../Properties/launchSettings.json | 13 ++ .../Services/ITitleColorService.cs | 6 + .../Services/TitleColorService.cs | 19 ++ .../Views/Home/Index.cshtml | 10 + .../Views/Home/Privacy.cshtml | 6 + .../Views/Shared/Error.cshtml | 25 ++ .../Views/Shared/_Layout.cshtml | 30 +++ .../Views/_ViewImports.cshtml | 3 + .../Views/_ViewStart.cshtml | 3 + .../appsettings.Development.json | 8 + .../appsettings.Production.json | 5 + .../ProjectConfigurationDemo/appsettings.json | 13 ++ .../wwwroot/css/site.css | 49 ++++ .../wwwroot/favicon.ico | Bin 0 -> 32038 bytes .../wwwroot/js/site.js | 1 + .../OptionsPattern.Tests.csproj | 26 +++ .../OptionsPatternTests.cs | 92 ++++++++ .../ProjectConfigurationDemo.sln | 48 ++++ .../Controllers/HomeController.cs | 35 +++ .../Models/ErrorViewModel.cs | 8 + .../Models/HomeModel.cs | 6 + .../Models/LoggingLevelConfiguration.cs | 6 + .../Models/TitleConfiguration.cs | 9 + .../ProjectConfigurationDemo/Program.cs | 24 ++ .../ProjectConfigurationDemo.csproj | 9 + .../Properties/launchSettings.json | 13 ++ .../Services/ITitleColorService.cs | 6 + .../Services/TitleColorService.cs | 19 ++ .../Views/Home/Index.cshtml | 10 + .../Views/Home/Privacy.cshtml | 6 + .../Views/Shared/Error.cshtml | 25 ++ .../Views/Shared/_Layout.cshtml | 30 +++ .../Views/_ViewImports.cshtml | 3 + .../Views/_ViewStart.cshtml | 3 + .../appsettings.Development.json | 8 + .../appsettings.Production.json | 5 + .../ProjectConfigurationDemo/appsettings.json | 27 +++ .../wwwroot/css/site.css | 49 ++++ .../wwwroot/favicon.ico | Bin 0 -> 32038 bytes .../wwwroot/js/site.js | 1 + .../OptionsValidation.Tests.csproj | 26 +++ .../OptionsValidationTests.cs | 109 +++++++++ .../ProjectConfigurationDemo.sln | 48 ++++ .../TitleConfigurationValidation.cs | 24 ++ .../Controllers/HomeController.cs | 35 +++ .../Models/ErrorViewModel.cs | 8 + .../Models/HomeModel.cs | 6 + .../Models/LoggingLevelConfiguration.cs | 6 + .../Models/TitleConfiguration.cs | 16 ++ .../ProjectConfigurationDemo/Program.cs | 29 +++ .../ProjectConfigurationDemo.csproj | 9 + .../Properties/launchSettings.json | 13 ++ .../Services/ITitleColorService.cs | 6 + .../Services/TitleColorService.cs | 19 ++ .../Views/Home/Index.cshtml | 10 + .../Views/Home/Privacy.cshtml | 6 + .../Views/Shared/Error.cshtml | 25 ++ .../Views/Shared/_Layout.cshtml | 30 +++ .../Views/_ViewImports.cshtml | 3 + .../Views/_ViewStart.cshtml | 3 + .../appsettings.Development.json | 8 + .../appsettings.Production.json | 5 + .../ProjectConfigurationDemo/appsettings.json | 21 ++ .../wwwroot/css/site.css | 49 ++++ .../wwwroot/favicon.ico | Bin 0 -> 32038 bytes .../wwwroot/js/site.js | 1 + .../AspNetCoreConfigurationSeries/README.md | 85 +++++++ .../ProjectConfigurationDemo.sln | 48 ++++ .../TitleConfigurationValidation.cs | 24 ++ .../Controllers/HomeController.cs | 35 +++ .../Models/ConfigurationDbContext.cs | 13 ++ .../Models/ConfigurationEntity.cs | 11 + .../EFConfigurationProvider.cs | 38 ++++ .../EFConfigurationSource.cs | 11 + .../Models/EntityConfigurationExtensions.cs | 16 ++ .../Models/ErrorViewModel.cs | 8 + .../Models/HomeModel.cs | 6 + .../Models/LoggingLevelConfiguration.cs | 6 + .../Models/TitleConfiguration.cs | 16 ++ .../ProjectConfigurationDemo/Program.cs | 31 +++ .../ProjectConfigurationDemo.csproj | 18 ++ .../Properties/launchSettings.json | 13 ++ .../Services/ITitleColorService.cs | 6 + .../Services/TitleColorService.cs | 19 ++ .../Views/Home/Index.cshtml | 10 + .../Views/Home/Privacy.cshtml | 6 + .../Views/Shared/Error.cshtml | 25 ++ .../Views/Shared/_Layout.cshtml | 30 +++ .../Views/_ViewImports.cshtml | 3 + .../Views/_ViewStart.cshtml | 3 + .../appsettings.Development.json | 8 + .../appsettings.Production.json | 7 + .../ProjectConfigurationDemo/appsettings.json | 10 + .../wwwroot/css/site.css | 49 ++++ .../wwwroot/favicon.ico | Bin 0 -> 32038 bytes .../wwwroot/js/site.js | 1 + .../SecuringDataLocally/README.md | 33 +++ .../ConnectionStringSourceTests.cs | 59 +++++ .../RequiresDockerFactAttribute.cs | 62 +++++ .../SecretSuppliedConfigurationTests.cs | 47 ++++ .../SecuringDataLocally.Tests.csproj | 27 +++ 168 files changed, 3535 insertions(+) create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/AspNetCoreConfigurationSeries.sln create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ConfigurationBasics.Tests/ConfigurationBasics.Tests.csproj create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ConfigurationBasics.Tests/ReadingConfigurationTests.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo.sln create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Controllers/HomeController.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Models/ErrorViewModel.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Models/HomeModel.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Program.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Properties/launchSettings.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Home/Index.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Home/Privacy.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Shared/Error.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/_ViewImports.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/_ViewStart.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/appsettings.Development.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/appsettings.Production.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/appsettings.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/wwwroot/css/site.css create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/wwwroot/favicon.ico create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/wwwroot/js/site.js create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ConfigurationProviders.Tests/ConfigurationProviders.Tests.csproj create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ConfigurationProviders.Tests/ProviderOrderTests.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo.sln create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Controllers/HomeController.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/ErrorViewModel.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/HomeModel.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/TitleConfiguration.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Program.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Properties/launchSettings.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Services/ITitleColorService.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Services/TitleColorService.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Home/Index.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Home/Privacy.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Shared/Error.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/_ViewImports.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/_ViewStart.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.Development.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.Production.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.ini create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/wwwroot/css/site.css create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/wwwroot/favicon.ico create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/wwwroot/js/site.js create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/CustomConfigurationProvider.Tests/CustomConfigurationProvider.Tests.csproj create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/CustomConfigurationProvider.Tests/EFConfigurationProviderTests.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/CustomConfigurationProvider.Tests/RequiresDockerFactAttribute.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo.sln create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Controllers/HomeController.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationDbContext.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationEntity.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationProvider.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationSource.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/EntityConfigurationExtensions.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ErrorViewModel.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/HomeModel.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/TitleConfiguration.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Program.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Properties/launchSettings.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Services/ITitleColorService.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Services/TitleColorService.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Home/Index.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Home/Privacy.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Shared/Error.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/_ViewImports.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/_ViewStart.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/appsettings.Development.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/appsettings.Production.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/appsettings.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/wwwroot/css/site.css create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/wwwroot/favicon.ico create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/wwwroot/js/site.js create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/OptionsPattern.Tests/OptionsPattern.Tests.csproj create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/OptionsPattern.Tests/OptionsPatternTests.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo.sln create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Controllers/HomeController.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/ErrorViewModel.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/HomeModel.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/TitleConfiguration.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Program.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Properties/launchSettings.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Services/ITitleColorService.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Services/TitleColorService.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Home/Index.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Home/Privacy.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Shared/Error.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/_ViewImports.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/_ViewStart.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/appsettings.Development.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/appsettings.Production.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/appsettings.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/wwwroot/css/site.css create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/wwwroot/favicon.ico create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/wwwroot/js/site.js create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/OptionsValidation.Tests/OptionsValidation.Tests.csproj create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/OptionsValidation.Tests/OptionsValidationTests.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo.sln create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Controllers/HomeController.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/ErrorViewModel.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/HomeModel.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/TitleConfiguration.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Program.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Properties/launchSettings.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Services/ITitleColorService.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Services/TitleColorService.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Home/Index.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Home/Privacy.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Shared/Error.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/_ViewImports.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/_ViewStart.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/appsettings.Development.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/appsettings.Production.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/appsettings.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/wwwroot/css/site.css create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/wwwroot/favicon.ico create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/wwwroot/js/site.js create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/README.md create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo.sln create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Controllers/HomeController.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationDbContext.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationEntity.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationProvider.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationSource.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/EntityConfigurationExtensions.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ErrorViewModel.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/HomeModel.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/TitleConfiguration.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Program.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Properties/launchSettings.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Services/ITitleColorService.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Services/TitleColorService.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Home/Index.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Home/Privacy.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Shared/Error.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/_ViewImports.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/_ViewStart.cshtml create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/appsettings.Development.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/appsettings.Production.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/appsettings.json create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/wwwroot/css/site.css create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/wwwroot/favicon.ico create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/wwwroot/js/site.js create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/README.md create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/ConnectionStringSourceTests.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/RequiresDockerFactAttribute.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/SecretSuppliedConfigurationTests.cs create mode 100644 aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/SecuringDataLocally.Tests.csproj diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/AspNetCoreConfigurationSeries.sln b/aspnetcore-webapi/AspNetCoreConfigurationSeries/AspNetCoreConfigurationSeries.sln new file mode 100644 index 0000000000..f12cc5610d --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/AspNetCoreConfigurationSeries.sln @@ -0,0 +1,214 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ConfigurationBasics", "ConfigurationBasics", "{E440020E-AEC5-2003-E9BF-47A625837F4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectConfigurationDemo", "ConfigurationBasics\ProjectConfigurationDemo\ProjectConfigurationDemo.csproj", "{72C6FAF1-FD5C-4864-A8E6-C4AA41B0A011}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigurationBasics.Tests", "ConfigurationBasics\ConfigurationBasics.Tests\ConfigurationBasics.Tests.csproj", "{C0E70EFC-A182-40D2-901F-9A583B9AEF5C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OptionsPattern", "OptionsPattern", "{5D588406-17D5-0299-8AE0-6BF58B0530F0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectConfigurationDemo", "OptionsPattern\ProjectConfigurationDemo\ProjectConfigurationDemo.csproj", "{E2160BF6-266C-4027-804C-542E3447C0E6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptionsPattern.Tests", "OptionsPattern\OptionsPattern.Tests\OptionsPattern.Tests.csproj", "{79ECBFF8-DCD6-4C93-8996-2F072103AE5F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OptionsValidation", "OptionsValidation", "{1391FDFE-BD35-8919-74EB-F3B38CB2D00D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectConfigurationDemo", "OptionsValidation\ProjectConfigurationDemo\ProjectConfigurationDemo.csproj", "{2516A86F-DBE7-44A4-8FCA-FC9F734EC904}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptionsValidation.Tests", "OptionsValidation\OptionsValidation.Tests\OptionsValidation.Tests.csproj", "{A6453D0E-163F-4410-9EBF-17E4DE82874B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ConfigurationProviders", "ConfigurationProviders", "{8591B607-85C4-7281-F2D9-E7CB135B7177}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectConfigurationDemo", "ConfigurationProviders\ProjectConfigurationDemo\ProjectConfigurationDemo.csproj", "{FCDD4279-65E2-45FB-9C75-AB3B774AB42E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigurationProviders.Tests", "ConfigurationProviders\ConfigurationProviders.Tests\ConfigurationProviders.Tests.csproj", "{59272CC8-3F27-4A7A-9D8B-0112CC0225DB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CustomConfigurationProvider", "CustomConfigurationProvider", "{199531CA-AC53-58B9-48ED-1EE607A3B125}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectConfigurationDemo", "CustomConfigurationProvider\ProjectConfigurationDemo\ProjectConfigurationDemo.csproj", "{4DF72404-A31D-4FAC-B8A4-DCFF95FDCEA4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomConfigurationProvider.Tests", "CustomConfigurationProvider\CustomConfigurationProvider.Tests\CustomConfigurationProvider.Tests.csproj", "{8CC5F09B-CEC3-4B32-91E8-D59AFDCAE130}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SecuringDataLocally", "SecuringDataLocally", "{E878789B-BF1A-09CD-D764-9880293A342A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectConfigurationDemo", "SecuringDataLocally\ProjectConfigurationDemo\ProjectConfigurationDemo.csproj", "{42D6CA51-79C0-4A3A-B45E-5D0025B2E0F3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecuringDataLocally.Tests", "SecuringDataLocally\SecuringDataLocally.Tests\SecuringDataLocally.Tests.csproj", "{07D4908B-1FE0-49BD-96B4-5244943691A2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {72C6FAF1-FD5C-4864-A8E6-C4AA41B0A011}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72C6FAF1-FD5C-4864-A8E6-C4AA41B0A011}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72C6FAF1-FD5C-4864-A8E6-C4AA41B0A011}.Debug|x64.ActiveCfg = Debug|Any CPU + {72C6FAF1-FD5C-4864-A8E6-C4AA41B0A011}.Debug|x64.Build.0 = Debug|Any CPU + {72C6FAF1-FD5C-4864-A8E6-C4AA41B0A011}.Debug|x86.ActiveCfg = Debug|Any CPU + {72C6FAF1-FD5C-4864-A8E6-C4AA41B0A011}.Debug|x86.Build.0 = Debug|Any CPU + {72C6FAF1-FD5C-4864-A8E6-C4AA41B0A011}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72C6FAF1-FD5C-4864-A8E6-C4AA41B0A011}.Release|Any CPU.Build.0 = Release|Any CPU + {72C6FAF1-FD5C-4864-A8E6-C4AA41B0A011}.Release|x64.ActiveCfg = Release|Any CPU + {72C6FAF1-FD5C-4864-A8E6-C4AA41B0A011}.Release|x64.Build.0 = Release|Any CPU + {72C6FAF1-FD5C-4864-A8E6-C4AA41B0A011}.Release|x86.ActiveCfg = Release|Any CPU + {72C6FAF1-FD5C-4864-A8E6-C4AA41B0A011}.Release|x86.Build.0 = Release|Any CPU + {C0E70EFC-A182-40D2-901F-9A583B9AEF5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0E70EFC-A182-40D2-901F-9A583B9AEF5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0E70EFC-A182-40D2-901F-9A583B9AEF5C}.Debug|x64.ActiveCfg = Debug|Any CPU + {C0E70EFC-A182-40D2-901F-9A583B9AEF5C}.Debug|x64.Build.0 = Debug|Any CPU + {C0E70EFC-A182-40D2-901F-9A583B9AEF5C}.Debug|x86.ActiveCfg = Debug|Any CPU + {C0E70EFC-A182-40D2-901F-9A583B9AEF5C}.Debug|x86.Build.0 = Debug|Any CPU + {C0E70EFC-A182-40D2-901F-9A583B9AEF5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0E70EFC-A182-40D2-901F-9A583B9AEF5C}.Release|Any CPU.Build.0 = Release|Any CPU + {C0E70EFC-A182-40D2-901F-9A583B9AEF5C}.Release|x64.ActiveCfg = Release|Any CPU + {C0E70EFC-A182-40D2-901F-9A583B9AEF5C}.Release|x64.Build.0 = Release|Any CPU + {C0E70EFC-A182-40D2-901F-9A583B9AEF5C}.Release|x86.ActiveCfg = Release|Any CPU + {C0E70EFC-A182-40D2-901F-9A583B9AEF5C}.Release|x86.Build.0 = Release|Any CPU + {E2160BF6-266C-4027-804C-542E3447C0E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2160BF6-266C-4027-804C-542E3447C0E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2160BF6-266C-4027-804C-542E3447C0E6}.Debug|x64.ActiveCfg = Debug|Any CPU + {E2160BF6-266C-4027-804C-542E3447C0E6}.Debug|x64.Build.0 = Debug|Any CPU + {E2160BF6-266C-4027-804C-542E3447C0E6}.Debug|x86.ActiveCfg = Debug|Any CPU + {E2160BF6-266C-4027-804C-542E3447C0E6}.Debug|x86.Build.0 = Debug|Any CPU + {E2160BF6-266C-4027-804C-542E3447C0E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2160BF6-266C-4027-804C-542E3447C0E6}.Release|Any CPU.Build.0 = Release|Any CPU + {E2160BF6-266C-4027-804C-542E3447C0E6}.Release|x64.ActiveCfg = Release|Any CPU + {E2160BF6-266C-4027-804C-542E3447C0E6}.Release|x64.Build.0 = Release|Any CPU + {E2160BF6-266C-4027-804C-542E3447C0E6}.Release|x86.ActiveCfg = Release|Any CPU + {E2160BF6-266C-4027-804C-542E3447C0E6}.Release|x86.Build.0 = Release|Any CPU + {79ECBFF8-DCD6-4C93-8996-2F072103AE5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79ECBFF8-DCD6-4C93-8996-2F072103AE5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79ECBFF8-DCD6-4C93-8996-2F072103AE5F}.Debug|x64.ActiveCfg = Debug|Any CPU + {79ECBFF8-DCD6-4C93-8996-2F072103AE5F}.Debug|x64.Build.0 = Debug|Any CPU + {79ECBFF8-DCD6-4C93-8996-2F072103AE5F}.Debug|x86.ActiveCfg = Debug|Any CPU + {79ECBFF8-DCD6-4C93-8996-2F072103AE5F}.Debug|x86.Build.0 = Debug|Any CPU + {79ECBFF8-DCD6-4C93-8996-2F072103AE5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79ECBFF8-DCD6-4C93-8996-2F072103AE5F}.Release|Any CPU.Build.0 = Release|Any CPU + {79ECBFF8-DCD6-4C93-8996-2F072103AE5F}.Release|x64.ActiveCfg = Release|Any CPU + {79ECBFF8-DCD6-4C93-8996-2F072103AE5F}.Release|x64.Build.0 = Release|Any CPU + {79ECBFF8-DCD6-4C93-8996-2F072103AE5F}.Release|x86.ActiveCfg = Release|Any CPU + {79ECBFF8-DCD6-4C93-8996-2F072103AE5F}.Release|x86.Build.0 = Release|Any CPU + {2516A86F-DBE7-44A4-8FCA-FC9F734EC904}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2516A86F-DBE7-44A4-8FCA-FC9F734EC904}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2516A86F-DBE7-44A4-8FCA-FC9F734EC904}.Debug|x64.ActiveCfg = Debug|Any CPU + {2516A86F-DBE7-44A4-8FCA-FC9F734EC904}.Debug|x64.Build.0 = Debug|Any CPU + {2516A86F-DBE7-44A4-8FCA-FC9F734EC904}.Debug|x86.ActiveCfg = Debug|Any CPU + {2516A86F-DBE7-44A4-8FCA-FC9F734EC904}.Debug|x86.Build.0 = Debug|Any CPU + {2516A86F-DBE7-44A4-8FCA-FC9F734EC904}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2516A86F-DBE7-44A4-8FCA-FC9F734EC904}.Release|Any CPU.Build.0 = Release|Any CPU + {2516A86F-DBE7-44A4-8FCA-FC9F734EC904}.Release|x64.ActiveCfg = Release|Any CPU + {2516A86F-DBE7-44A4-8FCA-FC9F734EC904}.Release|x64.Build.0 = Release|Any CPU + {2516A86F-DBE7-44A4-8FCA-FC9F734EC904}.Release|x86.ActiveCfg = Release|Any CPU + {2516A86F-DBE7-44A4-8FCA-FC9F734EC904}.Release|x86.Build.0 = Release|Any CPU + {A6453D0E-163F-4410-9EBF-17E4DE82874B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6453D0E-163F-4410-9EBF-17E4DE82874B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6453D0E-163F-4410-9EBF-17E4DE82874B}.Debug|x64.ActiveCfg = Debug|Any CPU + {A6453D0E-163F-4410-9EBF-17E4DE82874B}.Debug|x64.Build.0 = Debug|Any CPU + {A6453D0E-163F-4410-9EBF-17E4DE82874B}.Debug|x86.ActiveCfg = Debug|Any CPU + {A6453D0E-163F-4410-9EBF-17E4DE82874B}.Debug|x86.Build.0 = Debug|Any CPU + {A6453D0E-163F-4410-9EBF-17E4DE82874B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6453D0E-163F-4410-9EBF-17E4DE82874B}.Release|Any CPU.Build.0 = Release|Any CPU + {A6453D0E-163F-4410-9EBF-17E4DE82874B}.Release|x64.ActiveCfg = Release|Any CPU + {A6453D0E-163F-4410-9EBF-17E4DE82874B}.Release|x64.Build.0 = Release|Any CPU + {A6453D0E-163F-4410-9EBF-17E4DE82874B}.Release|x86.ActiveCfg = Release|Any CPU + {A6453D0E-163F-4410-9EBF-17E4DE82874B}.Release|x86.Build.0 = Release|Any CPU + {FCDD4279-65E2-45FB-9C75-AB3B774AB42E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCDD4279-65E2-45FB-9C75-AB3B774AB42E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCDD4279-65E2-45FB-9C75-AB3B774AB42E}.Debug|x64.ActiveCfg = Debug|Any CPU + {FCDD4279-65E2-45FB-9C75-AB3B774AB42E}.Debug|x64.Build.0 = Debug|Any CPU + {FCDD4279-65E2-45FB-9C75-AB3B774AB42E}.Debug|x86.ActiveCfg = Debug|Any CPU + {FCDD4279-65E2-45FB-9C75-AB3B774AB42E}.Debug|x86.Build.0 = Debug|Any CPU + {FCDD4279-65E2-45FB-9C75-AB3B774AB42E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCDD4279-65E2-45FB-9C75-AB3B774AB42E}.Release|Any CPU.Build.0 = Release|Any CPU + {FCDD4279-65E2-45FB-9C75-AB3B774AB42E}.Release|x64.ActiveCfg = Release|Any CPU + {FCDD4279-65E2-45FB-9C75-AB3B774AB42E}.Release|x64.Build.0 = Release|Any CPU + {FCDD4279-65E2-45FB-9C75-AB3B774AB42E}.Release|x86.ActiveCfg = Release|Any CPU + {FCDD4279-65E2-45FB-9C75-AB3B774AB42E}.Release|x86.Build.0 = Release|Any CPU + {59272CC8-3F27-4A7A-9D8B-0112CC0225DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59272CC8-3F27-4A7A-9D8B-0112CC0225DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59272CC8-3F27-4A7A-9D8B-0112CC0225DB}.Debug|x64.ActiveCfg = Debug|Any CPU + {59272CC8-3F27-4A7A-9D8B-0112CC0225DB}.Debug|x64.Build.0 = Debug|Any CPU + {59272CC8-3F27-4A7A-9D8B-0112CC0225DB}.Debug|x86.ActiveCfg = Debug|Any CPU + {59272CC8-3F27-4A7A-9D8B-0112CC0225DB}.Debug|x86.Build.0 = Debug|Any CPU + {59272CC8-3F27-4A7A-9D8B-0112CC0225DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59272CC8-3F27-4A7A-9D8B-0112CC0225DB}.Release|Any CPU.Build.0 = Release|Any CPU + {59272CC8-3F27-4A7A-9D8B-0112CC0225DB}.Release|x64.ActiveCfg = Release|Any CPU + {59272CC8-3F27-4A7A-9D8B-0112CC0225DB}.Release|x64.Build.0 = Release|Any CPU + {59272CC8-3F27-4A7A-9D8B-0112CC0225DB}.Release|x86.ActiveCfg = Release|Any CPU + {59272CC8-3F27-4A7A-9D8B-0112CC0225DB}.Release|x86.Build.0 = Release|Any CPU + {4DF72404-A31D-4FAC-B8A4-DCFF95FDCEA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4DF72404-A31D-4FAC-B8A4-DCFF95FDCEA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4DF72404-A31D-4FAC-B8A4-DCFF95FDCEA4}.Debug|x64.ActiveCfg = Debug|Any CPU + {4DF72404-A31D-4FAC-B8A4-DCFF95FDCEA4}.Debug|x64.Build.0 = Debug|Any CPU + {4DF72404-A31D-4FAC-B8A4-DCFF95FDCEA4}.Debug|x86.ActiveCfg = Debug|Any CPU + {4DF72404-A31D-4FAC-B8A4-DCFF95FDCEA4}.Debug|x86.Build.0 = Debug|Any CPU + {4DF72404-A31D-4FAC-B8A4-DCFF95FDCEA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4DF72404-A31D-4FAC-B8A4-DCFF95FDCEA4}.Release|Any CPU.Build.0 = Release|Any CPU + {4DF72404-A31D-4FAC-B8A4-DCFF95FDCEA4}.Release|x64.ActiveCfg = Release|Any CPU + {4DF72404-A31D-4FAC-B8A4-DCFF95FDCEA4}.Release|x64.Build.0 = Release|Any CPU + {4DF72404-A31D-4FAC-B8A4-DCFF95FDCEA4}.Release|x86.ActiveCfg = Release|Any CPU + {4DF72404-A31D-4FAC-B8A4-DCFF95FDCEA4}.Release|x86.Build.0 = Release|Any CPU + {8CC5F09B-CEC3-4B32-91E8-D59AFDCAE130}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CC5F09B-CEC3-4B32-91E8-D59AFDCAE130}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CC5F09B-CEC3-4B32-91E8-D59AFDCAE130}.Debug|x64.ActiveCfg = Debug|Any CPU + {8CC5F09B-CEC3-4B32-91E8-D59AFDCAE130}.Debug|x64.Build.0 = Debug|Any CPU + {8CC5F09B-CEC3-4B32-91E8-D59AFDCAE130}.Debug|x86.ActiveCfg = Debug|Any CPU + {8CC5F09B-CEC3-4B32-91E8-D59AFDCAE130}.Debug|x86.Build.0 = Debug|Any CPU + {8CC5F09B-CEC3-4B32-91E8-D59AFDCAE130}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CC5F09B-CEC3-4B32-91E8-D59AFDCAE130}.Release|Any CPU.Build.0 = Release|Any CPU + {8CC5F09B-CEC3-4B32-91E8-D59AFDCAE130}.Release|x64.ActiveCfg = Release|Any CPU + {8CC5F09B-CEC3-4B32-91E8-D59AFDCAE130}.Release|x64.Build.0 = Release|Any CPU + {8CC5F09B-CEC3-4B32-91E8-D59AFDCAE130}.Release|x86.ActiveCfg = Release|Any CPU + {8CC5F09B-CEC3-4B32-91E8-D59AFDCAE130}.Release|x86.Build.0 = Release|Any CPU + {42D6CA51-79C0-4A3A-B45E-5D0025B2E0F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42D6CA51-79C0-4A3A-B45E-5D0025B2E0F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42D6CA51-79C0-4A3A-B45E-5D0025B2E0F3}.Debug|x64.ActiveCfg = Debug|Any CPU + {42D6CA51-79C0-4A3A-B45E-5D0025B2E0F3}.Debug|x64.Build.0 = Debug|Any CPU + {42D6CA51-79C0-4A3A-B45E-5D0025B2E0F3}.Debug|x86.ActiveCfg = Debug|Any CPU + {42D6CA51-79C0-4A3A-B45E-5D0025B2E0F3}.Debug|x86.Build.0 = Debug|Any CPU + {42D6CA51-79C0-4A3A-B45E-5D0025B2E0F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42D6CA51-79C0-4A3A-B45E-5D0025B2E0F3}.Release|Any CPU.Build.0 = Release|Any CPU + {42D6CA51-79C0-4A3A-B45E-5D0025B2E0F3}.Release|x64.ActiveCfg = Release|Any CPU + {42D6CA51-79C0-4A3A-B45E-5D0025B2E0F3}.Release|x64.Build.0 = Release|Any CPU + {42D6CA51-79C0-4A3A-B45E-5D0025B2E0F3}.Release|x86.ActiveCfg = Release|Any CPU + {42D6CA51-79C0-4A3A-B45E-5D0025B2E0F3}.Release|x86.Build.0 = Release|Any CPU + {07D4908B-1FE0-49BD-96B4-5244943691A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07D4908B-1FE0-49BD-96B4-5244943691A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07D4908B-1FE0-49BD-96B4-5244943691A2}.Debug|x64.ActiveCfg = Debug|Any CPU + {07D4908B-1FE0-49BD-96B4-5244943691A2}.Debug|x64.Build.0 = Debug|Any CPU + {07D4908B-1FE0-49BD-96B4-5244943691A2}.Debug|x86.ActiveCfg = Debug|Any CPU + {07D4908B-1FE0-49BD-96B4-5244943691A2}.Debug|x86.Build.0 = Debug|Any CPU + {07D4908B-1FE0-49BD-96B4-5244943691A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07D4908B-1FE0-49BD-96B4-5244943691A2}.Release|Any CPU.Build.0 = Release|Any CPU + {07D4908B-1FE0-49BD-96B4-5244943691A2}.Release|x64.ActiveCfg = Release|Any CPU + {07D4908B-1FE0-49BD-96B4-5244943691A2}.Release|x64.Build.0 = Release|Any CPU + {07D4908B-1FE0-49BD-96B4-5244943691A2}.Release|x86.ActiveCfg = Release|Any CPU + {07D4908B-1FE0-49BD-96B4-5244943691A2}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {72C6FAF1-FD5C-4864-A8E6-C4AA41B0A011} = {E440020E-AEC5-2003-E9BF-47A625837F4A} + {C0E70EFC-A182-40D2-901F-9A583B9AEF5C} = {E440020E-AEC5-2003-E9BF-47A625837F4A} + {E2160BF6-266C-4027-804C-542E3447C0E6} = {5D588406-17D5-0299-8AE0-6BF58B0530F0} + {79ECBFF8-DCD6-4C93-8996-2F072103AE5F} = {5D588406-17D5-0299-8AE0-6BF58B0530F0} + {2516A86F-DBE7-44A4-8FCA-FC9F734EC904} = {1391FDFE-BD35-8919-74EB-F3B38CB2D00D} + {A6453D0E-163F-4410-9EBF-17E4DE82874B} = {1391FDFE-BD35-8919-74EB-F3B38CB2D00D} + {FCDD4279-65E2-45FB-9C75-AB3B774AB42E} = {8591B607-85C4-7281-F2D9-E7CB135B7177} + {59272CC8-3F27-4A7A-9D8B-0112CC0225DB} = {8591B607-85C4-7281-F2D9-E7CB135B7177} + {4DF72404-A31D-4FAC-B8A4-DCFF95FDCEA4} = {199531CA-AC53-58B9-48ED-1EE607A3B125} + {8CC5F09B-CEC3-4B32-91E8-D59AFDCAE130} = {199531CA-AC53-58B9-48ED-1EE607A3B125} + {42D6CA51-79C0-4A3A-B45E-5D0025B2E0F3} = {E878789B-BF1A-09CD-D764-9880293A342A} + {07D4908B-1FE0-49BD-96B4-5244943691A2} = {E878789B-BF1A-09CD-D764-9880293A342A} + EndGlobalSection +EndGlobal diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ConfigurationBasics.Tests/ConfigurationBasics.Tests.csproj b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ConfigurationBasics.Tests/ConfigurationBasics.Tests.csproj new file mode 100644 index 0000000000..8f4da1b477 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ConfigurationBasics.Tests/ConfigurationBasics.Tests.csproj @@ -0,0 +1,26 @@ + + + + net10.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ConfigurationBasics.Tests/ReadingConfigurationTests.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ConfigurationBasics.Tests/ReadingConfigurationTests.cs new file mode 100644 index 0000000000..d887bfbf03 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ConfigurationBasics.Tests/ReadingConfigurationTests.cs @@ -0,0 +1,68 @@ +using Microsoft.Extensions.Configuration; +using ProjectConfigurationDemo.Models; + +namespace ConfigurationBasics.Tests; + +public class ReadingConfigurationTests +{ + private static IConfiguration BuildConfiguration() => + new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["Logging:LogLevel:Default"] = "Information", + ["Logging:LogLevel:Microsoft"] = "Warning", + ["ConnectionStrings:sqlConnection"] = "Server=.;Database=CodeMazeCommerce;Trusted_Connection=True" + }) + .Build(); + + [Fact] + public void GivenAConfiguration_WhenReadWithTheIndexer_ThenItReturnsTheValueAtThatKeyPath() + { + var configuration = BuildConfiguration(); + + var logLevel = configuration["Logging:LogLevel:Default"]; + + Assert.Equal("Information", logLevel); + } + + [Fact] + public void GivenAConfiguration_WhenReadWithGetValue_ThenItConvertsToTheRequestedType() + { + var configuration = BuildConfiguration(); + + var logLevel = configuration.GetValue("Logging:LogLevel:Default"); + + Assert.Equal("Information", logLevel); + } + + [Fact] + public void GivenAConfiguration_WhenGetConnectionStringIsCalled_ThenItReadsTheConnectionStringsSection() + { + var configuration = BuildConfiguration(); + + var connectionString = configuration.GetConnectionString("sqlConnection"); + + Assert.Equal("Server=.;Database=CodeMazeCommerce;Trusted_Connection=True", connectionString); + } + + [Fact] + public void GivenAConfiguration_WhenBindIsCalled_ThenItPopulatesTheStronglyTypedObject() + { + var configuration = BuildConfiguration(); + var logLevelConfiguration = new LoggingLevelConfiguration(); + + configuration.Bind("Logging:LogLevel", logLevelConfiguration); + + Assert.Equal("Information", logLevelConfiguration.Default); + } + + [Fact] + public void GivenAConfiguration_WhenAKeyIsMissing_ThenTheIndexerReturnsNullRatherThanThrowing() + { + var configuration = BuildConfiguration(); + + var missing = configuration["Logging:LogLevel:NotThere"]; + + Assert.Null(missing); + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo.sln b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo.sln new file mode 100644 index 0000000000..072813330c --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectConfigurationDemo", "ProjectConfigurationDemo\ProjectConfigurationDemo.csproj", "{D8638F89-19EC-4916-83C6-27EA52E28F84}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigurationBasics.Tests", "ConfigurationBasics.Tests\ConfigurationBasics.Tests.csproj", "{9F1EF799-C7E1-485D-BD6F-DDBCC3091C04}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D8638F89-19EC-4916-83C6-27EA52E28F84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8638F89-19EC-4916-83C6-27EA52E28F84}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8638F89-19EC-4916-83C6-27EA52E28F84}.Debug|x64.ActiveCfg = Debug|Any CPU + {D8638F89-19EC-4916-83C6-27EA52E28F84}.Debug|x64.Build.0 = Debug|Any CPU + {D8638F89-19EC-4916-83C6-27EA52E28F84}.Debug|x86.ActiveCfg = Debug|Any CPU + {D8638F89-19EC-4916-83C6-27EA52E28F84}.Debug|x86.Build.0 = Debug|Any CPU + {D8638F89-19EC-4916-83C6-27EA52E28F84}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8638F89-19EC-4916-83C6-27EA52E28F84}.Release|Any CPU.Build.0 = Release|Any CPU + {D8638F89-19EC-4916-83C6-27EA52E28F84}.Release|x64.ActiveCfg = Release|Any CPU + {D8638F89-19EC-4916-83C6-27EA52E28F84}.Release|x64.Build.0 = Release|Any CPU + {D8638F89-19EC-4916-83C6-27EA52E28F84}.Release|x86.ActiveCfg = Release|Any CPU + {D8638F89-19EC-4916-83C6-27EA52E28F84}.Release|x86.Build.0 = Release|Any CPU + {9F1EF799-C7E1-485D-BD6F-DDBCC3091C04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F1EF799-C7E1-485D-BD6F-DDBCC3091C04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F1EF799-C7E1-485D-BD6F-DDBCC3091C04}.Debug|x64.ActiveCfg = Debug|Any CPU + {9F1EF799-C7E1-485D-BD6F-DDBCC3091C04}.Debug|x64.Build.0 = Debug|Any CPU + {9F1EF799-C7E1-485D-BD6F-DDBCC3091C04}.Debug|x86.ActiveCfg = Debug|Any CPU + {9F1EF799-C7E1-485D-BD6F-DDBCC3091C04}.Debug|x86.Build.0 = Debug|Any CPU + {9F1EF799-C7E1-485D-BD6F-DDBCC3091C04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F1EF799-C7E1-485D-BD6F-DDBCC3091C04}.Release|Any CPU.Build.0 = Release|Any CPU + {9F1EF799-C7E1-485D-BD6F-DDBCC3091C04}.Release|x64.ActiveCfg = Release|Any CPU + {9F1EF799-C7E1-485D-BD6F-DDBCC3091C04}.Release|x64.Build.0 = Release|Any CPU + {9F1EF799-C7E1-485D-BD6F-DDBCC3091C04}.Release|x86.ActiveCfg = Release|Any CPU + {9F1EF799-C7E1-485D-BD6F-DDBCC3091C04}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Controllers/HomeController.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Controllers/HomeController.cs new file mode 100644 index 0000000000..edc9c5444f --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Controllers/HomeController.cs @@ -0,0 +1,31 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using ProjectConfigurationDemo.Models; + +namespace ProjectConfigurationDemo.Controllers; + +public class HomeController(ILogger logger, IConfiguration configuration) : Controller +{ + private readonly ILogger _logger = logger; + private readonly IConfiguration _configuration = configuration; + + public IActionResult Index() + { + var logLevelConfiguration = new LoggingLevelConfiguration(); + + _configuration.Bind("Logging:LogLevel", logLevelConfiguration); + + var homeModel = new HomeModel + { + DefaultLogLevel = logLevelConfiguration.Default + }; + + return View(homeModel); + } + + public IActionResult Privacy() => View(); + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() => + View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Models/ErrorViewModel.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Models/ErrorViewModel.cs new file mode 100644 index 0000000000..d3daed82d7 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Models/ErrorViewModel.cs @@ -0,0 +1,8 @@ +namespace ProjectConfigurationDemo.Models; + +public class ErrorViewModel +{ + public string? RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Models/HomeModel.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Models/HomeModel.cs new file mode 100644 index 0000000000..9c6b9346c4 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Models/HomeModel.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Models; + +public class HomeModel +{ + public string? DefaultLogLevel { get; set; } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs new file mode 100644 index 0000000000..9cc59d60f1 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Models; + +public class LoggingLevelConfiguration +{ + public string? Default { get; set; } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Program.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Program.cs new file mode 100644 index 0000000000..65df0ebb42 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Program.cs @@ -0,0 +1,13 @@ +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllersWithViews(); + +var app = builder.Build(); + +app.MapStaticAssets(); +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}") + .WithStaticAssets(); + +app.Run(); diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj new file mode 100644 index 0000000000..a3a34b647c --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Properties/launchSettings.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Properties/launchSettings.json new file mode 100644 index 0000000000..92ac1b0576 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Home/Index.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Home/Index.cshtml new file mode 100644 index 0000000000..40b46f575f --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Home/Index.cshtml @@ -0,0 +1,9 @@ +@model HomeModel +@{ + ViewData["Title"] = "Home Page"; +} +
+

Welcome

+ Our default logging level is @Model.DefaultLogLevel +

Learn about building Web apps with ASP.NET Core.

+
diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Home/Privacy.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Home/Privacy.cshtml new file mode 100644 index 0000000000..d01477f389 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Home/Privacy.cshtml @@ -0,0 +1,6 @@ +@{ + ViewData["Title"] = "Privacy Policy"; +} +

@ViewData["Title"]

+ +

Use this page to detail your site's privacy policy.

diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Shared/Error.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Shared/Error.cshtml new file mode 100644 index 0000000000..8f923fdf49 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Shared/Error.cshtml @@ -0,0 +1,25 @@ +@model ErrorViewModel +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +@if (Model.ShowRequestId) +{ +

+ Request ID: @Model.RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the + ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app. +

diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000000..208bd9a352 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml @@ -0,0 +1,30 @@ + + + + + + @ViewData["Title"] - ProjectConfigurationDemo + + + +
+ +
+
+
+ @RenderBody() +
+
+ +
+ © @DateTime.Now.Year - ProjectConfigurationDemo - + Privacy +
+ + @await RenderSectionAsync("Scripts", required: false) + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/_ViewImports.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/_ViewImports.cshtml new file mode 100644 index 0000000000..92266eb487 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using ProjectConfigurationDemo +@using ProjectConfigurationDemo.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/_ViewStart.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/_ViewStart.cshtml new file mode 100644 index 0000000000..820a2f6e02 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/appsettings.Development.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/appsettings.Production.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/appsettings.Production.json new file mode 100644 index 0000000000..50c2698458 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/appsettings.Production.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "sqlConnection": "Server=ProductionServerName;Database=CodeMazeCommerce;Trusted_Connection=True;TrustServerCertificate=True" + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/appsettings.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/appsettings.json new file mode 100644 index 0000000000..079b7edb29 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/appsettings.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "ConnectionStrings": { + "sqlConnection": "Server=.;Database=CodeMazeCommerce;Trusted_Connection=True;TrustServerCertificate=True" + }, + "OtherLoggingProvider": { + "LogLevel": { + "Default": "Debug" + } + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/wwwroot/css/site.css b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/wwwroot/css/site.css new file mode 100644 index 0000000000..d78436d57f --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/wwwroot/css/site.css @@ -0,0 +1,49 @@ +html { + font-size: 14px; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; +} + +body { + margin: 0; + color: #212529; + background-color: #fff; +} + +header nav { + display: flex; + gap: 1rem; + padding: 1rem 1.5rem; + border-bottom: 1px solid #e5e5e5; +} + +header nav a { + color: #212529; + text-decoration: none; +} + +.container { + max-width: 960px; + margin: 0 auto; + padding: 1.5rem; +} + +.text-center { + text-align: center; +} + +.display-4 { + font-size: 3.5rem; + font-weight: 300; +} + +.text-danger { + color: #dc3545; +} + +footer { + max-width: 960px; + margin: 0 auto; + padding: 1rem 1.5rem; + border-top: 1px solid #e5e5e5; + color: #6c757d; +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/wwwroot/favicon.ico b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationBasics/ProjectConfigurationDemo/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a3a799985c43bc7309d701b2cad129023377dc71 GIT binary patch literal 32038 zcmeHwX>eTEbtY7aYbrGrkNjgie?1jXjZ#zP%3n{}GObKv$BxI7Sl;Bwl5E+Qtj&t8 z*p|m4DO#HoJC-FyvNnp8NP<{Na0LMnTtO21(rBP}?EAiNjWgeO?z`{3ZoURUQlV2d zY1Pqv{m|X_oO91|?^z!6@@~od!@OH>&BN;>c@O+yUfy5w>LccTKJJ&`-k<%M^Zvi( z<$dKp=jCnNX5Qa+M_%6g|IEv~4R84q9|7E=|Ho(Wz3f-0wPjaRL;W*N^>q%^KGRr7 zxbjSORb_c&eO;oV_DZ7ua!sPH=0c+W;`vzJ#j~-x3uj};50#vqo*0w4!LUqs*UCh9 zvy2S%$#8$K4EOa&e@~aBS65_hc~Mpu=454VT2^KzWqEpBA=ME|O;1cn?8p<+{MKJf zbK#@1wzL44m$k(?85=Obido7=C|xWKe%66$z)NrzRwR>?hK?_bbwT z@Da?lBrBL}Zemo1@!9pYRau&!ld17h{f+UV0sY(R{ET$PBB|-=Nr@l-nY6w8HEAw* zRMIQU`24Jl_IFEPcS=_HdrOP5yf81z_?@M>83Vv65$QFr9nPg(wr`Ke8 zaY4ogdnMA*F7a4Q1_uXadTLUpCk;$ZPRRJ^sMOch;rlbvUGc1R9=u;dr9YANbQ<4Z z#P|Cp9BP$FXNPolgyr1XGt$^lFPF}rmBF5rj1Kh5%dforrP8W}_qJL$2qMBS-#%-|s#BPZBSETsn_EBYcr(W5dq( z@f%}C|iN7)YN`^)h7R?Cg}Do*w-!zwZb9=BMp%Wsh@nb22hA zA{`wa8Q;yz6S)zfo%sl08^GF`9csI9BlGnEy#0^Y3b);M+n<(}6jziM7nhe57a1rj zC@(2ISYBL^UtWChKzVWgf%4LW2Tqg_^7jMw`C$KvU+mcakFjV(BGAW9g%CzSyM;Df z143=mq0oxaK-H;o>F3~zJ<(3-j&?|QBn)WJfP#JR zRuA;`N?L83wQt78QIA$(Z)lGQY9r^SFal;LB^qi`8%8@y+mwcGsf~nv)bBy2S7z~9 z=;X@Gglk)^jpbNz?1;`!J3QUfAOp4U$Uxm5>92iT`mek#$>s`)M>;e4{#%HAAcb^8_Ax%ersk|}# z0bd;ZPu|2}18KtvmIo8`1@H~@2ejwo(5rFS`Z4&O{$$+ch2hC0=06Jh`@p+p8LZzY z&2M~8T6X^*X?yQ$3N5EzRv$(FtSxhW>>ABUyp!{484f8(%C1_y)3D%Qgfl_!sz`LTXOjR&L!zPA0qH_iNS!tY{!^2WfD%uT}P zI<~&?@&))5&hPPHVRl9);TPO>@UI2d!^ksb!$9T96V(F){puTsn(}qt_WXNw4VvHj zf;6A_XCvE`Z@}E-IOaG0rs>K>^=Sr&OgT_p;F@v0VCN0Y$r|Lw1?Wjt`AKK~RT*kJ z2>QPuVgLNcF+XKno;WBv$yj@d_WFJbl*#*V_Cwzo@%3n5%z4g21G*PVZ)wM5$A{klYozmGlB zT@u2+s}=f}25%IA!yNcXUr!!1)z(Nqbhojg0lv@7@0UlvUMT)*r;M$d0-t)Z?B1@qQk()o!4fqvfr_I0r7 zy1(NdkHEj#Yu{K>T#We#b#FD=c1XhS{hdTh9+8gy-vkcdkk*QS@y(xxEMb1w6z<^~ zYcETGfB#ibR#ql0EiD;PR$L&Vrh2uRv5t_$;NxC;>7_S5_OXxsi8udY3BUUdi55Sk zcyKM+PQ9YMA%D1kH1q48OFG(Gbl=FmV;yk8o>k%0$rJ8%-IYsHclnYuTskkaiCGkUlkMY~mx&K}XRlKIW;odWIeuKjtbc^8bBOTqK zjj(ot`_j?A6y_h%vxE9o*ntx#PGrnK7AljD_r58ylE*oy@{IY%+mA^!|2vW_`>`aC{#3`#3;D_$^S^cM zRcF+uTO2sICledvFgNMU@A%M)%8JbSLq{dD|2|2Sg8vvh_uV6*Q?F&rKaV{v_qz&y z`f;stIb?Cb2!Cg7CG91Bhu@D@RaIrq-+o+T2fwFu#|j>lD6ZS9-t^5cx>p|?flqUA z;Cgs#V)O#`Aw4$Kr)L5?|7f4izl!;n0jux}tEW$&&YBXz9o{+~HhoiYDJ`w5BVTl&ARya=M7zdy$FEe}iGBur8XE>rhLj&_yDk5D4n2GJZ07u7%zyAfNtOLn;)M?h*Py-Xtql5aJOtL4U8e|!t? z((sc6&OJXrPdVef^wZV&x=Z&~uA7^ix8rly^rEj?#d&~pQ{HN8Yq|fZ#*bXn-26P^ z5!)xRzYO9{u6vx5@q_{FE4#7BipS#{&J7*>y}lTyV94}dfE%Yk>@@pDe&F7J09(-0|wuI|$of-MRfK51#t@t2+U|*s=W; z!Y&t{dS%!4VEEi$efA!#<<7&04?kB}Soprd8*jYv;-Qj~h~4v>{XX~kjF+@Z7<t?^|i z#>_ag2i-CRAM8Ret^rZt*^K?`G|o>1o(mLkewxyA)38k93`<~4VFI?5VB!kBh%NNU zxb8K(^-MU1ImWQxG~nFB-Un;6n{lQz_FfsW9^H$Xcn{;+W^ZcG$0qLM#eNV=vGE@# z1~k&!h4@T|IiI<47@pS|i?Qcl=XZJL#$JKve;booMqDUYY{(xcdj6STDE=n?;fsS1 ze`h~Q{CT$K{+{t+#*I1=&&-UU8M&}AwAxD-rMa=e!{0gQXP@6azBq9(ji11uJF%@5 zCvV`#*?;ZguQ7o|nH%bm*s&jLej#@B35gy32ZAE0`Pz@#j6R&kN5w{O4~1rhDoU zEBdU)%Nl?8zi|DR((u|gg~r$aLYmGMyK%FO*qLvwxK5+cn*`;O`16c!&&XT{$j~5k zXb^fbh1GT-CI*Nj{-?r7HNg=e3E{6rxuluPXY z5Nm8ktc$o4-^SO0|Es_sp!A$8GVwOX+%)cH<;=u#R#nz;7QsHl;J@a{5NUAmAHq4D zIU5@jT!h?kUp|g~iN*!>jM6K!W5ar0v~fWrSHK@})@6Lh#h)C6F6@)&-+C3(zO! z8+kV|B7LctM3DpI*~EYo>vCj>_?x&H;>y0*vKwE0?vi$CLt zfSJB##P|M2dEUDBPKW=9cY-F;L;h3Fs4E2ERdN#NSL7ctAC z?-}_a{*L@GA7JHJudxtDVA{K5Yh*k(%#x4W7w+^ zcb-+ofbT5ieG+@QG2lx&7!MyE2JWDP@$k`M;0`*d+oQmJ2A^de!3c53HFcfW_Wtv< zKghQ;*FifmI}kE4dc@1y-u;@qs|V75Z^|Q0l0?teobTE8tGl@EB?k#q_wUjypJ*R zyEI=DJ^Z+d*&}B_xoWvs27LtH7972qqMxVFcX9}c&JbeNCXUZM0`nQIkf&C}&skSt z^9fw@b^Hb)!^hE2IJq~~GktG#ZWwWG<`@V&ckVR&r=JAO4YniJewVcG`HF;59}=bf zLyz0uxf6MhuSyH#-^!ZbHxYl^mmBVrx) zyrb8sQ*qBd_WXm9c~Of$&ZP$b^)<~0%nt#7y$1Jg$e}WCK>TeUB{P>|b1FAB?%K7>;XiOfd}JQ`|IP#Vf%kVy zXa4;XFZ+>n;F>uX&3|4zqWK2u3c<>q;tzjsb1;d{u;L$-hq3qe@82(ob<3qom#%`+ z;vzYAs7TIMl_O75BXu|r`Qhc4UT*vN$3Oo0kAC!{f2#HexDy|qUpgTF;k{o6|L>7l z=?`=*LXaow1o;oNNLXsGTrvC)$R&{m=94Tf+2iTT3Y_Or z-!;^0a{kyWtO4vksG_3cyc7HQ0~detf0+2+qxq(e1NS251N}w5iTSrM)`0p8rem!j zZ56hGD=pHI*B+dd)2B`%|9f0goozCSeXPw3 z+58k~sI02Yz#lOneJzYcG)EB0|F+ggC6D|B`6}d0khAK-gz7U3EGT|M_9$ZINqZjwf>P zJCZ=ogSoE`=yV5YXrcTQZx@Un(64*AlLiyxWnCJ9I<5Nc*eK6eV1Mk}ci0*NrJ=t| zCXuJG`#7GBbPceFtFEpl{(lTm`LX=B_!H+& z>$*Hf}}y zkt@nLXFG9%v**s{z&{H4e?aqp%&l#oU8lxUxk2o%K+?aAe6jLojA& z_|J0<-%u^<;NT*%4)n2-OdqfctSl6iCHE?W_Q2zpJken#_xUJlidzs249H=b#g z?}L4-Tnp6)t_5X?_$v)vz`s9@^BME2X@w<>sKZ3=B{%*B$T5Nj%6!-Hr;I!Scj`lH z&2dHFlOISwWJ&S2vf~@I4i~(0*T%OFiuX|eD*nd2utS4$1_JM?zmp>a#CsVy6Er^z zeNNZZDE?R3pM?>~e?H_N`C`hy%m4jb;6L#8=a7l>3eJS2LGgEUxsau-Yh9l~o7=Yh z2mYg3`m5*3Ik|lKQf~euzZlCWzaN&=vHuHtOwK!2@W6)hqq$Zm|7`Nmu%9^F6UH?+ z@2ii+=iJ;ZzhiUKu$QB()nKk3FooI>Jr_IjzY6=qxYy;&mvi7BlQ?t4kRjIhb|2q? zd^K~{-^cxjVSj?!Xs=Da5IHmFzRj!Kzh~b!?`P7c&T9s77VLYB?8_?F zauM^)p;qFG!9PHLfIsnt43UnmV?Wn?Ki7aXSosgq;f?MYUuSIYwOn(5vWhb{f%$pn z4ySN-z}_%7|B);A@PA5k*7kkdr4xZ@s{e9j+9w;*RFm;XPDQwx%~;8iBzSKTIGKO z{53ZZU*OLr@S5=k;?CM^i#zkxs3Sj%z0U`L%q`qM+tP zX$aL;*^g$7UyM2Go+_4A+f)IQcy^G$h2E zb?nT$XlgTEFJI8GN6NQf%-eVn9mPilRqUbT$pN-|;FEjq@Ao&TxpZg=mEgBHB zU@grU;&sfmqlO=6|G3sU;7t8rbK$?X0y_v9$^{X`m4jZ_BR|B|@?ZCLSPPEzz`w1n zP5nA;4(kQFKm%$enjkkBxM%Y}2si&d|62L)U(dCzCGn56HN+i#6|nV-TGIo0;W;`( zW-y=1KF4dp$$mC_|6}pbb>IHoKQeZajXQB>jVR?u`R>%l1o54?6NnS*arpVopdEF; zeC5J3*M0p`*8lif;!irrcjC?(uExejsi~>4wKYwstGY^N@KY}TujLx`S=Cu+T=!dx zKWlPm->I**E{A*q-Z^FFT5$G%7Ij0_*Mo4-y6~RmyTzUB&lfae(WZfO>um}mnsDXPEbau-!13!!xd!qh*{C)6&bz0j1I{>y$D-S)b*)JMCPk!=~KL&6Ngin0p6MCOxF2L_R9t8N!$2Wpced<#`y!F;w zKTi5V_kX&X09wAIJ#anfg9Dhn0s7(C6Nj3S-mVn(i|C6ZAVq0$hE)874co};g z^hR7pe4lU$P;*ggYc4o&UTQC%liCXooIfkI3TNaBV%t~FRr}yHu7kjQ2J*3;e%;iW zvDVCh8=G80KAeyhCuY2LjrC!Od1rvF7h}zszxGV)&!)6ChP5WAjv-zQAMNJIG!JHS zwl?pLxC-V5II#(hQ`l)ZAp&M0xd4%cxmco*MIk?{BD=BK`1vpc}D39|XlV z{c&0oGdDa~TL2FT4lh=~1NL5O-P~0?V2#ie`v^CnANfGUM!b4F=JkCwd7Q`c8Na2q zJGQQk^?6w}Vg9-{|2047((lAV84uN%sK!N2?V(!_1{{v6rdgZl56f0zDMQ+q)jKzzu^ztsVken;=DjAh6G`Cw`Q4G+BjS+n*=KI~^K{W=%t zbD-rN)O4|*Q~@<#@1Vx$E!0W9`B~IZeFn87sHMXD>$M%|Bh93rdGf1lKoX3K651t&nhsl= zXxG|%@8}Bbrlp_u#t*DZX<}_0Yb{A9*1Pd_)LtqNwy6xT4pZrOY{s?N4)pPwT(i#y zT%`lRi8U#Ken4fw>H+N`{f#FF?ZxFlLZg7z7#cr4X>id z{9kUD`d2=w_Zlb{^c`5IOxWCZ1k<0T1D1Z31IU0Q2edsZ1K0xv$pQVYq2KEp&#v#Z z?{m@Lin;*Str(C2sfF^L>{R3cjY`~#)m>Wm$Y|1fzeS0-$(Q^z@} zEO*vlb-^XK9>w&Ef^=Zzo-1AFSP#9zb~X5_+){$(eB4K z8gtW+nl{q+CTh+>v(gWrsP^DB*ge(~Q$AGxJ-eYc1isti%$%nM<_&Ev?%|??PK`$p z{f-PM{Ym8k<$$)(F9)tqzFJ?h&Dk@D?Dt{4CHKJWLs8$zy6+(R)pr@0ur)xY{=uXFFzH_> z-F^tN1y(2hG8V)GpDg%wW0Px_ep~nIjD~*HCSxDi0y`H!`V*~RHs^uQsb1*bK1qGpmd zB1m`Cjw0`nLBF2|umz+a#2X$c?Lj;M?Lj;MUp*d>7j~ayNAyj@SLpeH`)BgRH}byy zyQSat!;U{@O(<<2fp&oQkIy$z`_CQ-)O@RN;QD9T4y|wIJ^%U#(BF%=`i49}j!D-) zkOwPSJaG03SMkE~BzW}b_v>LA&y)EEYO6sbdnTX*$>UF|JhZ&^MSb4}Tgbne_4n+C zwI8U4i~PI>7a3{kVa8|))*%C0|K+bIbmV~a`|G#+`TU#g zXW;bWIcWsQi9c4X*RUDpIfyoPY)2bI-r9)xulm1CJDkQd6u+f)_N=w1ElgEBjprPF z3o?Ly0RVeY_{3~fPVckRMxe2lM8hj!B8F)JO z!`AP6>u>5Y&3o9t0QxBpNE=lJx#NyIbp1gD zzUYBIPYHIv9ngk-Zt~<)62^1Zs1LLYMh@_tP^I7EX-9)Ed0^@y{k65Gp0KRcTmMWw zU|+)qx{#q0SL+4q?Q`i0>COIIF8a0Cf&C`hbMj?LmG9K&iW-?PJt*u)38tTXAP>@R zZL6uH^!RYNq$p>PKz7f-zvg>OKXcZ8h!%Vo@{VUZp|+iUD_xb(N~G|6c#oQK^nHZU zKg#F6<)+`rf~k*Xjjye+syV{bwU2glMMMs-^ss4`bYaVroXzn`YQUd__UlZL_mLs z(vO}k!~(mi|L+(5&;>r<;|OHnbXBE78LruP;{yBxZ6y7K3)nMo-{6PCI7gQi6+rF_ zkPod!Z8n}q46ykrlQS|hVB(}(2Kf7BCZ>Vc;V>ccbk2~NGaf6wGQH@W9&?Zt3v(h*P4xDrN>ex7+jH*+Qg z%^jH$&+*!v{sQ!xkWN4+>|b}qGvEd6ANzgqoVy5Qfws}ef2QqF{iiR5{pT}PS&yjo z>lron#va-p=v;m>WB+XVz|o;UJFdjo5_!RRD|6W{4}A2a#bZv)gS_`b|KsSH)Sd_JIr%<%n06TX&t{&!H#{)?4W9hlJ`R1>FyugOh3=D_{einr zu(Wf`qTkvED+gEULO0I*Hs%f;&=`=X4;N8Ovf28x$A*11`dmfy2=$+PNqX>XcG`h% zJY&A6@&)*WT^rC(Caj}2+|X|6cICm5h0OK0cGB_!wEKFZJU)OQ+TZ1q2bTx9hxnq& z$9ee|f9|0M^)#E&Pr4)f?o&DMM4w>Ksb{hF(0|wh+5_{vPow{V%TFzU2za&gjttNi zIyR9qA56dX52Qbv2aY^g`U7R43-p`#sO1A=KS2aKgfR+Yu^bQ*i-qu z%0mP;Ap)B~zZgO9lG^`325gOf?iUHF{~7jyGC)3L(eL(SQ70VzR~wLN18tnx(Cz2~ zctBl1kI)wAe+cxWHw*NW-d;=pd+>+wd$a@GBju*wFvabSaPtHiT!o#QFC+wBVwYo3s=y;z1jM+M=Fj!FZM>UzpL-eZzOT( zhmZmEfWa=%KE#V3-ZK5#v!Hzd{zc^{ctF~- z>DT-U`}5!fk$aj24`#uGdB7r`>oX5tU|d*b|N3V1lXmv%MGrvE(dXG)^-J*LA>$LE z7kut4`zE)v{@Op|(|@i#c>tM!12FQh?}PfA0`Bp%=%*RiXVzLDXnXtE@4B)5uR}a> zbNU}q+712pIrM`k^odG8dKtG$zwHmQI^c}tfjx5?egx3!e%JRm_64e+>`Ra1IRfLb z1KQ`SxmH{cZfyVS5m(&`{V}Y4j6J{b17`h6KWqZ&hfc(oR zxM%w!$F(mKy05kY&lco3%zvLCxBW+t*rxO+i=qGMvobx0-<7`VUu)ka`){=ew+Ovt zg%52_{&UbkUA8aJPWsk)gYWV4`dnxI%s?7^fGpq{ZQuu=VH{-t7w~K%_E<8`zS;V- zKTho*>;UQQul^1GT^HCt@I-q?)&4!QDgBndn?3sNKYKCQFU4LGKJ$n@Je$&w9@E$X z^p@iJ(v&`1(tq~1zc>0Vow-KR&vm!GUzT?Eqgnc)leZ9p)-Z*C!zqb=-$XG0 z^!8RfuQs5s>Q~qcz92(a_Q+KH?C*vCTr~UdTiR`JGuNH8v(J|FTiSEcPrBpmHRtmd zI2Jng0J=bXK);YY^rM?jzn?~X-Pe`GbAy{D)Y6D&1GY-EBcy%Bq?bKh?A>DD9DD!p z?{q02wno2sraGUkZv5dx+J8)&K$)No43Zr(*S`FEdL!4C)}WE}vJd%{S6-3VUw>Wp z?Aasv`T0^%P$2vE?L+Qhj~qB~K%eW)xH(=b_jU}TLD&BP*Pc9hz@Z=e0nkpLkWl}> z_5J^i(9Z7$(XG9~I3sY)`OGZ#_L06+Dy4E>UstcP-rU@xJ$&rxvo!n1Ao`P~KLU-8 z{zDgN4-&A6N!kPSYbQ&7sLufi`YtE2uN$S?e&5n>Y4(q#|KP!cc1j)T^QrUXMPFaP z_SoYO8S8G}Z$?AL4`;pE?7J5K8yWqy23>cCT2{=-)+A$X^-I9=e!@J@A&-;Ufc)`H}c(VI&;0x zrrGv()5mjP%jXzS{^|29?bLNXS0bC%p!YXI!;O457rjCEEzMkGf~B3$T}dXBO23tP z+Ci>;5UoM?C@bU@f9G1^X3=ly&ZeFH<@|RnOG--A&)fd)AUgjw?%izq{p(KJ`EP0v z2mU)P!+3t@X14DA=E2RR-|p${GZ9ETX=d+kJRZL$nSa0daI@&oUUxnZg0xd_xu>Vz lzF#z5%kSKX?YLH3ll^(hI(_`L*t#Iva2Ede*Z;>H_ + + + net10.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ConfigurationProviders.Tests/ProviderOrderTests.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ConfigurationProviders.Tests/ProviderOrderTests.cs new file mode 100644 index 0000000000..16982ced01 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ConfigurationProviders.Tests/ProviderOrderTests.cs @@ -0,0 +1,100 @@ +using Microsoft.Extensions.Configuration; + +namespace ConfigurationProviders.Tests; + +public class ProviderOrderTests : IDisposable +{ + private readonly string _directory = + Directory.CreateTempSubdirectory("ConfigurationProvidersTests").FullName; + + private string WriteJson(string level) + { + var path = Path.Combine(_directory, $"appsettings.{Guid.NewGuid():N}.json"); + File.WriteAllText(path, "{\"Logging\":{\"LogLevel\":{\"Default\":\"" + level + "\"}}}"); + return path; + } + + private string WriteIni(string level) + { + var path = Path.Combine(_directory, $"appsettings.{Guid.NewGuid():N}.ini"); + File.WriteAllText(path, $"[Logging:LogLevel]{Environment.NewLine}Default={level}{Environment.NewLine}"); + return path; + } + + // The article's central claim, executable: when two sources define the same + // key, the one added last wins. + [Fact] + public void GivenJsonThenIni_WhenBothDefineTheSameKey_ThenTheIniValueWins() + { + var json = WriteJson("Information"); + var ini = WriteIni("Debug"); + + var configuration = new ConfigurationBuilder() + .AddJsonFile(json) + .AddIniFile(ini) + .Build(); + + Assert.Equal("Debug", configuration["Logging:LogLevel:Default"]); + } + + [Fact] + public void GivenIniThenJson_WhenBothDefineTheSameKey_ThenTheJsonValueWins() + { + var json = WriteJson("Information"); + var ini = WriteIni("Debug"); + + var configuration = new ConfigurationBuilder() + .AddIniFile(ini) + .AddJsonFile(json) + .Build(); + + Assert.Equal("Information", configuration["Logging:LogLevel:Default"]); + } + + // The repo's appsettings.ini carried the header [Logging:Level], which writes + // a key the logging system never reads. The corrected header is the one that + // actually overrides the JSON value. + [Fact] + public void GivenTheCorrectedIniSectionHeader_WhenTheKeyIsRead_ThenItLandsOnTheLoggingKeyPath() + { + var ini = Path.Combine(_directory, $"header.{Guid.NewGuid():N}.ini"); + File.WriteAllText(ini, + $"[Logging:LogLevel]{Environment.NewLine}Default=Warning{Environment.NewLine}"); + + var configuration = new ConfigurationBuilder().AddIniFile(ini).Build(); + + Assert.Equal("Warning", configuration["Logging:LogLevel:Default"]); + Assert.Null(configuration["Logging:Level:Default"]); + } + + [Fact] + public void GivenEnvironmentVariables_WhenTheDoubleUnderscoreSeparatorIsUsed_ThenItBecomesAKeyPath() + { + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { ["Logging:LogLevel:Default"] = "Information" }) + .AddEnvironmentVariables() + .Build(); + + Environment.SetEnvironmentVariable("Logging__LogLevel__Default", "Critical"); + try + { + var rebuilt = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { ["Logging:LogLevel:Default"] = "Information" }) + .AddEnvironmentVariables() + .Build(); + + Assert.Equal("Information", configuration["Logging:LogLevel:Default"]); + Assert.Equal("Critical", rebuilt["Logging:LogLevel:Default"]); + } + finally + { + Environment.SetEnvironmentVariable("Logging__LogLevel__Default", null); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + try { Directory.Delete(_directory, recursive: true); } catch (IOException) { } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo.sln b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo.sln new file mode 100644 index 0000000000..c74392b262 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectConfigurationDemo", "ProjectConfigurationDemo\ProjectConfigurationDemo.csproj", "{85175962-4C37-48FC-A151-5726F000290C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigurationProviders.Tests", "ConfigurationProviders.Tests\ConfigurationProviders.Tests.csproj", "{6C1033FF-D148-43F5-B436-6AAABBBF1456}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {85175962-4C37-48FC-A151-5726F000290C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85175962-4C37-48FC-A151-5726F000290C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85175962-4C37-48FC-A151-5726F000290C}.Debug|x64.ActiveCfg = Debug|Any CPU + {85175962-4C37-48FC-A151-5726F000290C}.Debug|x64.Build.0 = Debug|Any CPU + {85175962-4C37-48FC-A151-5726F000290C}.Debug|x86.ActiveCfg = Debug|Any CPU + {85175962-4C37-48FC-A151-5726F000290C}.Debug|x86.Build.0 = Debug|Any CPU + {85175962-4C37-48FC-A151-5726F000290C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85175962-4C37-48FC-A151-5726F000290C}.Release|Any CPU.Build.0 = Release|Any CPU + {85175962-4C37-48FC-A151-5726F000290C}.Release|x64.ActiveCfg = Release|Any CPU + {85175962-4C37-48FC-A151-5726F000290C}.Release|x64.Build.0 = Release|Any CPU + {85175962-4C37-48FC-A151-5726F000290C}.Release|x86.ActiveCfg = Release|Any CPU + {85175962-4C37-48FC-A151-5726F000290C}.Release|x86.Build.0 = Release|Any CPU + {6C1033FF-D148-43F5-B436-6AAABBBF1456}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C1033FF-D148-43F5-B436-6AAABBBF1456}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C1033FF-D148-43F5-B436-6AAABBBF1456}.Debug|x64.ActiveCfg = Debug|Any CPU + {6C1033FF-D148-43F5-B436-6AAABBBF1456}.Debug|x64.Build.0 = Debug|Any CPU + {6C1033FF-D148-43F5-B436-6AAABBBF1456}.Debug|x86.ActiveCfg = Debug|Any CPU + {6C1033FF-D148-43F5-B436-6AAABBBF1456}.Debug|x86.Build.0 = Debug|Any CPU + {6C1033FF-D148-43F5-B436-6AAABBBF1456}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C1033FF-D148-43F5-B436-6AAABBBF1456}.Release|Any CPU.Build.0 = Release|Any CPU + {6C1033FF-D148-43F5-B436-6AAABBBF1456}.Release|x64.ActiveCfg = Release|Any CPU + {6C1033FF-D148-43F5-B436-6AAABBBF1456}.Release|x64.Build.0 = Release|Any CPU + {6C1033FF-D148-43F5-B436-6AAABBBF1456}.Release|x86.ActiveCfg = Release|Any CPU + {6C1033FF-D148-43F5-B436-6AAABBBF1456}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs new file mode 100644 index 0000000000..ca47852084 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; + +namespace ProjectConfigurationDemo.ConfigurationValidation; + +public class TitleConfigurationValidation : IValidateOptions +{ + private readonly string[] _colors = ["red", "green", "blue", "black", "purple", "yellow", "brown", "pink"]; + + public ValidateOptionsResult Validate(string? name, TitleConfiguration options) + { + var builder = new ValidateOptionsResultBuilder(); + + if (string.IsNullOrEmpty(options.WelcomeMessage) || options.WelcomeMessage.Length > 60) + builder.AddError("Welcome message must be defined and it must be less than 60 characters long.", + nameof(options.WelcomeMessage)); + + if (!_colors.Contains(options.Color)) + builder.AddError($"Provided title color '{options.Color}' is not among allowed colors.", + nameof(options.Color)); + + return builder.Build(); + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Controllers/HomeController.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Controllers/HomeController.cs new file mode 100644 index 0000000000..dd426ae2aa --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Controllers/HomeController.cs @@ -0,0 +1,35 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; +using ProjectConfigurationDemo.Services; + +namespace ProjectConfigurationDemo.Controllers; + +public class HomeController( + ILogger logger, + IOptionsSnapshot homePageTitleConfiguration, + ITitleColorService titleColorService) : Controller +{ + private readonly ILogger _logger = logger; + private readonly TitleConfiguration _homePageTitleConfiguration = homePageTitleConfiguration.Value; + private readonly ITitleColorService _titleColorService = titleColorService; + + public IActionResult Index() + { + var homeModel = new HomeModel + { + Configuration = _homePageTitleConfiguration + }; + + homeModel.Configuration.Color = _titleColorService.GetTitleColor(); + + return View(homeModel); + } + + public IActionResult Privacy() => View(); + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() => + View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/ErrorViewModel.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/ErrorViewModel.cs new file mode 100644 index 0000000000..d3daed82d7 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/ErrorViewModel.cs @@ -0,0 +1,8 @@ +namespace ProjectConfigurationDemo.Models; + +public class ErrorViewModel +{ + public string? RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/HomeModel.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/HomeModel.cs new file mode 100644 index 0000000000..7838fe8088 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/HomeModel.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Models; + +public class HomeModel +{ + public TitleConfiguration Configuration { get; set; } = new(); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs new file mode 100644 index 0000000000..9cc59d60f1 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Models; + +public class LoggingLevelConfiguration +{ + public string? Default { get; set; } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/TitleConfiguration.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/TitleConfiguration.cs new file mode 100644 index 0000000000..cf0cb49713 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Models/TitleConfiguration.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace ProjectConfigurationDemo.Models; + +public class TitleConfiguration +{ + [Required] + [MaxLength(60)] + public string WelcomeMessage { get; set; } = string.Empty; + + public bool ShowWelcomeMessage { get; set; } + + public string Color { get; set; } = string.Empty; + + public bool UseRandomTitleColor { get; set; } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Program.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Program.cs new file mode 100644 index 0000000000..946c4987b3 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Program.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.ConfigurationValidation; +using ProjectConfigurationDemo.Models; +using ProjectConfigurationDemo.Services; + +var builder = WebApplication.CreateBuilder(args); + +builder.Configuration + .AddIniFile("appsettings.ini", optional: true, reloadOnChange: true) + .AddIniFile($"appsettings.{builder.Environment.EnvironmentName}.ini", optional: true, reloadOnChange: true); + +builder.Services + .AddOptionsWithValidateOnStart() + .Bind(builder.Configuration.GetSection("Pages:HomePage")) + .ValidateDataAnnotations(); + +builder.Services.TryAddEnumerable( + ServiceDescriptor.Singleton, TitleConfigurationValidation>()); + +builder.Services.TryAddSingleton(); + +builder.Services.AddControllersWithViews(); + +var app = builder.Build(); + +app.MapStaticAssets(); +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}") + .WithStaticAssets(); + +app.Run(); diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj new file mode 100644 index 0000000000..fca27dbddb --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + enable + enable + d5a08119-f24b-42c7-a729-38895bb7b067 + + + + + + + + + + + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Properties/launchSettings.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Properties/launchSettings.json new file mode 100644 index 0000000000..92ac1b0576 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Services/ITitleColorService.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Services/ITitleColorService.cs new file mode 100644 index 0000000000..2ee1036bf8 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Services/ITitleColorService.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Services; + +public interface ITitleColorService +{ + string GetTitleColor(); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Services/TitleColorService.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Services/TitleColorService.cs new file mode 100644 index 0000000000..1e95304883 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Services/TitleColorService.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; + +namespace ProjectConfigurationDemo.Services; + +public class TitleColorService(IOptionsMonitor titleConfiguration) : ITitleColorService +{ + private readonly string[] _colors = ["red", "green", "blue", "black", "purple", "yellow", "brown", "pink"]; + private readonly IOptionsMonitor _titleConfiguration = titleConfiguration; + + public string GetTitleColor() + { + var configuration = _titleConfiguration.CurrentValue; + + return configuration.UseRandomTitleColor + ? _colors[Random.Shared.Next(_colors.Length)] + : configuration.Color; + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Home/Index.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Home/Index.cshtml new file mode 100644 index 0000000000..c286342c56 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Home/Index.cshtml @@ -0,0 +1,10 @@ +@model HomeModel +@{ + ViewData["Title"] = "Home Page"; +} +
+ @if (Model.Configuration.ShowWelcomeMessage) + { +

@Model.Configuration.WelcomeMessage

+ } +
diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Home/Privacy.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Home/Privacy.cshtml new file mode 100644 index 0000000000..d01477f389 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Home/Privacy.cshtml @@ -0,0 +1,6 @@ +@{ + ViewData["Title"] = "Privacy Policy"; +} +

@ViewData["Title"]

+ +

Use this page to detail your site's privacy policy.

diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Shared/Error.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Shared/Error.cshtml new file mode 100644 index 0000000000..8f923fdf49 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Shared/Error.cshtml @@ -0,0 +1,25 @@ +@model ErrorViewModel +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +@if (Model.ShowRequestId) +{ +

+ Request ID: @Model.RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the + ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app. +

diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000000..208bd9a352 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml @@ -0,0 +1,30 @@ + + + + + + @ViewData["Title"] - ProjectConfigurationDemo + + + +
+ +
+
+
+ @RenderBody() +
+
+ +
+ © @DateTime.Now.Year - ProjectConfigurationDemo - + Privacy +
+ + @await RenderSectionAsync("Scripts", required: false) + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/_ViewImports.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/_ViewImports.cshtml new file mode 100644 index 0000000000..92266eb487 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using ProjectConfigurationDemo +@using ProjectConfigurationDemo.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/_ViewStart.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/_ViewStart.cshtml new file mode 100644 index 0000000000..820a2f6e02 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.Development.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.Production.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.Production.json new file mode 100644 index 0000000000..50c2698458 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.Production.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "sqlConnection": "Server=ProductionServerName;Database=CodeMazeCommerce;Trusted_Connection=True;TrustServerCertificate=True" + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.ini b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.ini new file mode 100644 index 0000000000..5db26e0df2 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.ini @@ -0,0 +1,7 @@ +[Logging:LogLevel] +Default=Information +Microsoft=Warning +Microsoft.Hosting.Lifetime=Information + +[ConnectionStrings] +sqlConnection=Server=.;Database=CodeMazeCommerce;Trusted_Connection=True;TrustServerCertificate=True diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.json new file mode 100644 index 0000000000..1310f75cab --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/appsettings.json @@ -0,0 +1,21 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "ConnectionStrings": { + "sqlConnection": "Server=.;Database=CodeMazeCommerce;Trusted_Connection=True;TrustServerCertificate=True" + }, + "Pages": { + "HomePage": { + "WelcomeMessage": "Welcome to the ProjectConfigurationDemo Home Page", + "ShowWelcomeMessage": true, + "Color": "black", + "UseRandomTitleColor": true + } + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/wwwroot/css/site.css b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/wwwroot/css/site.css new file mode 100644 index 0000000000..d78436d57f --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/wwwroot/css/site.css @@ -0,0 +1,49 @@ +html { + font-size: 14px; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; +} + +body { + margin: 0; + color: #212529; + background-color: #fff; +} + +header nav { + display: flex; + gap: 1rem; + padding: 1rem 1.5rem; + border-bottom: 1px solid #e5e5e5; +} + +header nav a { + color: #212529; + text-decoration: none; +} + +.container { + max-width: 960px; + margin: 0 auto; + padding: 1.5rem; +} + +.text-center { + text-align: center; +} + +.display-4 { + font-size: 3.5rem; + font-weight: 300; +} + +.text-danger { + color: #dc3545; +} + +footer { + max-width: 960px; + margin: 0 auto; + padding: 1rem 1.5rem; + border-top: 1px solid #e5e5e5; + color: #6c757d; +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/wwwroot/favicon.ico b/aspnetcore-webapi/AspNetCoreConfigurationSeries/ConfigurationProviders/ProjectConfigurationDemo/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a3a799985c43bc7309d701b2cad129023377dc71 GIT binary patch literal 32038 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 + + + net10.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/CustomConfigurationProvider.Tests/EFConfigurationProviderTests.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/CustomConfigurationProvider.Tests/EFConfigurationProviderTests.cs new file mode 100644 index 0000000000..53d83b77c7 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/CustomConfigurationProvider.Tests/EFConfigurationProviderTests.cs @@ -0,0 +1,76 @@ +using Microsoft.Extensions.Configuration; +using ProjectConfigurationDemo.Models; +using Testcontainers.MsSql; + +namespace CustomConfigurationProvider.Tests; + +public class EFConfigurationProviderTests : IAsyncLifetime +{ + private const string SqlServerImage = "mcr.microsoft.com/mssql/server:2022-latest"; + + private readonly MsSqlContainer _sqlServer = new MsSqlBuilder(SqlServerImage).Build(); + + public async Task InitializeAsync() + { + if (DockerAvailability.IsAvailable || DockerAvailability.IsRequired) + await _sqlServer.StartAsync(); + } + + public async Task DisposeAsync() + { + if (DockerAvailability.IsAvailable || DockerAvailability.IsRequired) + await _sqlServer.DisposeAsync(); + } + + private ConfigurationManager BuildConfiguration() + { + var manager = new ConfigurationManager(); + + ((IConfigurationBuilder)manager).AddInMemoryCollection(new Dictionary + { + ["ConnectionStrings:sqlConnection"] = _sqlServer.GetConnectionString() + }); + + manager.AddEntityConfiguration(); + + return manager; + } + + [RequiresDockerFact] + public void GivenAnEmptyDatabase_WhenTheProviderLoads_ThenItSeedsAndServesTheDefaultValues() + { + var configuration = BuildConfiguration(); + + Assert.Equal("Welcome to the ProjectConfigurationDemo Home Page", + configuration["Pages:HomePage:WelcomeMessage"]); + Assert.Equal("black", configuration["Pages:HomePage:Color"]); + Assert.Equal("true", configuration["Pages:HomePage:UseRandomTitleColor"]); + } + + // The second run is the one that used to be subtly broken: the seeding branch + // built a case-insensitive dictionary and the ToDictionary branch did not, so + // the demo passed on the first run and was case-sensitive on every run after. + [RequiresDockerFact] + public void GivenAnAlreadySeededDatabase_WhenTheProviderLoadsAgain_ThenKeyLookupStaysCaseInsensitive() + { + _ = BuildConfiguration(); + + var secondRun = BuildConfiguration(); + + Assert.Equal("black", secondRun["Pages:HomePage:Color"]); + Assert.Equal("black", secondRun["pages:homepage:color"]); + Assert.Equal("black", secondRun.GetSection("pages:homepage")["color"]); + } + + [RequiresDockerFact] + public void GivenTheProviderIsRegistered_WhenTheSectionIsBound_ThenTheOptionsClassIsPopulated() + { + var configuration = BuildConfiguration(); + + var titleConfiguration = configuration.GetSection("Pages:HomePage").Get(); + + Assert.NotNull(titleConfiguration); + Assert.Equal("Welcome to the ProjectConfigurationDemo Home Page", titleConfiguration.WelcomeMessage); + Assert.True(titleConfiguration.UseRandomTitleColor); + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/CustomConfigurationProvider.Tests/RequiresDockerFactAttribute.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/CustomConfigurationProvider.Tests/RequiresDockerFactAttribute.cs new file mode 100644 index 0000000000..e3f94bd403 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/CustomConfigurationProvider.Tests/RequiresDockerFactAttribute.cs @@ -0,0 +1,62 @@ +using System.IO.Pipes; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace CustomConfigurationProvider.Tests; + +/// +/// A [Fact] that is skipped when no Docker daemon is reachable, so the suite stays +/// green on a developer machine without Docker and really runs where a daemon is +/// available, starting a SQL Server container. Set +/// CONFIGURATION_SERIES_REQUIRE_DOCKER=1 to turn a missing daemon into a failure +/// instead of a skip. +/// +public sealed class RequiresDockerFactAttribute : FactAttribute +{ + public RequiresDockerFactAttribute() + { + if (DockerAvailability.IsAvailable || DockerAvailability.IsRequired) + return; + + Skip = "Docker is not reachable on this machine, so the SQL Server container cannot start."; + } +} + +public static class DockerAvailability +{ + private static readonly Lazy Probe = new(Detect); + + public static bool IsAvailable => Probe.Value; + + public static bool IsRequired => + Environment.GetEnvironmentVariable("CONFIGURATION_SERIES_REQUIRE_DOCKER") == "1"; + + // Connect to the daemon rather than looking for a socket or a pipe on disk: + // Docker Desktop leaves its named pipe behind when it is not running, so the + // cheaper check reports a daemon that is not there. + private static bool Detect() + { + if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("DOCKER_HOST"))) + return true; + + try + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + using var pipe = new NamedPipeClientStream(".", "docker_engine", PipeDirection.InOut); + pipe.Connect(timeout: 1000); + + return pipe.IsConnected; + } + + using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + socket.Connect(new UnixDomainSocketEndPoint("/var/run/docker.sock")); + + return socket.Connected; + } + catch (Exception) + { + return false; + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo.sln b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo.sln new file mode 100644 index 0000000000..588065f072 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectConfigurationDemo", "ProjectConfigurationDemo\ProjectConfigurationDemo.csproj", "{5EA1A2AB-57F4-45DB-992E-38153540DD19}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomConfigurationProvider.Tests", "CustomConfigurationProvider.Tests\CustomConfigurationProvider.Tests.csproj", "{27DEA374-3B60-4C1F-BD6C-F58ED7A9939F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5EA1A2AB-57F4-45DB-992E-38153540DD19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5EA1A2AB-57F4-45DB-992E-38153540DD19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5EA1A2AB-57F4-45DB-992E-38153540DD19}.Debug|x64.ActiveCfg = Debug|Any CPU + {5EA1A2AB-57F4-45DB-992E-38153540DD19}.Debug|x64.Build.0 = Debug|Any CPU + {5EA1A2AB-57F4-45DB-992E-38153540DD19}.Debug|x86.ActiveCfg = Debug|Any CPU + {5EA1A2AB-57F4-45DB-992E-38153540DD19}.Debug|x86.Build.0 = Debug|Any CPU + {5EA1A2AB-57F4-45DB-992E-38153540DD19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5EA1A2AB-57F4-45DB-992E-38153540DD19}.Release|Any CPU.Build.0 = Release|Any CPU + {5EA1A2AB-57F4-45DB-992E-38153540DD19}.Release|x64.ActiveCfg = Release|Any CPU + {5EA1A2AB-57F4-45DB-992E-38153540DD19}.Release|x64.Build.0 = Release|Any CPU + {5EA1A2AB-57F4-45DB-992E-38153540DD19}.Release|x86.ActiveCfg = Release|Any CPU + {5EA1A2AB-57F4-45DB-992E-38153540DD19}.Release|x86.Build.0 = Release|Any CPU + {27DEA374-3B60-4C1F-BD6C-F58ED7A9939F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27DEA374-3B60-4C1F-BD6C-F58ED7A9939F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27DEA374-3B60-4C1F-BD6C-F58ED7A9939F}.Debug|x64.ActiveCfg = Debug|Any CPU + {27DEA374-3B60-4C1F-BD6C-F58ED7A9939F}.Debug|x64.Build.0 = Debug|Any CPU + {27DEA374-3B60-4C1F-BD6C-F58ED7A9939F}.Debug|x86.ActiveCfg = Debug|Any CPU + {27DEA374-3B60-4C1F-BD6C-F58ED7A9939F}.Debug|x86.Build.0 = Debug|Any CPU + {27DEA374-3B60-4C1F-BD6C-F58ED7A9939F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27DEA374-3B60-4C1F-BD6C-F58ED7A9939F}.Release|Any CPU.Build.0 = Release|Any CPU + {27DEA374-3B60-4C1F-BD6C-F58ED7A9939F}.Release|x64.ActiveCfg = Release|Any CPU + {27DEA374-3B60-4C1F-BD6C-F58ED7A9939F}.Release|x64.Build.0 = Release|Any CPU + {27DEA374-3B60-4C1F-BD6C-F58ED7A9939F}.Release|x86.ActiveCfg = Release|Any CPU + {27DEA374-3B60-4C1F-BD6C-F58ED7A9939F}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs new file mode 100644 index 0000000000..ca47852084 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; + +namespace ProjectConfigurationDemo.ConfigurationValidation; + +public class TitleConfigurationValidation : IValidateOptions +{ + private readonly string[] _colors = ["red", "green", "blue", "black", "purple", "yellow", "brown", "pink"]; + + public ValidateOptionsResult Validate(string? name, TitleConfiguration options) + { + var builder = new ValidateOptionsResultBuilder(); + + if (string.IsNullOrEmpty(options.WelcomeMessage) || options.WelcomeMessage.Length > 60) + builder.AddError("Welcome message must be defined and it must be less than 60 characters long.", + nameof(options.WelcomeMessage)); + + if (!_colors.Contains(options.Color)) + builder.AddError($"Provided title color '{options.Color}' is not among allowed colors.", + nameof(options.Color)); + + return builder.Build(); + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Controllers/HomeController.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Controllers/HomeController.cs new file mode 100644 index 0000000000..dd426ae2aa --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Controllers/HomeController.cs @@ -0,0 +1,35 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; +using ProjectConfigurationDemo.Services; + +namespace ProjectConfigurationDemo.Controllers; + +public class HomeController( + ILogger logger, + IOptionsSnapshot homePageTitleConfiguration, + ITitleColorService titleColorService) : Controller +{ + private readonly ILogger _logger = logger; + private readonly TitleConfiguration _homePageTitleConfiguration = homePageTitleConfiguration.Value; + private readonly ITitleColorService _titleColorService = titleColorService; + + public IActionResult Index() + { + var homeModel = new HomeModel + { + Configuration = _homePageTitleConfiguration + }; + + homeModel.Configuration.Color = _titleColorService.GetTitleColor(); + + return View(homeModel); + } + + public IActionResult Privacy() => View(); + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() => + View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationDbContext.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationDbContext.cs new file mode 100644 index 0000000000..c8a57f0ebf --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationDbContext.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; + +namespace ProjectConfigurationDemo.Models; + +public class ConfigurationDbContext(string? connectionString) : DbContext +{ + private readonly string? _connectionString = connectionString; + + public DbSet ConfigurationEntities { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlServer(_connectionString); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationEntity.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationEntity.cs new file mode 100644 index 0000000000..c1c67b7ff0 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationEntity.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace ProjectConfigurationDemo.Models; + +public class ConfigurationEntity +{ + [Key] + public string Key { get; set; } = string.Empty; + + public string? Value { get; set; } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationProvider.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationProvider.cs new file mode 100644 index 0000000000..975612f02a --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationProvider.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; + +namespace ProjectConfigurationDemo.Models.ConfigurationProviders; + +public class EFConfigurationProvider(string? connectionString) : ConfigurationProvider +{ + private readonly string? _connectionString = connectionString; + + public override void Load() + { + using var dbContext = new ConfigurationDbContext(_connectionString); + + dbContext.Database.EnsureCreated(); + + Data = dbContext.ConfigurationEntities.Any() + ? dbContext.ConfigurationEntities.ToDictionary(c => c.Key, c => c.Value, StringComparer.OrdinalIgnoreCase) + : CreateAndSaveDefaultValues(dbContext); + } + + private static Dictionary CreateAndSaveDefaultValues(ConfigurationDbContext dbContext) + { + var configValues = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "Pages:HomePage:WelcomeMessage", "Welcome to the ProjectConfigurationDemo Home Page" }, + { "Pages:HomePage:ShowWelcomeMessage", "true" }, + { "Pages:HomePage:Color", "black" }, + { "Pages:HomePage:UseRandomTitleColor", "true" } + }; + + dbContext.ConfigurationEntities.AddRange( + [.. configValues.Select(kvp => new ConfigurationEntity { Key = kvp.Key, Value = kvp.Value })]); + + dbContext.SaveChanges(); + + return configValues; + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationSource.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationSource.cs new file mode 100644 index 0000000000..9c842dd4b7 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationSource.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.Configuration; + +namespace ProjectConfigurationDemo.Models.ConfigurationProviders; + +public class EFConfigurationSource(string? connectionString) : IConfigurationSource +{ + private readonly string? _connectionString = connectionString; + + public IConfigurationProvider Build(IConfigurationBuilder builder) + => new EFConfigurationProvider(_connectionString); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/EntityConfigurationExtensions.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/EntityConfigurationExtensions.cs new file mode 100644 index 0000000000..5a6f5f669a --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/EntityConfigurationExtensions.cs @@ -0,0 +1,16 @@ +using ProjectConfigurationDemo.Models.ConfigurationProviders; + +namespace ProjectConfigurationDemo.Models; + +public static class EntityConfigurationExtensions +{ + public static ConfigurationManager AddEntityConfiguration(this ConfigurationManager manager) + { + var connectionString = manager.GetConnectionString("sqlConnection"); + + IConfigurationBuilder configBuilder = manager; + configBuilder.Add(new EFConfigurationSource(connectionString)); + + return manager; + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ErrorViewModel.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ErrorViewModel.cs new file mode 100644 index 0000000000..d3daed82d7 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/ErrorViewModel.cs @@ -0,0 +1,8 @@ +namespace ProjectConfigurationDemo.Models; + +public class ErrorViewModel +{ + public string? RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/HomeModel.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/HomeModel.cs new file mode 100644 index 0000000000..7838fe8088 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/HomeModel.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Models; + +public class HomeModel +{ + public TitleConfiguration Configuration { get; set; } = new(); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs new file mode 100644 index 0000000000..9cc59d60f1 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Models; + +public class LoggingLevelConfiguration +{ + public string? Default { get; set; } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/TitleConfiguration.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/TitleConfiguration.cs new file mode 100644 index 0000000000..cf0cb49713 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Models/TitleConfiguration.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace ProjectConfigurationDemo.Models; + +public class TitleConfiguration +{ + [Required] + [MaxLength(60)] + public string WelcomeMessage { get; set; } = string.Empty; + + public bool ShowWelcomeMessage { get; set; } + + public string Color { get; set; } = string.Empty; + + public bool UseRandomTitleColor { get; set; } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Program.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Program.cs new file mode 100644 index 0000000000..6ef319fc24 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Program.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.ConfigurationValidation; +using ProjectConfigurationDemo.Models; +using ProjectConfigurationDemo.Services; + +var builder = WebApplication.CreateBuilder(args); + +builder.Configuration.AddEntityConfiguration(); + +builder.Services + .AddOptionsWithValidateOnStart() + .Bind(builder.Configuration.GetSection("Pages:HomePage")) + .ValidateDataAnnotations(); + +builder.Services.TryAddEnumerable( + ServiceDescriptor.Singleton, TitleConfigurationValidation>()); + +builder.Services.TryAddSingleton(); + +builder.Services.AddControllersWithViews(); + +var app = builder.Build(); + +app.MapStaticAssets(); +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}") + .WithStaticAssets(); + +app.Run(); diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj new file mode 100644 index 0000000000..6f7bd435ce --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + f5fc4b7d-16ac-48b3-a865-d86096b35b16 + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Properties/launchSettings.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Properties/launchSettings.json new file mode 100644 index 0000000000..92ac1b0576 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Services/ITitleColorService.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Services/ITitleColorService.cs new file mode 100644 index 0000000000..2ee1036bf8 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Services/ITitleColorService.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Services; + +public interface ITitleColorService +{ + string GetTitleColor(); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Services/TitleColorService.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Services/TitleColorService.cs new file mode 100644 index 0000000000..1e95304883 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Services/TitleColorService.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; + +namespace ProjectConfigurationDemo.Services; + +public class TitleColorService(IOptionsMonitor titleConfiguration) : ITitleColorService +{ + private readonly string[] _colors = ["red", "green", "blue", "black", "purple", "yellow", "brown", "pink"]; + private readonly IOptionsMonitor _titleConfiguration = titleConfiguration; + + public string GetTitleColor() + { + var configuration = _titleConfiguration.CurrentValue; + + return configuration.UseRandomTitleColor + ? _colors[Random.Shared.Next(_colors.Length)] + : configuration.Color; + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Home/Index.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Home/Index.cshtml new file mode 100644 index 0000000000..c286342c56 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Home/Index.cshtml @@ -0,0 +1,10 @@ +@model HomeModel +@{ + ViewData["Title"] = "Home Page"; +} +
+ @if (Model.Configuration.ShowWelcomeMessage) + { +

@Model.Configuration.WelcomeMessage

+ } +
diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Home/Privacy.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Home/Privacy.cshtml new file mode 100644 index 0000000000..d01477f389 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Home/Privacy.cshtml @@ -0,0 +1,6 @@ +@{ + ViewData["Title"] = "Privacy Policy"; +} +

@ViewData["Title"]

+ +

Use this page to detail your site's privacy policy.

diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Shared/Error.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Shared/Error.cshtml new file mode 100644 index 0000000000..8f923fdf49 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Shared/Error.cshtml @@ -0,0 +1,25 @@ +@model ErrorViewModel +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +@if (Model.ShowRequestId) +{ +

+ Request ID: @Model.RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the + ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app. +

diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000000..208bd9a352 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml @@ -0,0 +1,30 @@ + + + + + + @ViewData["Title"] - ProjectConfigurationDemo + + + +
+ +
+
+
+ @RenderBody() +
+
+ +
+ © @DateTime.Now.Year - ProjectConfigurationDemo - + Privacy +
+ + @await RenderSectionAsync("Scripts", required: false) + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/_ViewImports.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/_ViewImports.cshtml new file mode 100644 index 0000000000..92266eb487 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using ProjectConfigurationDemo +@using ProjectConfigurationDemo.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/_ViewStart.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/_ViewStart.cshtml new file mode 100644 index 0000000000..820a2f6e02 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/appsettings.Development.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/appsettings.Production.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/appsettings.Production.json new file mode 100644 index 0000000000..50c2698458 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/appsettings.Production.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "sqlConnection": "Server=ProductionServerName;Database=CodeMazeCommerce;Trusted_Connection=True;TrustServerCertificate=True" + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/appsettings.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/appsettings.json new file mode 100644 index 0000000000..9db2c25d6c --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "ConnectionStrings": { + "sqlConnection": "Server=(localdb)\\MSSQLLocalDB;Database=CodeMazeCommerce;Trusted_Connection=True;TrustServerCertificate=True" + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/wwwroot/css/site.css b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/wwwroot/css/site.css new file mode 100644 index 0000000000..d78436d57f --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/wwwroot/css/site.css @@ -0,0 +1,49 @@ +html { + font-size: 14px; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; +} + +body { + margin: 0; + color: #212529; + background-color: #fff; +} + +header nav { + display: flex; + gap: 1rem; + padding: 1rem 1.5rem; + border-bottom: 1px solid #e5e5e5; +} + +header nav a { + color: #212529; + text-decoration: none; +} + +.container { + max-width: 960px; + margin: 0 auto; + padding: 1.5rem; +} + +.text-center { + text-align: center; +} + +.display-4 { + font-size: 3.5rem; + font-weight: 300; +} + +.text-danger { + color: #dc3545; +} + +footer { + max-width: 960px; + margin: 0 auto; + padding: 1rem 1.5rem; + border-top: 1px solid #e5e5e5; + color: #6c757d; +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/wwwroot/favicon.ico b/aspnetcore-webapi/AspNetCoreConfigurationSeries/CustomConfigurationProvider/ProjectConfigurationDemo/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a3a799985c43bc7309d701b2cad129023377dc71 GIT binary patch literal 32038 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 + + + net10.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/OptionsPattern.Tests/OptionsPatternTests.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/OptionsPattern.Tests/OptionsPatternTests.cs new file mode 100644 index 0000000000..9611cf521e --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/OptionsPattern.Tests/OptionsPatternTests.cs @@ -0,0 +1,92 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; +using ProjectConfigurationDemo.Services; + +namespace OptionsPattern.Tests; + +public class OptionsPatternTests +{ + private static IConfiguration BuildConfiguration() => + new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["Pages:HomePage:WelcomeMessage"] = "Welcome to the Home Page", + ["Pages:HomePage:ShowWelcomeMessage"] = "true", + ["Pages:HomePage:Color"] = "red", + ["Pages:HomePage:UseRandomTitleColor"] = "true", + ["Pages:ProductPage:WelcomeMessage"] = "Welcome to the Product Page", + ["Pages:ProductPage:ShowWelcomeMessage"] = "true", + ["Pages:ProductPage:Color"] = "black", + ["Pages:ProductPage:UseRandomTitleColor"] = "false" + }) + .Build(); + + private static ServiceProvider BuildNamedOptionsProvider() + { + var configuration = BuildConfiguration(); + var services = new ServiceCollection(); + + services.Configure("HomePage", configuration.GetSection("Pages:HomePage")); + services.Configure("ProductPage", configuration.GetSection("Pages:ProductPage")); + services.AddSingleton(); + + return services.BuildServiceProvider(); + } + + [Fact] + public void GivenAnUnnamedRegistration_WhenIOptionsValueIsRead_ThenTheSectionIsBoundToTheClass() + { + var services = new ServiceCollection(); + services.Configure(BuildConfiguration().GetSection("Pages:HomePage")); + + var options = services.BuildServiceProvider().GetRequiredService>(); + + Assert.Equal("Welcome to the Home Page", options.Value.WelcomeMessage); + Assert.True(options.Value.ShowWelcomeMessage); + Assert.Equal("red", options.Value.Color); + } + + [Fact] + public void GivenTwoNamedRegistrations_WhenEachIsRead_ThenOneClassCarriesTwoDifferentInstances() + { + using var provider = BuildNamedOptionsProvider(); + var monitor = provider.GetRequiredService>(); + + var homePage = monitor.Get("HomePage"); + var productPage = monitor.Get("ProductPage"); + + Assert.Equal("Welcome to the Home Page", homePage.WelcomeMessage); + Assert.Equal("Welcome to the Product Page", productPage.WelcomeMessage); + Assert.True(homePage.UseRandomTitleColor); + Assert.False(productPage.UseRandomTitleColor); + } + + [Fact] + public void GivenAPageThatDoesNotUseARandomColor_WhenTheColorIsRequested_ThenTheConfiguredColorComesBack() + { + using var provider = BuildNamedOptionsProvider(); + var titleColorService = provider.GetRequiredService(); + + var color = titleColorService.GetTitleColor("ProductPage"); + + Assert.Equal("black", color); + } + + // This is the test that would have caught 'random.Next(7)' over eight colours: + // "pink" sits at index 7 and was unreachable. + [Fact] + public void GivenAPageThatUsesARandomColor_WhenTheColorIsRequestedManyTimes_ThenEveryColorCanBeSelected() + { + using var provider = BuildNamedOptionsProvider(); + var titleColorService = provider.GetRequiredService(); + string[] expected = ["red", "green", "blue", "black", "purple", "yellow", "brown", "pink"]; + + var seen = new HashSet(); + for (var i = 0; i < 20_000; i++) + seen.Add(titleColorService.GetTitleColor("HomePage")); + + Assert.Equal(expected.Order(), seen.Order()); + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo.sln b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo.sln new file mode 100644 index 0000000000..179e1789e1 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectConfigurationDemo", "ProjectConfigurationDemo\ProjectConfigurationDemo.csproj", "{DC004E8F-0365-4446-A71D-7BD64A697C40}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptionsPattern.Tests", "OptionsPattern.Tests\OptionsPattern.Tests.csproj", "{A1B78DA2-B190-4CD6-84FE-9CE54FACA25C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DC004E8F-0365-4446-A71D-7BD64A697C40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC004E8F-0365-4446-A71D-7BD64A697C40}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC004E8F-0365-4446-A71D-7BD64A697C40}.Debug|x64.ActiveCfg = Debug|Any CPU + {DC004E8F-0365-4446-A71D-7BD64A697C40}.Debug|x64.Build.0 = Debug|Any CPU + {DC004E8F-0365-4446-A71D-7BD64A697C40}.Debug|x86.ActiveCfg = Debug|Any CPU + {DC004E8F-0365-4446-A71D-7BD64A697C40}.Debug|x86.Build.0 = Debug|Any CPU + {DC004E8F-0365-4446-A71D-7BD64A697C40}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC004E8F-0365-4446-A71D-7BD64A697C40}.Release|Any CPU.Build.0 = Release|Any CPU + {DC004E8F-0365-4446-A71D-7BD64A697C40}.Release|x64.ActiveCfg = Release|Any CPU + {DC004E8F-0365-4446-A71D-7BD64A697C40}.Release|x64.Build.0 = Release|Any CPU + {DC004E8F-0365-4446-A71D-7BD64A697C40}.Release|x86.ActiveCfg = Release|Any CPU + {DC004E8F-0365-4446-A71D-7BD64A697C40}.Release|x86.Build.0 = Release|Any CPU + {A1B78DA2-B190-4CD6-84FE-9CE54FACA25C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1B78DA2-B190-4CD6-84FE-9CE54FACA25C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1B78DA2-B190-4CD6-84FE-9CE54FACA25C}.Debug|x64.ActiveCfg = Debug|Any CPU + {A1B78DA2-B190-4CD6-84FE-9CE54FACA25C}.Debug|x64.Build.0 = Debug|Any CPU + {A1B78DA2-B190-4CD6-84FE-9CE54FACA25C}.Debug|x86.ActiveCfg = Debug|Any CPU + {A1B78DA2-B190-4CD6-84FE-9CE54FACA25C}.Debug|x86.Build.0 = Debug|Any CPU + {A1B78DA2-B190-4CD6-84FE-9CE54FACA25C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1B78DA2-B190-4CD6-84FE-9CE54FACA25C}.Release|Any CPU.Build.0 = Release|Any CPU + {A1B78DA2-B190-4CD6-84FE-9CE54FACA25C}.Release|x64.ActiveCfg = Release|Any CPU + {A1B78DA2-B190-4CD6-84FE-9CE54FACA25C}.Release|x64.Build.0 = Release|Any CPU + {A1B78DA2-B190-4CD6-84FE-9CE54FACA25C}.Release|x86.ActiveCfg = Release|Any CPU + {A1B78DA2-B190-4CD6-84FE-9CE54FACA25C}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Controllers/HomeController.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Controllers/HomeController.cs new file mode 100644 index 0000000000..061458f080 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Controllers/HomeController.cs @@ -0,0 +1,35 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; +using ProjectConfigurationDemo.Services; + +namespace ProjectConfigurationDemo.Controllers; + +public class HomeController( + ILogger logger, + IOptionsSnapshot homePageTitleConfiguration, + ITitleColorService titleColorService) : Controller +{ + private readonly ILogger _logger = logger; + private readonly TitleConfiguration _homePageTitleConfiguration = homePageTitleConfiguration.Get("HomePage"); + private readonly ITitleColorService _titleColorService = titleColorService; + + public IActionResult Index() + { + var homeModel = new HomeModel + { + Configuration = _homePageTitleConfiguration + }; + + homeModel.Configuration.Color = _titleColorService.GetTitleColor("HomePage"); + + return View(homeModel); + } + + public IActionResult Privacy() => View(); + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() => + View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/ErrorViewModel.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/ErrorViewModel.cs new file mode 100644 index 0000000000..d3daed82d7 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/ErrorViewModel.cs @@ -0,0 +1,8 @@ +namespace ProjectConfigurationDemo.Models; + +public class ErrorViewModel +{ + public string? RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/HomeModel.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/HomeModel.cs new file mode 100644 index 0000000000..7838fe8088 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/HomeModel.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Models; + +public class HomeModel +{ + public TitleConfiguration Configuration { get; set; } = new(); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs new file mode 100644 index 0000000000..9cc59d60f1 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Models; + +public class LoggingLevelConfiguration +{ + public string? Default { get; set; } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/TitleConfiguration.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/TitleConfiguration.cs new file mode 100644 index 0000000000..74437a0342 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Models/TitleConfiguration.cs @@ -0,0 +1,9 @@ +namespace ProjectConfigurationDemo.Models; + +public class TitleConfiguration +{ + public string WelcomeMessage { get; set; } = string.Empty; + public bool ShowWelcomeMessage { get; set; } + public string Color { get; set; } = string.Empty; + public bool UseRandomTitleColor { get; set; } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Program.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Program.cs new file mode 100644 index 0000000000..b51ea3c843 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Program.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; +using ProjectConfigurationDemo.Models; +using ProjectConfigurationDemo.Services; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.Configure("HomePage", + builder.Configuration.GetSection("Pages:HomePage")); +builder.Services.Configure("ProductPage", + builder.Configuration.GetSection("Pages:ProductPage")); + +builder.Services.TryAddSingleton(); + +builder.Services.AddControllersWithViews(); + +var app = builder.Build(); + +app.MapStaticAssets(); +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}") + .WithStaticAssets(); + +app.Run(); diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj new file mode 100644 index 0000000000..a3a34b647c --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Properties/launchSettings.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Properties/launchSettings.json new file mode 100644 index 0000000000..92ac1b0576 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Services/ITitleColorService.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Services/ITitleColorService.cs new file mode 100644 index 0000000000..046d6382dd --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Services/ITitleColorService.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Services; + +public interface ITitleColorService +{ + string GetTitleColor(string pageTitleConfiguration); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Services/TitleColorService.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Services/TitleColorService.cs new file mode 100644 index 0000000000..e93e0c8074 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Services/TitleColorService.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; + +namespace ProjectConfigurationDemo.Services; + +public class TitleColorService(IOptionsMonitor titleConfiguration) : ITitleColorService +{ + private readonly string[] _colors = ["red", "green", "blue", "black", "purple", "yellow", "brown", "pink"]; + private readonly IOptionsMonitor _titleConfiguration = titleConfiguration; + + public string GetTitleColor(string pageTitleConfiguration) + { + var configuration = _titleConfiguration.Get(pageTitleConfiguration); + + return configuration.UseRandomTitleColor + ? _colors[Random.Shared.Next(_colors.Length)] + : configuration.Color; + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Home/Index.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Home/Index.cshtml new file mode 100644 index 0000000000..c286342c56 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Home/Index.cshtml @@ -0,0 +1,10 @@ +@model HomeModel +@{ + ViewData["Title"] = "Home Page"; +} +
+ @if (Model.Configuration.ShowWelcomeMessage) + { +

@Model.Configuration.WelcomeMessage

+ } +
diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Home/Privacy.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Home/Privacy.cshtml new file mode 100644 index 0000000000..d01477f389 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Home/Privacy.cshtml @@ -0,0 +1,6 @@ +@{ + ViewData["Title"] = "Privacy Policy"; +} +

@ViewData["Title"]

+ +

Use this page to detail your site's privacy policy.

diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Shared/Error.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Shared/Error.cshtml new file mode 100644 index 0000000000..8f923fdf49 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Shared/Error.cshtml @@ -0,0 +1,25 @@ +@model ErrorViewModel +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +@if (Model.ShowRequestId) +{ +

+ Request ID: @Model.RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the + ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app. +

diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000000..208bd9a352 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml @@ -0,0 +1,30 @@ + + + + + + @ViewData["Title"] - ProjectConfigurationDemo + + + +
+ +
+
+
+ @RenderBody() +
+
+ +
+ © @DateTime.Now.Year - ProjectConfigurationDemo - + Privacy +
+ + @await RenderSectionAsync("Scripts", required: false) + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/_ViewImports.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/_ViewImports.cshtml new file mode 100644 index 0000000000..92266eb487 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using ProjectConfigurationDemo +@using ProjectConfigurationDemo.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/_ViewStart.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/_ViewStart.cshtml new file mode 100644 index 0000000000..820a2f6e02 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/appsettings.Development.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/appsettings.Production.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/appsettings.Production.json new file mode 100644 index 0000000000..50c2698458 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/appsettings.Production.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "sqlConnection": "Server=ProductionServerName;Database=CodeMazeCommerce;Trusted_Connection=True;TrustServerCertificate=True" + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/appsettings.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/appsettings.json new file mode 100644 index 0000000000..b1b819eeac --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/appsettings.json @@ -0,0 +1,27 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "ConnectionStrings": { + "sqlConnection": "Server=.;Database=CodeMazeCommerce;Trusted_Connection=True;TrustServerCertificate=True" + }, + "Pages": { + "HomePage": { + "WelcomeMessage": "Welcome to the ProjectConfigurationDemo Home Page", + "ShowWelcomeMessage": true, + "Color": "red", + "UseRandomTitleColor": true + }, + "ProductPage": { + "WelcomeMessage": "Welcome to the ProjectConfigurationDemo Product Page", + "ShowWelcomeMessage": true, + "Color": "black", + "UseRandomTitleColor": false + } + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/wwwroot/css/site.css b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/wwwroot/css/site.css new file mode 100644 index 0000000000..d78436d57f --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/wwwroot/css/site.css @@ -0,0 +1,49 @@ +html { + font-size: 14px; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; +} + +body { + margin: 0; + color: #212529; + background-color: #fff; +} + +header nav { + display: flex; + gap: 1rem; + padding: 1rem 1.5rem; + border-bottom: 1px solid #e5e5e5; +} + +header nav a { + color: #212529; + text-decoration: none; +} + +.container { + max-width: 960px; + margin: 0 auto; + padding: 1.5rem; +} + +.text-center { + text-align: center; +} + +.display-4 { + font-size: 3.5rem; + font-weight: 300; +} + +.text-danger { + color: #dc3545; +} + +footer { + max-width: 960px; + margin: 0 auto; + padding: 1rem 1.5rem; + border-top: 1px solid #e5e5e5; + color: #6c757d; +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/wwwroot/favicon.ico b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsPattern/ProjectConfigurationDemo/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a3a799985c43bc7309d701b2cad129023377dc71 GIT binary patch literal 32038 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 + + + net10.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/OptionsValidation.Tests/OptionsValidationTests.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/OptionsValidation.Tests/OptionsValidationTests.cs new file mode 100644 index 0000000000..390a2042cc --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/OptionsValidation.Tests/OptionsValidationTests.cs @@ -0,0 +1,109 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.ConfigurationValidation; +using ProjectConfigurationDemo.Models; + +namespace OptionsValidation.Tests; + +public class OptionsValidationTests +{ + private static ServiceProvider BuildProvider(Dictionary values) + { + var configuration = new ConfigurationBuilder().AddInMemoryCollection(values).Build(); + var services = new ServiceCollection(); + + services + .AddOptionsWithValidateOnStart() + .Bind(configuration.GetSection("Pages:HomePage")) + .ValidateDataAnnotations(); + + services.TryAddEnumerable( + ServiceDescriptor.Singleton, TitleConfigurationValidation>()); + + return services.BuildServiceProvider(); + } + + private static Dictionary ValidConfiguration() => new() + { + ["Pages:HomePage:WelcomeMessage"] = "Welcome to the Home Page", + ["Pages:HomePage:ShowWelcomeMessage"] = "true", + ["Pages:HomePage:Color"] = "black", + ["Pages:HomePage:UseRandomTitleColor"] = "true" + }; + + [Fact] + public void GivenAValidConfiguration_WhenTheOptionsAreResolved_ThenTheyBindWithoutThrowing() + { + using var provider = BuildProvider(ValidConfiguration()); + + var options = provider.GetRequiredService>(); + + Assert.Equal("Welcome to the Home Page", options.Value.WelcomeMessage); + Assert.Equal("black", options.Value.Color); + } + + [Fact] + public void GivenAMissingWelcomeMessage_WhenTheOptionsAreResolved_ThenAnOptionsValidationExceptionIsThrown() + { + var values = ValidConfiguration(); + values["Pages:HomePage:WelcomeMessage"] = string.Empty; + + using var provider = BuildProvider(values); + + Assert.Throws( + () => provider.GetRequiredService>().Value); + } + + [Fact] + public void GivenAWelcomeMessageOverSixtyCharacters_WhenTheOptionsAreResolved_ThenAnOptionsValidationExceptionIsThrown() + { + var values = ValidConfiguration(); + values["Pages:HomePage:WelcomeMessage"] = new string('a', 61); + + using var provider = BuildProvider(values); + + Assert.Throws( + () => provider.GetRequiredService>().Value); + } + + [Fact] + public void GivenAColorOutsideTheAllowedList_WhenTheOptionsAreResolved_ThenTheValidatorsMessageIsReported() + { + var values = ValidConfiguration(); + values["Pages:HomePage:Color"] = "magenta"; + + using var provider = BuildProvider(values); + + var exception = Assert.Throws( + () => provider.GetRequiredService>().Value); + + Assert.Contains("is not among allowed colors", exception.Message); + } + + // The failure-accumulation change: the hand-written validator reports every + // broken rule in one pass instead of returning on the first one. + [Fact] + public void GivenTwoBrokenRules_WhenTheValidatorRuns_ThenBothFailuresAreReported() + { + var validator = new TitleConfigurationValidation(); + var options = new TitleConfiguration { WelcomeMessage = string.Empty, Color = "magenta" }; + + var result = validator.Validate(name: null, options); + + Assert.True(result.Failed); + Assert.Equal(2, result.Failures!.Count()); + } + + [Fact] + public void GivenAValidOptionsInstance_WhenTheValidatorRuns_ThenItSucceeds() + { + var validator = new TitleConfigurationValidation(); + var options = new TitleConfiguration { WelcomeMessage = "Welcome", Color = "blue" }; + + var result = validator.Validate(name: null, options); + + Assert.True(result.Succeeded); + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo.sln b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo.sln new file mode 100644 index 0000000000..3dd99953d5 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectConfigurationDemo", "ProjectConfigurationDemo\ProjectConfigurationDemo.csproj", "{B6411524-0F8C-49BD-8F26-8C0CECD23485}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptionsValidation.Tests", "OptionsValidation.Tests\OptionsValidation.Tests.csproj", "{68A3CD60-56D8-4852-9C4F-265420B3D362}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B6411524-0F8C-49BD-8F26-8C0CECD23485}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6411524-0F8C-49BD-8F26-8C0CECD23485}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6411524-0F8C-49BD-8F26-8C0CECD23485}.Debug|x64.ActiveCfg = Debug|Any CPU + {B6411524-0F8C-49BD-8F26-8C0CECD23485}.Debug|x64.Build.0 = Debug|Any CPU + {B6411524-0F8C-49BD-8F26-8C0CECD23485}.Debug|x86.ActiveCfg = Debug|Any CPU + {B6411524-0F8C-49BD-8F26-8C0CECD23485}.Debug|x86.Build.0 = Debug|Any CPU + {B6411524-0F8C-49BD-8F26-8C0CECD23485}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6411524-0F8C-49BD-8F26-8C0CECD23485}.Release|Any CPU.Build.0 = Release|Any CPU + {B6411524-0F8C-49BD-8F26-8C0CECD23485}.Release|x64.ActiveCfg = Release|Any CPU + {B6411524-0F8C-49BD-8F26-8C0CECD23485}.Release|x64.Build.0 = Release|Any CPU + {B6411524-0F8C-49BD-8F26-8C0CECD23485}.Release|x86.ActiveCfg = Release|Any CPU + {B6411524-0F8C-49BD-8F26-8C0CECD23485}.Release|x86.Build.0 = Release|Any CPU + {68A3CD60-56D8-4852-9C4F-265420B3D362}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68A3CD60-56D8-4852-9C4F-265420B3D362}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68A3CD60-56D8-4852-9C4F-265420B3D362}.Debug|x64.ActiveCfg = Debug|Any CPU + {68A3CD60-56D8-4852-9C4F-265420B3D362}.Debug|x64.Build.0 = Debug|Any CPU + {68A3CD60-56D8-4852-9C4F-265420B3D362}.Debug|x86.ActiveCfg = Debug|Any CPU + {68A3CD60-56D8-4852-9C4F-265420B3D362}.Debug|x86.Build.0 = Debug|Any CPU + {68A3CD60-56D8-4852-9C4F-265420B3D362}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68A3CD60-56D8-4852-9C4F-265420B3D362}.Release|Any CPU.Build.0 = Release|Any CPU + {68A3CD60-56D8-4852-9C4F-265420B3D362}.Release|x64.ActiveCfg = Release|Any CPU + {68A3CD60-56D8-4852-9C4F-265420B3D362}.Release|x64.Build.0 = Release|Any CPU + {68A3CD60-56D8-4852-9C4F-265420B3D362}.Release|x86.ActiveCfg = Release|Any CPU + {68A3CD60-56D8-4852-9C4F-265420B3D362}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs new file mode 100644 index 0000000000..ca47852084 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; + +namespace ProjectConfigurationDemo.ConfigurationValidation; + +public class TitleConfigurationValidation : IValidateOptions +{ + private readonly string[] _colors = ["red", "green", "blue", "black", "purple", "yellow", "brown", "pink"]; + + public ValidateOptionsResult Validate(string? name, TitleConfiguration options) + { + var builder = new ValidateOptionsResultBuilder(); + + if (string.IsNullOrEmpty(options.WelcomeMessage) || options.WelcomeMessage.Length > 60) + builder.AddError("Welcome message must be defined and it must be less than 60 characters long.", + nameof(options.WelcomeMessage)); + + if (!_colors.Contains(options.Color)) + builder.AddError($"Provided title color '{options.Color}' is not among allowed colors.", + nameof(options.Color)); + + return builder.Build(); + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Controllers/HomeController.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Controllers/HomeController.cs new file mode 100644 index 0000000000..dd426ae2aa --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Controllers/HomeController.cs @@ -0,0 +1,35 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; +using ProjectConfigurationDemo.Services; + +namespace ProjectConfigurationDemo.Controllers; + +public class HomeController( + ILogger logger, + IOptionsSnapshot homePageTitleConfiguration, + ITitleColorService titleColorService) : Controller +{ + private readonly ILogger _logger = logger; + private readonly TitleConfiguration _homePageTitleConfiguration = homePageTitleConfiguration.Value; + private readonly ITitleColorService _titleColorService = titleColorService; + + public IActionResult Index() + { + var homeModel = new HomeModel + { + Configuration = _homePageTitleConfiguration + }; + + homeModel.Configuration.Color = _titleColorService.GetTitleColor(); + + return View(homeModel); + } + + public IActionResult Privacy() => View(); + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() => + View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/ErrorViewModel.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/ErrorViewModel.cs new file mode 100644 index 0000000000..d3daed82d7 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/ErrorViewModel.cs @@ -0,0 +1,8 @@ +namespace ProjectConfigurationDemo.Models; + +public class ErrorViewModel +{ + public string? RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/HomeModel.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/HomeModel.cs new file mode 100644 index 0000000000..7838fe8088 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/HomeModel.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Models; + +public class HomeModel +{ + public TitleConfiguration Configuration { get; set; } = new(); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs new file mode 100644 index 0000000000..9cc59d60f1 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Models; + +public class LoggingLevelConfiguration +{ + public string? Default { get; set; } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/TitleConfiguration.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/TitleConfiguration.cs new file mode 100644 index 0000000000..cf0cb49713 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Models/TitleConfiguration.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace ProjectConfigurationDemo.Models; + +public class TitleConfiguration +{ + [Required] + [MaxLength(60)] + public string WelcomeMessage { get; set; } = string.Empty; + + public bool ShowWelcomeMessage { get; set; } + + public string Color { get; set; } = string.Empty; + + public bool UseRandomTitleColor { get; set; } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Program.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Program.cs new file mode 100644 index 0000000000..51051bd785 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Program.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.ConfigurationValidation; +using ProjectConfigurationDemo.Models; +using ProjectConfigurationDemo.Services; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services + .AddOptionsWithValidateOnStart() + .Bind(builder.Configuration.GetSection("Pages:HomePage")) + .ValidateDataAnnotations(); + +builder.Services.TryAddEnumerable( + ServiceDescriptor.Singleton, TitleConfigurationValidation>()); + +builder.Services.TryAddSingleton(); + +builder.Services.AddControllersWithViews(); + +var app = builder.Build(); + +app.MapStaticAssets(); +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}") + .WithStaticAssets(); + +app.Run(); diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj new file mode 100644 index 0000000000..a3a34b647c --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Properties/launchSettings.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Properties/launchSettings.json new file mode 100644 index 0000000000..92ac1b0576 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Services/ITitleColorService.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Services/ITitleColorService.cs new file mode 100644 index 0000000000..2ee1036bf8 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Services/ITitleColorService.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Services; + +public interface ITitleColorService +{ + string GetTitleColor(); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Services/TitleColorService.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Services/TitleColorService.cs new file mode 100644 index 0000000000..1e95304883 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Services/TitleColorService.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; + +namespace ProjectConfigurationDemo.Services; + +public class TitleColorService(IOptionsMonitor titleConfiguration) : ITitleColorService +{ + private readonly string[] _colors = ["red", "green", "blue", "black", "purple", "yellow", "brown", "pink"]; + private readonly IOptionsMonitor _titleConfiguration = titleConfiguration; + + public string GetTitleColor() + { + var configuration = _titleConfiguration.CurrentValue; + + return configuration.UseRandomTitleColor + ? _colors[Random.Shared.Next(_colors.Length)] + : configuration.Color; + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Home/Index.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Home/Index.cshtml new file mode 100644 index 0000000000..c286342c56 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Home/Index.cshtml @@ -0,0 +1,10 @@ +@model HomeModel +@{ + ViewData["Title"] = "Home Page"; +} +
+ @if (Model.Configuration.ShowWelcomeMessage) + { +

@Model.Configuration.WelcomeMessage

+ } +
diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Home/Privacy.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Home/Privacy.cshtml new file mode 100644 index 0000000000..d01477f389 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Home/Privacy.cshtml @@ -0,0 +1,6 @@ +@{ + ViewData["Title"] = "Privacy Policy"; +} +

@ViewData["Title"]

+ +

Use this page to detail your site's privacy policy.

diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Shared/Error.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Shared/Error.cshtml new file mode 100644 index 0000000000..8f923fdf49 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Shared/Error.cshtml @@ -0,0 +1,25 @@ +@model ErrorViewModel +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +@if (Model.ShowRequestId) +{ +

+ Request ID: @Model.RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the + ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app. +

diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000000..208bd9a352 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml @@ -0,0 +1,30 @@ + + + + + + @ViewData["Title"] - ProjectConfigurationDemo + + + +
+ +
+
+
+ @RenderBody() +
+
+ +
+ © @DateTime.Now.Year - ProjectConfigurationDemo - + Privacy +
+ + @await RenderSectionAsync("Scripts", required: false) + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/_ViewImports.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/_ViewImports.cshtml new file mode 100644 index 0000000000..92266eb487 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using ProjectConfigurationDemo +@using ProjectConfigurationDemo.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/_ViewStart.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/_ViewStart.cshtml new file mode 100644 index 0000000000..820a2f6e02 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/appsettings.Development.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/appsettings.Production.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/appsettings.Production.json new file mode 100644 index 0000000000..50c2698458 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/appsettings.Production.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "sqlConnection": "Server=ProductionServerName;Database=CodeMazeCommerce;Trusted_Connection=True;TrustServerCertificate=True" + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/appsettings.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/appsettings.json new file mode 100644 index 0000000000..1310f75cab --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/appsettings.json @@ -0,0 +1,21 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "ConnectionStrings": { + "sqlConnection": "Server=.;Database=CodeMazeCommerce;Trusted_Connection=True;TrustServerCertificate=True" + }, + "Pages": { + "HomePage": { + "WelcomeMessage": "Welcome to the ProjectConfigurationDemo Home Page", + "ShowWelcomeMessage": true, + "Color": "black", + "UseRandomTitleColor": true + } + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/wwwroot/css/site.css b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/wwwroot/css/site.css new file mode 100644 index 0000000000..d78436d57f --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/wwwroot/css/site.css @@ -0,0 +1,49 @@ +html { + font-size: 14px; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; +} + +body { + margin: 0; + color: #212529; + background-color: #fff; +} + +header nav { + display: flex; + gap: 1rem; + padding: 1rem 1.5rem; + border-bottom: 1px solid #e5e5e5; +} + +header nav a { + color: #212529; + text-decoration: none; +} + +.container { + max-width: 960px; + margin: 0 auto; + padding: 1.5rem; +} + +.text-center { + text-align: center; +} + +.display-4 { + font-size: 3.5rem; + font-weight: 300; +} + +.text-danger { + color: #dc3545; +} + +footer { + max-width: 960px; + margin: 0 auto; + padding: 1rem 1.5rem; + border-top: 1px solid #e5e5e5; + color: #6c757d; +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/wwwroot/favicon.ico b/aspnetcore-webapi/AspNetCoreConfigurationSeries/OptionsValidation/ProjectConfigurationDemo/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a3a799985c43bc7309d701b2cad129023377dc71 GIT binary patch literal 32038 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" -p 1433:1433 -d mcr.microsoft.com/mssql/server:2022-latest +``` + +`SecuringDataLocally` ships with **no connection string in `appsettings.json` at +all** — that is the whole subject of its article. Supply it from a user secret +before running that project: + +``` +cd SecuringDataLocally/ProjectConfigurationDemo +dotnet user-secrets set "ConnectionStrings:sqlConnection" "Server=(localdb)\MSSQLLocalDB;Database=CodeMazeCommerce;Trusted_Connection=True;TrustServerCertificate=True" +``` + +or from an environment variable, using the double underscore as the separator: + +``` +ConnectionStrings__sqlConnection=... +``` + +Each of the three projects that use the Secret Manager carries its **own** +`UserSecretsId`, so their secret stores are separate — which is the point the last +article makes about user secrets being scoped to a project. + +## The tests + +Every folder has a test project, and the four that need no database run anywhere. + +`CustomConfigurationProvider.Tests` and `SecuringDataLocally.Tests` start a real SQL +Server in a [Testcontainers](https://dotnet.testcontainers.org/) container, so they +need a running Docker daemon. Tests that need the container are marked +`[RequiresDockerFact]` and are **skipped**, not failed, when no daemon is reachable, +so `dotnet test` is green on a machine without Docker. Set +`CONFIGURATION_SERIES_REQUIRE_DOCKER=1` to turn a missing daemon into a failure +instead, which is the setting to use if you want the container tests to be +mandatory. diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo.sln b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo.sln new file mode 100644 index 0000000000..512781d2bd --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectConfigurationDemo", "ProjectConfigurationDemo\ProjectConfigurationDemo.csproj", "{5867396B-A23C-4D74-BA94-F765B4C4C9DD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecuringDataLocally.Tests", "SecuringDataLocally.Tests\SecuringDataLocally.Tests.csproj", "{2476046C-BC32-4D5B-B841-F8052907AC32}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5867396B-A23C-4D74-BA94-F765B4C4C9DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5867396B-A23C-4D74-BA94-F765B4C4C9DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5867396B-A23C-4D74-BA94-F765B4C4C9DD}.Debug|x64.ActiveCfg = Debug|Any CPU + {5867396B-A23C-4D74-BA94-F765B4C4C9DD}.Debug|x64.Build.0 = Debug|Any CPU + {5867396B-A23C-4D74-BA94-F765B4C4C9DD}.Debug|x86.ActiveCfg = Debug|Any CPU + {5867396B-A23C-4D74-BA94-F765B4C4C9DD}.Debug|x86.Build.0 = Debug|Any CPU + {5867396B-A23C-4D74-BA94-F765B4C4C9DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5867396B-A23C-4D74-BA94-F765B4C4C9DD}.Release|Any CPU.Build.0 = Release|Any CPU + {5867396B-A23C-4D74-BA94-F765B4C4C9DD}.Release|x64.ActiveCfg = Release|Any CPU + {5867396B-A23C-4D74-BA94-F765B4C4C9DD}.Release|x64.Build.0 = Release|Any CPU + {5867396B-A23C-4D74-BA94-F765B4C4C9DD}.Release|x86.ActiveCfg = Release|Any CPU + {5867396B-A23C-4D74-BA94-F765B4C4C9DD}.Release|x86.Build.0 = Release|Any CPU + {2476046C-BC32-4D5B-B841-F8052907AC32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2476046C-BC32-4D5B-B841-F8052907AC32}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2476046C-BC32-4D5B-B841-F8052907AC32}.Debug|x64.ActiveCfg = Debug|Any CPU + {2476046C-BC32-4D5B-B841-F8052907AC32}.Debug|x64.Build.0 = Debug|Any CPU + {2476046C-BC32-4D5B-B841-F8052907AC32}.Debug|x86.ActiveCfg = Debug|Any CPU + {2476046C-BC32-4D5B-B841-F8052907AC32}.Debug|x86.Build.0 = Debug|Any CPU + {2476046C-BC32-4D5B-B841-F8052907AC32}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2476046C-BC32-4D5B-B841-F8052907AC32}.Release|Any CPU.Build.0 = Release|Any CPU + {2476046C-BC32-4D5B-B841-F8052907AC32}.Release|x64.ActiveCfg = Release|Any CPU + {2476046C-BC32-4D5B-B841-F8052907AC32}.Release|x64.Build.0 = Release|Any CPU + {2476046C-BC32-4D5B-B841-F8052907AC32}.Release|x86.ActiveCfg = Release|Any CPU + {2476046C-BC32-4D5B-B841-F8052907AC32}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs new file mode 100644 index 0000000000..ca47852084 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/ConfigurationValidation/TitleConfigurationValidation.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; + +namespace ProjectConfigurationDemo.ConfigurationValidation; + +public class TitleConfigurationValidation : IValidateOptions +{ + private readonly string[] _colors = ["red", "green", "blue", "black", "purple", "yellow", "brown", "pink"]; + + public ValidateOptionsResult Validate(string? name, TitleConfiguration options) + { + var builder = new ValidateOptionsResultBuilder(); + + if (string.IsNullOrEmpty(options.WelcomeMessage) || options.WelcomeMessage.Length > 60) + builder.AddError("Welcome message must be defined and it must be less than 60 characters long.", + nameof(options.WelcomeMessage)); + + if (!_colors.Contains(options.Color)) + builder.AddError($"Provided title color '{options.Color}' is not among allowed colors.", + nameof(options.Color)); + + return builder.Build(); + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Controllers/HomeController.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Controllers/HomeController.cs new file mode 100644 index 0000000000..dd426ae2aa --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Controllers/HomeController.cs @@ -0,0 +1,35 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; +using ProjectConfigurationDemo.Services; + +namespace ProjectConfigurationDemo.Controllers; + +public class HomeController( + ILogger logger, + IOptionsSnapshot homePageTitleConfiguration, + ITitleColorService titleColorService) : Controller +{ + private readonly ILogger _logger = logger; + private readonly TitleConfiguration _homePageTitleConfiguration = homePageTitleConfiguration.Value; + private readonly ITitleColorService _titleColorService = titleColorService; + + public IActionResult Index() + { + var homeModel = new HomeModel + { + Configuration = _homePageTitleConfiguration + }; + + homeModel.Configuration.Color = _titleColorService.GetTitleColor(); + + return View(homeModel); + } + + public IActionResult Privacy() => View(); + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() => + View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationDbContext.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationDbContext.cs new file mode 100644 index 0000000000..c8a57f0ebf --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationDbContext.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; + +namespace ProjectConfigurationDemo.Models; + +public class ConfigurationDbContext(string? connectionString) : DbContext +{ + private readonly string? _connectionString = connectionString; + + public DbSet ConfigurationEntities { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlServer(_connectionString); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationEntity.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationEntity.cs new file mode 100644 index 0000000000..c1c67b7ff0 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationEntity.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace ProjectConfigurationDemo.Models; + +public class ConfigurationEntity +{ + [Key] + public string Key { get; set; } = string.Empty; + + public string? Value { get; set; } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationProvider.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationProvider.cs new file mode 100644 index 0000000000..975612f02a --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationProvider.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; + +namespace ProjectConfigurationDemo.Models.ConfigurationProviders; + +public class EFConfigurationProvider(string? connectionString) : ConfigurationProvider +{ + private readonly string? _connectionString = connectionString; + + public override void Load() + { + using var dbContext = new ConfigurationDbContext(_connectionString); + + dbContext.Database.EnsureCreated(); + + Data = dbContext.ConfigurationEntities.Any() + ? dbContext.ConfigurationEntities.ToDictionary(c => c.Key, c => c.Value, StringComparer.OrdinalIgnoreCase) + : CreateAndSaveDefaultValues(dbContext); + } + + private static Dictionary CreateAndSaveDefaultValues(ConfigurationDbContext dbContext) + { + var configValues = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "Pages:HomePage:WelcomeMessage", "Welcome to the ProjectConfigurationDemo Home Page" }, + { "Pages:HomePage:ShowWelcomeMessage", "true" }, + { "Pages:HomePage:Color", "black" }, + { "Pages:HomePage:UseRandomTitleColor", "true" } + }; + + dbContext.ConfigurationEntities.AddRange( + [.. configValues.Select(kvp => new ConfigurationEntity { Key = kvp.Key, Value = kvp.Value })]); + + dbContext.SaveChanges(); + + return configValues; + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationSource.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationSource.cs new file mode 100644 index 0000000000..9c842dd4b7 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ConfigurationProviders/EFConfigurationSource.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.Configuration; + +namespace ProjectConfigurationDemo.Models.ConfigurationProviders; + +public class EFConfigurationSource(string? connectionString) : IConfigurationSource +{ + private readonly string? _connectionString = connectionString; + + public IConfigurationProvider Build(IConfigurationBuilder builder) + => new EFConfigurationProvider(_connectionString); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/EntityConfigurationExtensions.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/EntityConfigurationExtensions.cs new file mode 100644 index 0000000000..5a6f5f669a --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/EntityConfigurationExtensions.cs @@ -0,0 +1,16 @@ +using ProjectConfigurationDemo.Models.ConfigurationProviders; + +namespace ProjectConfigurationDemo.Models; + +public static class EntityConfigurationExtensions +{ + public static ConfigurationManager AddEntityConfiguration(this ConfigurationManager manager) + { + var connectionString = manager.GetConnectionString("sqlConnection"); + + IConfigurationBuilder configBuilder = manager; + configBuilder.Add(new EFConfigurationSource(connectionString)); + + return manager; + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ErrorViewModel.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ErrorViewModel.cs new file mode 100644 index 0000000000..d3daed82d7 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/ErrorViewModel.cs @@ -0,0 +1,8 @@ +namespace ProjectConfigurationDemo.Models; + +public class ErrorViewModel +{ + public string? RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/HomeModel.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/HomeModel.cs new file mode 100644 index 0000000000..7838fe8088 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/HomeModel.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Models; + +public class HomeModel +{ + public TitleConfiguration Configuration { get; set; } = new(); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs new file mode 100644 index 0000000000..9cc59d60f1 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/LoggingLevelConfiguration.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Models; + +public class LoggingLevelConfiguration +{ + public string? Default { get; set; } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/TitleConfiguration.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/TitleConfiguration.cs new file mode 100644 index 0000000000..cf0cb49713 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Models/TitleConfiguration.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace ProjectConfigurationDemo.Models; + +public class TitleConfiguration +{ + [Required] + [MaxLength(60)] + public string WelcomeMessage { get; set; } = string.Empty; + + public bool ShowWelcomeMessage { get; set; } + + public string Color { get; set; } = string.Empty; + + public bool UseRandomTitleColor { get; set; } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Program.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Program.cs new file mode 100644 index 0000000000..6ef319fc24 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Program.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.ConfigurationValidation; +using ProjectConfigurationDemo.Models; +using ProjectConfigurationDemo.Services; + +var builder = WebApplication.CreateBuilder(args); + +builder.Configuration.AddEntityConfiguration(); + +builder.Services + .AddOptionsWithValidateOnStart() + .Bind(builder.Configuration.GetSection("Pages:HomePage")) + .ValidateDataAnnotations(); + +builder.Services.TryAddEnumerable( + ServiceDescriptor.Singleton, TitleConfigurationValidation>()); + +builder.Services.TryAddSingleton(); + +builder.Services.AddControllersWithViews(); + +var app = builder.Build(); + +app.MapStaticAssets(); +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}") + .WithStaticAssets(); + +app.Run(); diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj new file mode 100644 index 0000000000..3526addb84 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/ProjectConfigurationDemo.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + 77c3db5f-44fb-4632-bcb8-d535a49f7e20 + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Properties/launchSettings.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Properties/launchSettings.json new file mode 100644 index 0000000000..92ac1b0576 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Services/ITitleColorService.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Services/ITitleColorService.cs new file mode 100644 index 0000000000..2ee1036bf8 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Services/ITitleColorService.cs @@ -0,0 +1,6 @@ +namespace ProjectConfigurationDemo.Services; + +public interface ITitleColorService +{ + string GetTitleColor(); +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Services/TitleColorService.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Services/TitleColorService.cs new file mode 100644 index 0000000000..1e95304883 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Services/TitleColorService.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Options; +using ProjectConfigurationDemo.Models; + +namespace ProjectConfigurationDemo.Services; + +public class TitleColorService(IOptionsMonitor titleConfiguration) : ITitleColorService +{ + private readonly string[] _colors = ["red", "green", "blue", "black", "purple", "yellow", "brown", "pink"]; + private readonly IOptionsMonitor _titleConfiguration = titleConfiguration; + + public string GetTitleColor() + { + var configuration = _titleConfiguration.CurrentValue; + + return configuration.UseRandomTitleColor + ? _colors[Random.Shared.Next(_colors.Length)] + : configuration.Color; + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Home/Index.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Home/Index.cshtml new file mode 100644 index 0000000000..c286342c56 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Home/Index.cshtml @@ -0,0 +1,10 @@ +@model HomeModel +@{ + ViewData["Title"] = "Home Page"; +} +
+ @if (Model.Configuration.ShowWelcomeMessage) + { +

@Model.Configuration.WelcomeMessage

+ } +
diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Home/Privacy.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Home/Privacy.cshtml new file mode 100644 index 0000000000..d01477f389 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Home/Privacy.cshtml @@ -0,0 +1,6 @@ +@{ + ViewData["Title"] = "Privacy Policy"; +} +

@ViewData["Title"]

+ +

Use this page to detail your site's privacy policy.

diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Shared/Error.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Shared/Error.cshtml new file mode 100644 index 0000000000..8f923fdf49 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Shared/Error.cshtml @@ -0,0 +1,25 @@ +@model ErrorViewModel +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +@if (Model.ShowRequestId) +{ +

+ Request ID: @Model.RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the + ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app. +

diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000000..208bd9a352 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/Shared/_Layout.cshtml @@ -0,0 +1,30 @@ + + + + + + @ViewData["Title"] - ProjectConfigurationDemo + + + +
+ +
+
+
+ @RenderBody() +
+
+ +
+ © @DateTime.Now.Year - ProjectConfigurationDemo - + Privacy +
+ + @await RenderSectionAsync("Scripts", required: false) + + diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/_ViewImports.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/_ViewImports.cshtml new file mode 100644 index 0000000000..92266eb487 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using ProjectConfigurationDemo +@using ProjectConfigurationDemo.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/_ViewStart.cshtml b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/_ViewStart.cshtml new file mode 100644 index 0000000000..820a2f6e02 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/appsettings.Development.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/appsettings.Production.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/appsettings.Production.json new file mode 100644 index 0000000000..6cf0b42b23 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/appsettings.Production.json @@ -0,0 +1,7 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/appsettings.json b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/appsettings.json new file mode 100644 index 0000000000..d9d9a9bff6 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/wwwroot/css/site.css b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/wwwroot/css/site.css new file mode 100644 index 0000000000..d78436d57f --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/wwwroot/css/site.css @@ -0,0 +1,49 @@ +html { + font-size: 14px; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; +} + +body { + margin: 0; + color: #212529; + background-color: #fff; +} + +header nav { + display: flex; + gap: 1rem; + padding: 1rem 1.5rem; + border-bottom: 1px solid #e5e5e5; +} + +header nav a { + color: #212529; + text-decoration: none; +} + +.container { + max-width: 960px; + margin: 0 auto; + padding: 1.5rem; +} + +.text-center { + text-align: center; +} + +.display-4 { + font-size: 3.5rem; + font-weight: 300; +} + +.text-danger { + color: #dc3545; +} + +footer { + max-width: 960px; + margin: 0 auto; + padding: 1rem 1.5rem; + border-top: 1px solid #e5e5e5; + color: #6c757d; +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/wwwroot/favicon.ico b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/ProjectConfigurationDemo/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a3a799985c43bc7309d701b2cad129023377dc71 GIT binary patch literal 32038 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" # bash +``` + +This project has its **own** `UserSecretsId`, separate from the other two projects in +the series that use the Secret Manager, so a secret set here is not visible to them. + +The tests supply the connection string through configuration and point it at a +Testcontainers SQL Server container. They never put it back into `appsettings.json`. +Tests marked `[RequiresDockerFact]` are skipped when no Docker daemon is reachable; +set `CONFIGURATION_SERIES_REQUIRE_DOCKER=1` to make a missing daemon a failure. diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/ConnectionStringSourceTests.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/ConnectionStringSourceTests.cs new file mode 100644 index 0000000000..5a10cde9d1 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/ConnectionStringSourceTests.cs @@ -0,0 +1,59 @@ +using Microsoft.Extensions.Configuration; + +namespace SecuringDataLocally.Tests; + +public class ConnectionStringSourceTests +{ + internal static string AppSettingsPath => + Path.Combine(AppContext.BaseDirectory, "appsettings.json"); + + // The article's subject, asserted rather than described: the checked-in + // appsettings.json carries no connection string at all, so the value has to come + // from a user secret or an environment variable. + [Fact] + public void GivenTheShippedAppSettingsFile_WhenTheConnectionStringIsRead_ThenThereIsNone() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile(AppSettingsPath) + .Build(); + + Assert.Null(configuration.GetConnectionString("sqlConnection")); + Assert.Empty(configuration.GetSection("ConnectionStrings").GetChildren()); + } + + [Fact] + public void GivenAConnectionStringSuppliedOutsideTheFile_WhenItIsAddedLast_ThenItIsTheValueTheAppReads() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile(AppSettingsPath) + .AddInMemoryCollection(new Dictionary + { + ["ConnectionStrings:sqlConnection"] = "Server=from-a-secret;Database=CodeMazeCommerce" + }) + .Build(); + + Assert.Equal("Server=from-a-secret;Database=CodeMazeCommerce", + configuration.GetConnectionString("sqlConnection")); + } + + [Fact] + public void GivenAnEnvironmentVariable_WhenTheDoubleUnderscoreSeparatorIsUsed_ThenItReachesTheConnectionStringsSection() + { + Environment.SetEnvironmentVariable( + "ConnectionStrings__sqlConnection", "Server=from-an-environment-variable"); + try + { + var configuration = new ConfigurationBuilder() + .AddJsonFile(AppSettingsPath) + .AddEnvironmentVariables() + .Build(); + + Assert.Equal("Server=from-an-environment-variable", + configuration.GetConnectionString("sqlConnection")); + } + finally + { + Environment.SetEnvironmentVariable("ConnectionStrings__sqlConnection", null); + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/RequiresDockerFactAttribute.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/RequiresDockerFactAttribute.cs new file mode 100644 index 0000000000..18a4b74be1 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/RequiresDockerFactAttribute.cs @@ -0,0 +1,62 @@ +using System.IO.Pipes; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace SecuringDataLocally.Tests; + +/// +/// A [Fact] that is skipped when no Docker daemon is reachable, so the suite stays +/// green on a developer machine without Docker and really runs where a daemon is +/// available, starting a SQL Server container. Set +/// CONFIGURATION_SERIES_REQUIRE_DOCKER=1 to turn a missing daemon into a failure +/// instead of a skip. +/// +public sealed class RequiresDockerFactAttribute : FactAttribute +{ + public RequiresDockerFactAttribute() + { + if (DockerAvailability.IsAvailable || DockerAvailability.IsRequired) + return; + + Skip = "Docker is not reachable on this machine, so the SQL Server container cannot start."; + } +} + +public static class DockerAvailability +{ + private static readonly Lazy Probe = new(Detect); + + public static bool IsAvailable => Probe.Value; + + public static bool IsRequired => + Environment.GetEnvironmentVariable("CONFIGURATION_SERIES_REQUIRE_DOCKER") == "1"; + + // Connect to the daemon rather than looking for a socket or a pipe on disk: + // Docker Desktop leaves its named pipe behind when it is not running, so the + // cheaper check reports a daemon that is not there. + private static bool Detect() + { + if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("DOCKER_HOST"))) + return true; + + try + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + using var pipe = new NamedPipeClientStream(".", "docker_engine", PipeDirection.InOut); + pipe.Connect(timeout: 1000); + + return pipe.IsConnected; + } + + using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + socket.Connect(new UnixDomainSocketEndPoint("/var/run/docker.sock")); + + return socket.Connected; + } + catch (Exception) + { + return false; + } + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/SecretSuppliedConfigurationTests.cs b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/SecretSuppliedConfigurationTests.cs new file mode 100644 index 0000000000..d36254ee8b --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/SecretSuppliedConfigurationTests.cs @@ -0,0 +1,47 @@ +using Microsoft.Extensions.Configuration; +using ProjectConfigurationDemo.Models; +using Testcontainers.MsSql; + +namespace SecuringDataLocally.Tests; + +// The folder must still RUN once the secret is supplied. The connection string +// reaches the EF configuration provider through configuration, never through +// appsettings.json. +public class SecretSuppliedConfigurationTests : IAsyncLifetime +{ + private const string SqlServerImage = "mcr.microsoft.com/mssql/server:2022-latest"; + + private readonly MsSqlContainer _sqlServer = new MsSqlBuilder(SqlServerImage).Build(); + + public async Task InitializeAsync() + { + if (DockerAvailability.IsAvailable || DockerAvailability.IsRequired) + await _sqlServer.StartAsync(); + } + + public async Task DisposeAsync() + { + if (DockerAvailability.IsAvailable || DockerAvailability.IsRequired) + await _sqlServer.DisposeAsync(); + } + + [RequiresDockerFact] + public void GivenTheSecretIsSupplied_WhenTheApplicationConfigurationIsBuilt_ThenTheEntityProviderLoads() + { + var manager = new ConfigurationManager(); + + ((IConfigurationBuilder)manager) + .AddJsonFile(ConnectionStringSourceTests.AppSettingsPath) + .AddInMemoryCollection(new Dictionary + { + ["ConnectionStrings:sqlConnection"] = _sqlServer.GetConnectionString() + }); + + manager.AddEntityConfiguration(); + + var titleConfiguration = manager.GetSection("Pages:HomePage").Get(); + + Assert.NotNull(titleConfiguration); + Assert.Equal("Welcome to the ProjectConfigurationDemo Home Page", titleConfiguration.WelcomeMessage); + } +} diff --git a/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/SecuringDataLocally.Tests.csproj b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/SecuringDataLocally.Tests.csproj new file mode 100644 index 0000000000..b5a51c8064 --- /dev/null +++ b/aspnetcore-webapi/AspNetCoreConfigurationSeries/SecuringDataLocally/SecuringDataLocally.Tests/SecuringDataLocally.Tests.csproj @@ -0,0 +1,27 @@ + + + + net10.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + +