Skip to content

Add opt-in interface contracts for module configuration - #42

Merged
fw2568 merged 2 commits into
mainfrom
feature/module-interfaces
Jul 21, 2026
Merged

Add opt-in interface contracts for module configuration#42
fw2568 merged 2 commits into
mainfrom
feature/module-interfaces

Conversation

@fw2568

@fw2568 fw2568 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Part B of the reflection cleanup. Purely additive, no behavior change for existing modules.

What

Statically typed, trimming/AOT-friendly alternatives to the convention (reflection) module methods:

Interface Replaces convention method Package
IServiceConfiguringModule ConfigureServices Hosuto
IApplicationConfiguringModule Configure Hosuto.Hosting.AspNetCore
IContainerConfiguringModule ConfigureContainer Hosuto.SimpleInjector
IUseSimpleInjectorModule UseSimpleInjector Hosuto.SimpleInjector
IAddSimpleInjectorModule AddSimpleInjector Hosuto.SimpleInjector

How

Each bootstrap call site now dispatches to the interface when the module implements it, and falls back to the existing ModuleMethodInvoker reflection convention otherwise. The interfaces receive the same values the convention injects (the first IServiceProvider = the modules host services, which in the SimpleInjector model is the module container).

Why

  • Interface modules are checked at compile time and avoid the per-method reflection lookup/invoke — the basis for trimming/Native-AOT friendliness.
  • Fully opt-in: existing convention-based modules keep working unchanged (the reflection path is the fallback).

Tests

New ModuleInterfaceContractsTests: an interface-only module is configured end-to-end (ConfigureServices + ConfigureContainer run, hosted handler resolves the container-registered service), and IApplicationConfiguringModule.Configure is invoked for a web module. All suites pass on net6.0/net8.0/net9.0; the convention fallback remains covered by existing tests.

Introduce statically typed, trimming/AOT-friendly alternatives to the convention
(reflection) module methods: IServiceConfiguringModule (ConfigureServices),
IApplicationConfiguringModule (Configure), and IContainerConfiguringModule /
IUseSimpleInjectorModule / IAddSimpleInjectorModule for the SimpleInjector hooks.

Each bootstrap call site now dispatches to the interface when the module
implements it and falls back to the existing reflection convention otherwise.
Purely additive: existing convention-based modules are unaffected; interface
modules are compile-time checked and avoid reflection.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds opt-in, statically typed module configuration interfaces (as alternatives to reflection-based “convention methods”) and updates the bootstrap call sites to dispatch to these interfaces when implemented, falling back to ModuleMethodInvoker otherwise—aiming to improve trimming/AOT friendliness without breaking existing modules.

Changes:

  • Introduces new opt-in interfaces for service, app pipeline, and SimpleInjector module/container configuration.
  • Updates hosting/bootstrap handlers to prefer direct interface dispatch and preserve the existing reflection convention as fallback.
  • Adds an end-to-end test covering interface-only modules (services + container + ASP.NET Core Configure).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/Hosuto.SimpleInjector.Tests/Modules/Hosting/ModuleInterfaceContractsTests.cs New tests validating interface-based module configuration end-to-end.
src/Hosuto/Modules/IServiceConfiguringModule.cs Adds opt-in ConfigureServices interface contract.
src/Hosuto.SimpleInjector/Modules/ModuleContainerContracts.cs Adds opt-in SimpleInjector-related interface contracts (ConfigureContainer, UseSimpleInjector, AddSimpleInjector).
src/Hosuto.SimpleInjector/Modules/Hosting/BootstrapHostFilter.cs Dispatches UseSimpleInjector / ConfigureContainer via interfaces when implemented.
src/Hosuto.SimpleInjector/Modules/Hosting/AddSimpleInjectorModuleServicesFilter.cs Dispatches AddSimpleInjector via interface when implemented.
src/Hosuto.Hosting/Modules/Hosting/DefaultBootstrapHostHandler.cs Dispatches ConfigureServices via IServiceConfiguringModule when implemented.
src/Hosuto.Hosting.AspNetCore/Modules/IApplicationConfiguringModule.cs Adds opt-in Configure (ASP.NET Core pipeline) interface contract.
src/Hosuto.Hosting.AspNetCore/Modules/Hosting/WebModuleBootstrapHostHandler.cs Dispatches web Configure via IApplicationConfiguringModule when implemented.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +14 to +15
/// <param name="serviceProvider">The modules host service provider (same instance the
/// conventional method receives as its first <see cref="IServiceProvider"/> parameter).</param>
Comment on lines +14 to +15
/// <param name="serviceProvider">The modules host service provider (same instance the
/// conventional method receives as its first <see cref="IServiceProvider"/> parameter).</param>
Comment on lines +14 to +16
/// <param name="serviceProvider">The module's service provider - in the SimpleInjector
/// hosting model this is the module <see cref="Container"/> itself, matching the first
/// <see cref="IServiceProvider"/> parameter of the conventional method.</param>
Tests: the interface modules now implement the methods explicitly, so they are
invisible to the reflection convention (Type.GetMethod returns null). This proves
the interface-dispatch path runs rather than the reflection fallback - verified by
forcing the fallback, which makes the test fail. Adds coverage for
IUseSimpleInjectorModule and IAddSimpleInjectorModule.

Docs: drop the imprecise "this is the module Container itself" claim on
ConfigureContainer's serviceProvider (distinct from the container parameter);
document that the interface takes precedence over a same-named convention method,
and that the injected provider matches the convention only when IServiceProvider
was the convention method's first parameter.
@fw2568

fw2568 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback (Copilot + an independent Sonnet review) in 6257d70:

  • Test hardening (key): the interface test modules now implement the methods explicitly, so Type.GetMethod("…") returns null and the reflection convention can't find them — proving the interface-dispatch path runs, not the fallback. Verified by temporarily forcing the fallback, which makes the test fail. Added coverage for IUseSimpleInjectorModule and IAddSimpleInjectorModule.
  • Docs: dropped the imprecise "this is the module Container itself" wording on ConfigureContainer's serviceProvider (now clearly distinct from the container parameter); fixed the possessive grammar; documented interface-over-convention precedence and that provider fidelity holds only when IServiceProvider was the convention method's first parameter.

Consciously left (documented as known, not regressions): the container-vs-c closure in ConfigureContainer is pre-existing (same as the note on #41); the reflection fallback stays untouched by design.

@fw2568
fw2568 merged commit 25270c6 into main Jul 21, 2026
2 of 3 checks passed
fw2568 added a commit that referenced this pull request Jul 21, 2026
* Add opt-in minimal-API (WebApplication) inner host for web modules

New UseAspNetCoreMinimal() hosts a web module on a minimal-API WebApplication
instead of the classic HostBuilder + ConfigureWebHostDefaults host (net6+,
additive - the existing path stays the default). The module authoring model is
unchanged: ConfigureServices runs against builder.Services pre-build, and
Configure is deferred to pipeline build (start) via an IStartupFilter so it runs
after the module container (SimpleInjector/Autofac) has been configured. The
SimpleInjector bootstrap filter composes unchanged because WebApplication is an
IHost. Builds on the module interfaces from #42, with reflection convention as
fallback.

Adds a framework test (module services + container + endpoint with a
container-resolved dependency, over the real bootstrap path) and a net10 Razor
sample under samples/dotnet/minimal.

Known limitation (documented, follow-up): module static web assets are not yet
mapped on the minimal host - the classic file-provider/XML-manifest loader does
not fit .NET 9+ endpoint-based static web assets.

* Add IEndpointConfiguringModule; address review feedback on minimal host

Endpoint contract (from review discussion): IEndpointConfiguringModule lets a
module map endpoints the minimal-API way (endpoints.MapGet/MapRazorPages/...),
wired into both the minimal-API and classic web hosts so the same module works
on either backend. The Startup-era Configure(IApplicationBuilder) stays for
middleware.

Review fixes for the minimal host (Copilot + Sonnet):
- Honor IModuleHostingOptions.ValidateServiceProvider on the minimal path (was
  dropped, reintroducing the #40 ValidateOnBuild failure in Development on
  .NET 9+). Applied via builder.Host.UseDefaultServiceProvider - verified that
  builder.WebHost's factory is ignored by WebApplicationBuilder while builder.Host
  is honored.
- Pass a HostBuilderContext reflecting the inner module host (its environment +
  configuration) to the module services/configuration filters, instead of the
  outer host context.
- Add HostDefaults.ApplicationKey to the module configuration for parity with the
  classic host.
- Doc: note ConfigureBuilderAction runs against the restricted WebApplicationBuilder.Host.

Tests: idiomatic IEndpointConfiguringModule mapping over the real minimal path,
plus ValidateServiceProvider on/off (net9+). Sample updated to author the module
with all three contracts (services/middleware/endpoints). Green on net6/8/9.

* Fix CI build; support conventional MapEndpoints

CI: add the net10 samples/dotnet/minimal projects to Hosuto.sln. The pipeline
restores the solution and then builds **/*.csproj --no-restore; projects missing
from the solution were never restored and failed with NETSDK1004.

MapEndpoints: dispatch also to a conventional MapEndpoints(IEndpointRouteBuilder)
method when the module does not implement IEndpointConfiguringModule, matching the
interface-or-convention model of the other module methods (both web hosts). Adds a
convention-based endpoint test.
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.

2 participants