Skip to content

Document minimal hosting and ASP.NET Core parameter binding - #204

Merged
tillig merged 3 commits into
mainfrom
feature/aspnetcore-docs
Sep 1, 2026
Merged

tillig merged 3 commits into
mainfrom
feature/aspnetcore-docs

Conversation

@tillig

@tillig tillig commented Sep 1, 2026 •

Copy link
Copy Markdown
Member

Fixes #175. Fixes #179.

Minimal hosting (#175)

The page only ever showed the Startup class style, so the shape the current templates actually produce was undocumented — which is what #175 asked for. Minimal hosting now leads; Startup is kept after it, since plenty of applications still use it.

The rule people trip over — you cannot return an IServiceProvider from ConfigureServices, and cannot add the factory to the service collection — moved up into the Quick Start, because it applies to both styles and was previously buried in one of them.

Parameter binding (#179)

This is the one worth reading. Since .NET 7, ASP.NET Core asks IServiceProviderIsService whether a parameter is a service, to decide between injecting it and binding it from the request body. Autofac answers yes for every collection type, because collections always resolve and simply come back empty when nothing is registered.

So a collection parameter silently stops binding from the body and starts arriving empty:

// On .NET 6 this bound from the request body.
// On .NET 7+ it arrives empty.
[HttpPost]
public IActionResult Post(IReadOnlyCollection<WeatherForecast> forecasts)

Nothing throws, which is exactly why it earns a warning directive. The fix is an explicit [FromBody]. The section also explains why Autofac won't special-case empty collections — it would make the answer depend on registration order, break the case where you do want an empty set injected, and contradict documented collection behavior — so the guidance doesn't read like an unfixed oversight.

Verified against AutofacServiceProvider.IsService, which is ComponentRegistry.IsRegistered(new TypedService(serviceType)), and the exact collection set in InternalTypeExtensions.IsGenericListOrCollectionInterfaceType plus CollectionRegistrationSource.

That turned up a second gap. resolve/relationships.rst listed IEnumerable<B>, IList<B> and ICollection<B> but never mentioned IReadOnlyCollection<B>, IReadOnlyList<B> or arrays — precisely the types reported in the issue. Since the new section points readers there, it's fixed here too. The heading is deliberately left alone: an upstream Autofac.Extensions.DependencyInjection issue links that exact anchor.

Keyed services

AddKeyedSingleton, [FromKeyedServices] and KeyedService.AnyKey all work through Autofac, cross-referenced to core Autofac's own keyed service support. Confirmed against KeyedServiceTests and FromKeyedServicesAttributeExtensions.

Code that could not have compiled

The child lifetime scope sample had four defects: a variable named builder where containerBuilder was declared, constructors named Startup inside StartupOne and StartupTwo, a missing statement terminator on Task.WhenAll, and stray indentation. These were the last of the four class/constructor mismatches I catalogued during the structural pass.

Obsolete APIs and dead links

app.UseMvc() becomes routing/endpoint calls, loggerFactory.AddConsole/AddDebug are gone (logging is configured on the host), and IHostingEnvironment becomes IWebHostEnvironment.

All ten docs.asp.net links are gone. The nine-link "helpful links" list collapsed to a single pointer at Microsoft's DI documentation — a list of deep links into another product's docs is what rotted in the first place. Also dropped a 2016 blog post about RC2 behavior and its comment link, and the archived aspnet/DependencyInjection link now points into dotnet/runtime.

Coordination note

Your other session committed feature/link-new-examples locally, which also touches this file's Example section. I deliberately left that section untouched here so the two don't collide — that change belongs to its PR, and mine applies cleanly either way round.

Verification

sphinx -b html -W --keep-going, doc8 and pre-commit clean. Rendered HTML read for both new sections, and the page heading tree checked.


Follow-up: nested inline markup

The other session spotted a real bug here while my files were briefly on its branch — thanks for passing it along. The Quick Start callout was:

**You cannot return an ``IServiceProvider`` from ``ConfigureServices``, ...**

RST doesn't nest inline markup, so those double backticks rendered as literal backtick characters inside the bold:

<strong>You cannot return an ``IServiceProvider`` from ``ConfigureServices``, ...</strong>

sphinx -W can't catch this — the markup is perfectly valid, just not what was meant — which is why it survived the build being clean. Fixed by closing the bold around each literal, the same pattern the [FromBody] callout further down this page already used. It now renders as <strong> plus <code>, verified in the built HTML.

I scanned every page for the whole bug class, in both nesting directions (literal-inside-bold, literal-inside-italic, bold-inside-literal). Exactly two instances existed: this one and integration/servicefabric.rst:115, which had the identical defect and rendered wrong for the same reason.

That second fix is included here even though it predates this branch and isn't ASP.NET Core. It's one line, it's the same defect, and leaving a known-broken render in place purely to keep the diff on-topic seemed like the wrong trade. Easy to split out if you'd rather.

Fixes #175. Fixes #179.

**Minimal hosting.** The page only ever showed the Startup class style, so the
shape the current templates actually produce was undocumented. Minimal hosting
now leads, with the Startup class kept after it since plenty of applications
still use it. The rule people trip over - you cannot return an IServiceProvider
from ConfigureServices and cannot add the factory to the service collection -
moves up into the Quick Start, because it applies to both styles.

**Parameter binding.** Since .NET 7, ASP.NET Core asks IServiceProviderIsService
whether a parameter is a service to decide between injecting it and binding it
from the request body. Autofac answers yes for every collection type, because
collections always resolve and come back empty when nothing is registered, so a
collection parameter silently stops binding from the body and starts arriving
empty. Nothing throws, which is what makes it worth a warning. The fix is an
explicit [FromBody], and the section explains why Autofac won't special-case
empty collections rather than leaving it looking like an oversight.

resolve/relationships.rst under-reported the collection types behind that: it
listed IEnumerable, IList and ICollection but not IReadOnlyCollection,
IReadOnlyList or arrays, which are exactly the ones reported in the issue. The
heading is left alone so the anchor an upstream issue links to still resolves.

**Keyed services.** The Microsoft keyed service APIs work through Autofac, so
AddKeyedSingleton, [FromKeyedServices] and KeyedService.AnyKey are documented
with a cross-reference to core Autofac's own keyed service support.

Code fixes in the child lifetime scope sample, which could not have compiled:
a variable named builder where containerBuilder was declared, constructors
named Startup inside StartupOne and StartupTwo, a missing statement
terminator, and stray indentation.

Obsolete APIs in the retained samples: app.UseMvc() becomes routing and
endpoint calls, the loggerFactory.AddConsole and AddDebug calls are gone since
logging is configured on the host, and IHostingEnvironment becomes
IWebHostEnvironment.

Dead links: ten docs.asp.net links are gone. The nine-link "helpful links" list
is now a single pointer at Microsoft's dependency injection documentation,
since a list of deep links into another product's docs is what rotted in the
first place. Also drops a 2016 blog post about an RC2 behavior, and the
archived aspnet/DependencyInjection repository link now points into
dotnet/runtime.
RST doesn't nest inline markup, so double backticks inside a bold span render
as literal backtick characters rather than code. Sphinx can't warn about it -
the markup is valid, just not what was meant - so the only way to catch it is
to read the output.

Fixes the Quick Start callout on the ASP.NET Core page, which was inconsistent
with the [FromBody] callout further down the same page that already closed the
bold around the literal.

servicefabric.rst:115 had the identical defect and rendered wrong for the same
reason. It predates this branch and isn't ASP.NET Core, but it's the same
one-line fix and leaving a known-broken render in place to keep the diff tidy
seemed worse. A scan of every page turns up no other instances, in either
nesting direction.
The page now documents minimal hosting and the Startup class as separate styles,
but the Example section pointed only at AspNetCoreExample. AspNetCoreNoStartupExample
and AspNetCoreChildLifetimeScope had no link from anywhere in the documentation,
so the two hosting styles this page added had no runnable counterpart a reader
could find.
@tillig

tillig commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

Added the example link fixup here as agreed, in cf3f593 — one commit touching only the Example section of docs/integration/aspnetcore.rst.

The section listed only AspNetCoreExample, and AspNetCoreNoStartupExample and AspNetCoreChildLifetimeScope had no link from anywhere in the documentation. Since this PR splits the page into minimal hosting and Startup class styles, both of those now have a runnable counterpart a reader can actually find. MultitenantExample.AspNetCore gets a pointer to the multitenant page, where it is already covered, rather than a duplicate entry.

With this in, every example project in the Examples repository is linked from the page that documents it. The only two without links are ConfigurationExampleInterface and ConfigurationExamplePlugin, which are supporting libraries for ConfigurationExample and correctly have no page of their own.

Verified by simulating the merge — main with this branch's versions of its two files — and checking every Examples/tree/<branch>/src/<dir> reference against a local clone: all on main, all pointing at a directory that exists. pre-commit including doc8 passes. Also confirmed 6c6b800 fixed the nested inline literal I flagged earlier.

@tillig
tillig merged commit c9227fd into main Sep 1, 2026
2 checks passed
@tillig
tillig deleted the feature/aspnetcore-docs branch September 1, 2026 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant