Skip to content

Add AddNServiceBusInstallers for Generic Host Integration#7709

Merged
DavidBoike merged 15 commits into
masterfrom
installer-changeup
Apr 17, 2026
Merged

Add AddNServiceBusInstallers for Generic Host Integration#7709
DavidBoike merged 15 commits into
masterfrom
installer-changeup

Conversation

@danielmarbach
Copy link
Copy Markdown
Contributor

@danielmarbach danielmarbach commented Apr 16, 2026

This PR introduces AddNServiceBusInstallers on IServiceCollection, providing a modern, host-integrated alternative to the obsolete Installer.Setup() API.

The new API enables infrastructure installation for all registered NServiceBus endpoints as part of the .NET Generic Host lifecycle. It is designed for CI/CD pipelines, deployment scenarios, and one-shot provisioning tasks while preserving composability and alignment with Microsoft.Extensions.Hosting.

This approach ensures that NServiceBus participates fully in the Generic Host rather than introducing a parallel execution path.

Motivation

EnableInstallers() only executes installers when an endpoint fully starts. Users who need one-shot installer execution, such as during deployment or automated provisioning, must rely on the obsolete Installer.Setup() API.

AddNServiceBusInstallers provides a modern, discoverable, and composable solution that:

  • Integrates with the .NET Generic Host lifecycle
  • Supports multi-endpoint scenarios
  • Preserves EndpointConfiguration as the canonical entry point
  • Eliminates legacy installer patterns
  • Uses only public hosting extension points

Usage

Normal Application Run

var endpointConfiguration = new EndpointConfiguration("Sales");

// Optional: run installers during normal startup
endpointConfiguration.EnableInstallers();

services.AddNServiceBusEndpoint(endpointConfiguration);

await builder.Build().RunAsync();

Behavior:

  • Endpoints start normally.
  • Installers run only if enabled via EnableInstallers().
  • The host continues running.

Install Infrastructure and Exit

var endpointConfiguration = new EndpointConfiguration("Sales");

services.AddNServiceBusEndpoint(endpointConfiguration);
services.AddNServiceBusInstallers();

await builder.Build().RunAsync();

Behavior:

  • Installers run for all registered endpoints.
  • Message processing does not start.
  • The host shuts down gracefully after installation completes.

Optional: Override Automatic Shutdown

services.AddNServiceBusInstallers(options =>
{
    options.ShutdownBehavior = InstallersShutdownBehavior.Continue;
});

This allows the host to remain running after installation without relying on registration order or implicit overrides.

Design Principles

  • EndpointConfiguration remains canonical
    Endpoints are defined exactly as before.

  • Host-level installer mode
    Installer execution is determined by the host, not individual endpoints.

  • All-or-nothing installation
    In installer mode, infrastructure is installed for all registered endpoints.

  • Generic Host alignment
    The implementation relies solely on public extension points such as hosted services and IHostApplicationLifetime.

  • No side-channel lifecycle
    NServiceBus rides along with the Generic Host rather than bypassing it.

  • Multi-endpoint support
    The design naturally supports applications hosting multiple endpoints.

Lifecycle Overview

The following diagrams illustrate how installer mode integrates with the .NET Generic Host lifecycle compared to a normal application run.

Installer Mode (AddNServiceBusInstallers)

sequenceDiagram
    autonumber
    participant App as Application
    participant Host as Generic Host
    participant Endpoint as NServiceBus Endpoints
    participant Installers as Installers
    participant Lifetime as Host Lifetime

    App->>Host: Build().RunAsync()
    Host->>Endpoint: StartingAsync()
    Endpoint->>Installers: Execute installers
    Installers-->>Endpoint: Completed
    Host->>Endpoint: StartAsync() (skipped)
    Host->>Lifetime: StopApplication()
    Lifetime-->>Host: Trigger shutdown
    Host-->>App: RunAsync() returns
Loading

Normal Application Run

