Configuration series: migrate the six samples to net10.0 - #2149
Merged
vladimir-pecanac-main merged 2 commits intoSep 3, 2026
Merged
Conversation
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<string, string?> 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<DbContextOptionsBuilder>, 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Migrates the six-part Configuration in ASP.NET Core series from
CodeMazeBlog/aspnet-core-configurationinto this repository asaspnetcore-webapi/AspNetCoreConfigurationSeries/, one folder per article, with the aggregateAspNetCoreConfigurationSeries.slnat the two-level path CI builds.ConfigurationBasicsbasic-conceptsOptionsPatternoptions-patternOptionsValidationoptions-validationConfigurationProvidersconfiguration-providersCustomConfigurationProvidercustom-configuration-providerSecuringDataLocallysecuring-data-locallyThe
azure-key-vaultbranch is not migrated and is left untouched in the old repository.SecuringDataLocallyis the series tail.The folders are not a strict accumulation
Each folder was built from its own branch, not from its predecessor plus a diff. The branches are a linear git chain, but two steps deliberately remove something because the articles instruct it, and computing folder N+1 as folder N plus additions ships two wrong folders:
OptionsValidationdropsOptionsPattern's named options. Part 3's own text tells the reader to revertTitleColorService,ITitleColorServiceandHomeControllerto not use named options, and to remove thePages:ProductPagesection. SoOptionsPattern/Services/TitleColorService.csholds a monitor and calls.Get(name)behind aGetTitleColor(string)signature, whileOptionsValidation/Services/TitleColorService.csreads.CurrentValuebehind aGetTitleColor()signature. The two files are meant to differ. Please do not harmonise them.CustomConfigurationProviderreplaces part 4's INI registration rather than extending it. Part 5's commit overwrites theConfigureAppConfigurationbody, so from that branch onwardappsettings.inisat in the tree registered by nothing. It is dropped fromCustomConfigurationProviderandSecuringDataLocally, and kept inConfigurationProviders, where it is the article's subject.Common to all six
netcoreapp3.1tonet10.0;<Nullable>enable</Nullable>and<ImplicitUsings>enable</ImplicitUsings>;Startup.csandCreateHostBuildercollapsed into minimal hosting; the checked-inwwwroot/libBootstrap 4 tree dropped (about 40 files, none of it the articles' subject) with_Layout.cshtmltrimmed to match; a test project per folder.Per-folder fixes
ConfigurationBasics — nullable annotations on
HomeModel.DefaultLogLevelandLoggingLevelConfiguration.Default, both of which warn under<Nullable>enable</Nullable>; primary constructor onHomeController.OptionsPattern —
Random.Shared.Next(_colors.Length)inTitleColorService. The original wasnew Random().Next(7)over an eight-element array, so"pink"at index 7 was unreachable; the new test draws 20,000 times and asserts all eight colours appear, which is the test that would have caught it.string.Emptyinitialisers onTitleConfiguration's string properties.appsettings.jsonsets the home page colour tored, so the article's JSON block, its prose and its screenshot agree.OptionsValidation —
AddOptionsWithValidateOnStart<TitleConfiguration>().Bind(...).ValidateDataAnnotations(). The validator is registered withTryAddEnumerablerather thanTryAddSingleton: with one validator the two are indistinguishable, butTryAddSingletonis a no-op when any descriptor for the service type already exists, so a second validator added later would silently never run.Validate(string? name, ...)to matchIValidateOptions<T>and clearCS8767. Failures accumulate throughValidateOptionsResultBuilder, so a configuration with two broken rules reports both instead of only the first.[Required]and[MaxLength(60)]restored onWelcomeMessage, which makes theSystem.ComponentModel.DataAnnotationsusing live again.TitleColorServiceholds the monitor and reads.CurrentValueinside the method instead of capturing it in the constructor of a singleton, which silently defeated reload.ConfigurationProviders —
appsettings.ini's section header corrected from[Logging:Level]to[Logging:LogLevel].LevelandLogLevelare different keys, so the INI file never overrode the JSON logging configuration; the corrected header does, and a test asserts both halves. The INI file is copied to the output directory so the demonstration works from a clean clone. Fresh<UserSecretsId>.CustomConfigurationProvider —
Dictionary<string, string?>on the seeding helper and its literal, clearingCS8619againstConfigurationProvider.Data.StringComparer.OrdinalIgnoreCaseon theToDictionarybranch as well as the seeding branch: only the seeding branch had it, so the sample worked on the first run and was case-sensitive on every run after, withGetSection("pages:homepage")coming back empty.EFConfigurationSourcetakes a connection string instead of anAction<DbContextOptionsBuilder>, and registration is one line through anAddEntityConfiguration()extension onConfigurationManager, which also removes the interimconfigBuilder.Build().using var. EF Core 10.0.11. The connection string points at LocalDB rather than.\SQLEXPRESS, which is a separate install.SecuringDataLocally — ships with no
ConnectionStringssection inappsettings.jsonat all. That is the article's whole subject, andProgram.csstill readsConnectionStrings:sqlConnectionand hands it to the EF provider, so the project does not start until a user secret or an environment variable supplies it. The folderREADME.mdgives thedotnet user-secrets setline. The literal was deliberately not put back to make anything green; the tests supply it through configuration instead.The three projects that use the Secret Manager (
ConfigurationProviders,CustomConfigurationProvider,SecuringDataLocally) each carry a fresh, distinctUserSecretsId. The source repository is one project across branches and carries one GUID; copying it into three projects would give them a shared secret store and falsify the last article's central claim that user secrets are scoped to a project.Tests, and what runs where
Every folder has a test project asserting its own article's argument. Verified locally on SDK 10.0.302, Release configuration, per folder:
ConfigurationBasicsOptionsPatternOptionsValidationConfigurationProvidersCustomConfigurationProviderSecuringDataLocallydotnet buildover the aggregate solution is clean, with zero warnings.The Testcontainers half, stated plainly.
CustomConfigurationProvider.TestsandSecuringDataLocally.Testsstart a realmcr.microsoft.com/mssql/server:2022-latestcontainer. Those tests are marked[RequiresDockerFact], which skips them when no Docker daemon answers, sodotnet teststays green on a machine without Docker rather than failing. The probe connects to the daemon (named pipe on Windows,/var/run/docker.sockelsewhere) rather than looking for a file on disk, because Docker Desktop leaves its named pipe behind when it is not running.That is an environment gate, and an environment gate can hide a real failure behind a skip. Two things about that. The four folders that need no database are unconditional and cover everything that does not need SQL Server. And
CONFIGURATION_SERIES_REQUIRE_DOCKER=1turns the skip into a hard failure, which is the setting to use when you want proof the container tests really ran.The container tests were skipped on the machine that prepared this branch, which has no Docker installed, and they really ran on CI. From the
Build & testlog on this PR, every leg reportsSkipped: 0:The two folders that need a database took 34 s and 24 s against sub-second times everywhere else, which is the SQL Server container starting and
EnsureCreated()running against it. So the answer to "does the image start on a runner" is yes, measured rather than assumed. No leg is a vacuous green.One thing worth a look
Microsoft.Extensions.Configuration.Iniis already supplied by the ASP.NET Core shared framework onnet10.0, so NuGet reports the explicitPackageReferenceinConfigurationProvidersas redundant (NU1510). It is kept, withNoWarnand a comment, because the article tells the reader to install it and because a reader following the same steps in a non-web project genuinely does need it. If you would rather the sample not carry a redundant reference, it comes out in one line.