Rystem.DependencyInjection.Web 10.0.7

dotnet add package Rystem.DependencyInjection.Web --version 10.0.7
                    
NuGet\Install-Package Rystem.DependencyInjection.Web -Version 10.0.7
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Rystem.DependencyInjection.Web" Version="10.0.7" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Rystem.DependencyInjection.Web" Version="10.0.7" />
                    
Directory.Packages.props
<PackageReference Include="Rystem.DependencyInjection.Web" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Rystem.DependencyInjection.Web --version 10.0.7
                    
#r "nuget: Rystem.DependencyInjection.Web, 10.0.7"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Rystem.DependencyInjection.Web@10.0.7
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Rystem.DependencyInjection.Web&version=10.0.7
                    
Install as a Cake Addin
#tool nuget:?package=Rystem.DependencyInjection.Web&version=10.0.7
                    
Install as a Cake Tool

What is Rystem?

Rystem.DependencyInjection.Web

Rystem.DependencyInjection.Web adds a runtime-rebuild layer on top of Rystem.DependencyInjection for ASP.NET Core applications.

The package is built for scenarios where the application must keep serving requests while the underlying IServiceCollection changes at runtime. Instead of treating the container as immutable after startup, it keeps track of the current service collection, rebuilds a fresh provider when needed, and routes later requests through that latest provider.

This is a specialized package. It is most useful for:

  • dynamic feature activation
  • runtime factory expansion
  • test/demo environments that register services on demand
  • multi-tenant or plug-in style setups where registrations are not fully known at startup

Most examples below come from the current source and from the runtime integration tests in src/Core/Test/Rystem.Test.UnitTest/RuntimeServiceProvider/RuntimeServiceProviderTest.cs plus the sample API used by those tests in src/Extensions/Tests/Test/Rystem.Test.TestApi.

Resources

Installation

dotnet add package Rystem.DependencyInjection.Web

The current 10.x package targets net10.0 and references:

  • Microsoft.AspNetCore.App
  • Rystem.DependencyInjection

This package assumes the abstractions from Rystem.DependencyInjection, which in turn builds on the lower-level utility package Rystem.

Package Architecture

The package is intentionally small and is organized around two modules.

Module Purpose
RuntimeServiceProvider Tracks the current IServiceCollection, current built IServiceProvider, request-time swapping, and rebuild operations
Fallback Integrates with IFactory<T> so missing factory names can register new services and rebuild automatically

At a high level, the flow is:

  • call AddRuntimeServiceProvider() during service registration
  • call UseRuntimeServiceProvider() after the app is built
  • mutate the tracked IServiceCollection later
  • call RebuildAsync() to build the next provider
  • future resolutions and future HTTP requests use the newest provider

Table of Contents


Setup

Register runtime rebuilding during startup:

builder.Services.AddRuntimeServiceProvider();

var app = builder.Build();
app.UseRuntimeServiceProvider();

This does two important things:

  • stores the application IServiceCollection so it can be mutated later
  • inserts middleware that replaces HttpContext.RequestServices with a scope created from the latest rebuilt provider

You can also choose not to dispose the previous request service provider when the middleware swaps it out:

app.UseRuntimeServiceProvider(disposeOldServiceProvider: false);

That flag matters only if you need to keep the previous request-scoped provider alive longer than the default handoff behavior.

How Runtime Rebuilding Works

The package keeps global static references to:

  • the tracked IServiceCollection
  • the latest built IServiceProvider
  • the IApplicationBuilder used to patch the host-level Services property

When RebuildAsync() runs, it:

  1. reads the tracked service collection
  2. optionally migrates singleton instances from the old provider into matching singleton descriptors
  3. builds a new provider
  4. swaps the global current provider if that rebuild still represents the newest service count
  5. runs WarmUpAsync() on the rebuilt provider

This design means the package is best suited to application-level dynamic composition, not to isolated per-module container ownership.

Read the Current Collection and Provider

After setup, you can read either the mutable collection or the latest provider.

IServiceCollection services = RuntimeServiceProvider.GetServiceCollection();
IServiceProvider provider = RuntimeServiceProvider.GetServiceProvider();

GetServiceCollection() also clears the internal read-only flag on the collection before returning it, so later Add... calls can keep modifying the same collection instance.

GetServiceProvider() throws until the runtime provider has been initialized through UseRuntimeServiceProvider().


Add Services at Runtime

The simplest runtime update flow is:

await RuntimeServiceProvider
    .GetServiceCollection()
    .AddSingleton<MyNewService>()
    .RebuildAsync();

That pattern comes directly from the test API used by RuntimeServiceProviderTest.

For example, the sample controller checks whether AddedService exists and, if not, registers it and rebuilds immediately:

var value = _serviceProvider.GetService<AddedService>();
if (value == null)
{
    await RuntimeServiceProvider.GetServiceCollection()
         .AddSingleton<AddedService>()
         .RebuildAsync();
}

The integration test confirms the effect:

  • first request does not see AddedService
  • second request does see it
  • existing singleton instances stay the same by default
  • scoped and transient services are recreated as usual

Thread-safe Runtime Registration

If multiple threads may mutate the tracked service collection, use the built-in lock helper:

await RuntimeServiceProvider
    .AddServicesToServiceCollectionWithLock(services =>
    {
        services.AddSingleton(myServiceInstance);
    })
    .RebuildAsync();

Use this helper when registration can happen concurrently, for example from multiple requests or parallel background tasks.

The runtime tests also exercise this pattern by adding many mocked service types in parallel and rebuilding after each change.


RebuildAsync

You can rebuild either from the tracked collection instance or through the static shortcut:

await services.RebuildAsync();
await RuntimeServiceProvider.RebuildAsync();

Every rebuild ends by calling WarmUpAsync() on the latest provider, so warm-up actions registered through Rystem.DependencyInjection still participate in the runtime-rebuild flow.

Preserve existing singleton instances

By default, rebuild keeps the current singleton instance values when matching singleton descriptors still exist in the new collection.

await RuntimeServiceProvider.RebuildAsync(preserveValueForSingletonServices: true);

Or disable that behavior:

await RuntimeServiceProvider.RebuildAsync(preserveValueForSingletonServices: false);

That default preservation is why the runtime test can add AddedService while keeping the original ids for already-created singleton services.

Concurrent rebuild behavior

The implementation tracks the current number of registered services and only promotes the rebuilt provider if that rebuild still represents the largest service collection seen so far.

Practically, this means that when multiple rebuilds race:

  • older/smaller rebuilds do not overwrite a newer/larger one
  • the provider that wins is the one associated with the latest widest collection snapshot

The tests stress this behavior with repeated sequential and parallel rebuilds.


Factory Fallback with Auto-Rebuild

This package becomes especially powerful when combined with IFactory<T> from Rystem.DependencyInjection.

You can register a fallback that reacts when factory.Create(name) is called for an unknown name. Inside that fallback you dynamically add the missing factory registration, rebuild the service provider, and then return the newly available service.

Generic fallback registration

services.AddFactory<Factorized>("1");

services.AddActionAsFallbackWithServiceCollectionRebuilding<Factorized>(async context =>
{
    await Task.Delay(1);

    var singletonService = context.ServiceProvider.GetService<SingletonService>();
    if (singletonService != null)
    {
        context.ServiceColletionBuilder = serviceCollection =>
            serviceCollection.AddFactory<Factorized>(context.Name);
    }
});

That exact pattern is used in src/Extensions/Tests/Test/Rystem.Test.TestApi/Extensions/ServiceExtensions.cs.

After this is registered, the first call to an unknown factory name can materialize the missing registration on demand:

var factory = serviceProvider.GetRequiredService<IFactory<Factorized>>();
var created = factory.Create("dynamic-name");

The runtime tests validate this behavior both for one service and for many services in parallel.

Runtime type fallback registration

There is also a non-generic overload for runtime service types:

RuntimeServiceProvider.AddServicesToServiceCollectionWithLock(sc =>
{
    sc.AddActionAsFallbackWithServiceCollectionRebuilding(serviceType, async context =>
    {
        await Task.Delay(1);
        context.ServiceColletionBuilder = inner => inner.AddFactory(serviceType, context.Name);
    });
});

await RuntimeServiceProvider.RebuildAsync();

This is useful when the service contract itself is discovered dynamically.

FallbackBuilderForServiceCollection

The fallback delegate receives FallbackBuilderForServiceCollection:

Property Type Purpose
Name AnyOf<string, Enum>? The missing factory name that triggered the fallback
ServiceProvider IServiceProvider A fresh scope created from the current runtime provider
ServiceColletionBuilder Action<IServiceCollection> The action that will mutate the tracked service collection before rebuild

Note that the public property name is actually ServiceColletionBuilder in the source, including the typo. The README keeps that spelling because it is the real API surface.

Internally the flow is:

  1. the fallback builds a FallbackBuilderForServiceCollection
  2. your delegate populates ServiceColletionBuilder
  3. the package runs that action under AddServicesToServiceCollectionWithLock(...)
  4. it calls RebuildAsync()
  5. it resolves the requested service again from the rebuilt IFactory<T>

Practical Notes

  • This package uses static global state, so it is designed for one active application container per process.
  • UseRuntimeServiceProvider() relies on reflection to patch internal hosting fields and to unfreeze the service collection.
  • Request pipelines only see the updated provider for future requests; already-running requests continue on the scope they already have.
  • RebuildAsync() returns the current IServiceProvider, so you can chain additional startup logic after a rebuild.
  • The package is much easier to reason about when paired with the lower-level helpers from Rystem.DependencyInjection, especially AddFactory(...), warm-up, and service helper APIs.

Repository Examples

The most useful sources for this package are:

This README is intentionally architecture-first because Rystem.DependencyInjection.Web is not just one extension method. It is a runtime composition model for ASP.NET Core built on top of the base Rystem DI package.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Rystem.DependencyInjection.Web:

Package Downloads
Rystem.Test.XUnit

Rystem is a open-source framework to improve the System namespace in .Net

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.0.7 131 3/26/2026
10.0.6 269,646 3/3/2026
10.0.5 175 2/22/2026
10.0.4 199 2/9/2026
10.0.3 147,962 1/28/2026
10.0.1 209,112 11/12/2025
9.1.3 303 9/2/2025
9.1.2 764,517 5/29/2025
9.1.1 97,798 5/2/2025
9.0.32 186,741 4/15/2025
9.0.31 5,805 4/2/2025
9.0.30 88,847 3/26/2025
9.0.29 9,006 3/18/2025
9.0.28 235 3/17/2025
9.0.27 221 3/16/2025
9.0.26 258 3/13/2025
9.0.25 52,113 3/9/2025
9.0.23 212 3/9/2025
9.0.21 312 3/6/2025
9.0.20 19,563 3/6/2025
Loading failed