sequenceDiagram
    autonumber
    participant App as Application
    participant Host as Generic Host
    participant Endpoint as NServiceBus Endpoints
    participant Runtime as Messaging Runtime

    App->>Host: Build().RunAsync()
    Host->>Endpoint: StartingAsync()
    Endpoint-->>Host: Prepared
    Host->>Endpoint: StartAsync()
    Endpoint->>Runtime: Start message processing
    Runtime-->>Endpoint: Running
    Note over Host: Application runs normally
Loading

Evolution from Previous Designs

PR #7683AddNServiceBusEndpointInstaller

Introduced a dedicated installer-only registration. This approach implied a 1:1 relationship between endpoints and installers, which conflicted with the requirement to install infrastructure for all endpoints in a host.

PR #7691InstallNServiceBusEndpoints on HostApplicationBuilder

Built and discarded the host internally, bypassing the Generic Host lifecycle. This created a side-channel execution path and violated the principle that NServiceBus should integrate with, not circumvent, the host.

Final Design

AddNServiceBusInstallers treats installers as a host-level additive concern, ensuring full alignment with the Generic Host and avoiding parallel execution paths until we have a way to properly generated deployment manifests and separate deployments into dedicated CLI tools similar to EF migration.

Trade-offs

  • Hosted services may briefly start before shutdown is triggered.
  • This behavior is an inherent constraint of the Generic Host lifecycle.
  • Avoiding it would require bypassing the host, which was intentionally rejected.

The brief start-and-stop behavior is the honest cost of participating in the Generic Host lifecycle.

Benefits

  • Uses only public .NET Generic Host extension points
  • Provides a modern replacement for Installer.Setup()
  • Preserves EndpointConfiguration as the canonical entry point
  • Supports multi-endpoint scenarios
  • Avoids side-channel execution paths
  • Ensures composability with configuration, logging, and dependency injection
  • Aligns with modern .NET hosting principles

@danielmarbach danielmarbach changed the title Installer changeup Add AddNServiceBusInstallers for Generic Host Integration Apr 16, 2026
@danielmarbach danielmarbach added this to the 10.2.0 milestone Apr 16, 2026
Comment thread src/NServiceBus.Core/Hosting/ServiceCollectionExtensions.cs
Comment on lines +125 to +127
/// Other registered <see cref="IHostedService"/> and <see cref="BackgroundService"/> implementations
/// may briefly start before shutdown takes effect. Services that cooperatively check the
/// <see cref="IHostApplicationLifetime.ApplicationStopping"/> cancellation token will gracefully abort.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So if you have a hosted service that you don't want even starting a breath, your options are:

  1. Inject IHostApplicationLifetime, check ApplicationStopping token before "doing anything important" but that may not signal immediately?
  2. Use the same check you're using to decide to include AddNServiceBusInstallers (cmd line param, config, envvar, whatever) to exit early from that work.

Is that right?

Are there any options for when you have a hosted service whose code you don't directly control?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, that’s basically right.

Those first two options are the practical ones for hosted services you own:

  • Inject IHostApplicationLifetime and check ApplicationStopping before doing expensive or externally visible work. It won’t prevent StartAsync from being called, but it can help the service no-op quickly.
  • Use the same condition you already use for AddNServiceBusInstallers (config, env var, command line switch, etc.) and exit early from the hosted service.

For hosted services you don’t control or don't behave like "good host citizen", there usually isn’t a magic hook to stop the Generic Host from calling StartAsync once the service is registered. In practice the options are:

  • Conditionally don’t register that hosted service in installer mode
  • Use a separate installer-specific host/process that only wires up what is needed
  • Build a dedicated composition path for installer runs

That’s really the core constraint here: once something is registered as an IHostedService, the host lifecycle is going to treat it like one.

As described in the PR description that captured our discussion we intentionally aligned with the normal Generic Host lifecycle instead of creating a separate/private execution path that bypasses it. That means there can be a brief start/stop window for hosted services during installer runs, but it keeps behavior consistent with standard hosting expectations I believe.

@danielmarbach danielmarbach marked this pull request as ready for review April 17, 2026 14:59
@DavidBoike
Copy link
Copy Markdown
Member

@DavidBoike DavidBoike merged commit e0a8271 into master Apr 17, 2026
4 checks passed
@DavidBoike DavidBoike deleted the installer-changeup branch April 17, 2026 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants