Skip to content

Configuration series: migrate the six samples to net10.0 - #2149

Merged
vladimir-pecanac-main merged 2 commits into
CodeMazeBlog:mainfrom
vladimir-pecanac-main:seo/batch6-config-series
Sep 3, 2026
Merged

Configuration series: migrate the six samples to net10.0#2149
vladimir-pecanac-main merged 2 commits into
CodeMazeBlog:mainfrom
vladimir-pecanac-main:seo/batch6-config-series

Conversation

@vladimir-pecanac-main

@vladimir-pecanac-main vladimir-pecanac-main commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Migrates the six-part Configuration in ASP.NET Core series from CodeMazeBlog/aspnet-core-configuration into this repository as aspnetcore-webapi/AspNetCoreConfigurationSeries/, one folder per article, with the aggregate AspNetCoreConfigurationSeries.sln at the two-level path CI builds.

Folder Article Source branch
ConfigurationBasics How to Use IConfiguration in ASP.NET Core basic-concepts
OptionsPattern Options Pattern in ASP.NET Core With IOptions options-pattern
OptionsValidation Options Validation in ASP.NET Core options-validation
ConfigurationProviders ASP.NET Core Configuration Providers configuration-providers
CustomConfigurationProvider Custom Configuration Provider in ASP.NET Core With EF Core custom-configuration-provider
SecuringDataLocally User Secrets vs Environment Variables in ASP.NET Core securing-data-locally

The azure-key-vault branch is not migrated and is left untouched in the old repository. SecuringDataLocally is 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:

  • OptionsValidation drops OptionsPattern's named options. Part 3's own text tells the reader to revert TitleColorService, ITitleColorService and HomeController to not use named options, and to remove the Pages:ProductPage section. So OptionsPattern/Services/TitleColorService.cs holds a monitor and calls .Get(name) behind a GetTitleColor(string) signature, while OptionsValidation/Services/TitleColorService.cs reads .CurrentValue behind a GetTitleColor() signature. The two files are meant to differ. Please do not harmonise them.
  • CustomConfigurationProvider replaces part 4's INI registration rather than extending it. Part 5's commit overwrites the ConfigureAppConfiguration body, so from that branch onward appsettings.ini sat in the tree registered by nothing. It is dropped from CustomConfigurationProvider and SecuringDataLocally, and kept in ConfigurationProviders, where it is the article's subject.

Common to all six

netcoreapp3.1 to net10.0; <Nullable>enable</Nullable> and <ImplicitUsings>enable</ImplicitUsings>; Startup.cs and CreateHostBuilder collapsed into minimal hosting; the checked-in wwwroot/lib Bootstrap 4 tree dropped (about 40 files, none of it the articles' subject) with _Layout.cshtml trimmed to match; a test project per folder.

Per-folder fixes

ConfigurationBasics — nullable annotations on HomeModel.DefaultLogLevel and LoggingLevelConfiguration.Default, both of which warn under <Nullable>enable</Nullable>; primary constructor on HomeController.

OptionsPatternRandom.Shared.Next(_colors.Length) in TitleColorService. The original was new 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.Empty initialisers on TitleConfiguration's string properties. appsettings.json sets the home page colour to red, so the article's JSON block, its prose and its screenshot agree.

OptionsValidationAddOptionsWithValidateOnStart<TitleConfiguration>().Bind(...).ValidateDataAnnotations(). The validator is registered with TryAddEnumerable rather than TryAddSingleton: with one validator the two are indistinguishable, but TryAddSingleton is 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 match IValidateOptions<T> and clear CS8767. Failures accumulate through ValidateOptionsResultBuilder, so a configuration with two broken rules reports both instead of only the first. [Required] and [MaxLength(60)] restored on WelcomeMessage, which makes the System.ComponentModel.DataAnnotations using live again. TitleColorService holds the monitor and reads .CurrentValue inside the method instead of capturing it in the constructor of a singleton, which silently defeated reload.

ConfigurationProvidersappsettings.ini's section header corrected from [Logging:Level] to [Logging:LogLevel]. Level and LogLevel are 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>.

CustomConfigurationProviderDictionary<string, string?> on the seeding helper and its literal, clearing CS8619 against ConfigurationProvider.Data. StringComparer.OrdinalIgnoreCase on the ToDictionary branch 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, with GetSection("pages:homepage") coming back empty. EFConfigurationSource takes a connection string instead of an Action<DbContextOptionsBuilder>, and registration is one line through an AddEntityConfiguration() extension on ConfigurationManager, which also removes the interim configBuilder.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 ConnectionStrings section in appsettings.json at all. That is the article's whole subject, and Program.cs still reads ConnectionStrings:sqlConnection and hands it to the EF provider, so the project does not start until a user secret or an environment variable supplies it. The folder README.md gives the dotnet user-secrets set line. 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, distinct UserSecretsId. 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:

Folder Build Tests
ConfigurationBasics success 5 passed
OptionsPattern success 4 passed
OptionsValidation success 6 passed
ConfigurationProviders success 4 passed
CustomConfigurationProvider success 3 tests, all Testcontainers
SecuringDataLocally success 3 passed, 1 Testcontainers

dotnet build over the aggregate solution is clean, with zero warnings.

The Testcontainers half, stated plainly. CustomConfigurationProvider.Tests and SecuringDataLocally.Tests start a real mcr.microsoft.com/mssql/server:2022-latest container. Those tests are marked [RequiresDockerFact], which skips them when no Docker daemon answers, so dotnet test stays green on a machine without Docker rather than failing. The probe connects to the daemon (named pipe on Windows, /var/run/docker.sock elsewhere) 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=1 turns 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 & test log on this PR, every leg reports Skipped: 0:

Passed!  - Failed: 0, Passed: 5, Skipped: 0, Total: 5 - ConfigurationBasics.Tests.dll (net10.0)
Passed!  - Failed: 0, Passed: 4, Skipped: 0, Total: 4 - OptionsPattern.Tests.dll (net10.0)
Passed!  - Failed: 0, Passed: 6, Skipped: 0, Total: 6 - OptionsValidation.Tests.dll (net10.0)
Passed!  - Failed: 0, Passed: 4, Skipped: 0, Total: 4 - ConfigurationProviders.Tests.dll (net10.0)
Passed!  - Failed: 0, Passed: 3, Skipped: 0, Total: 3, Duration: 34 s - CustomConfigurationProvider.Tests.dll (net10.0)
Passed!  - Failed: 0, Passed: 4, Skipped: 0, Total: 4, Duration: 24 s - SecuringDataLocally.Tests.dll (net10.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.Ini is already supplied by the ASP.NET Core shared framework on net10.0, so NuGet reports the explicit PackageReference in ConfigurationProviders as redundant (NU1510). It is kept, with NoWarn and 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.

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.
@vladimir-pecanac-main
vladimir-pecanac-main merged commit fb6c063 into CodeMazeBlog:main Sep 3, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant