![]() |
VOOZH | about |
dotnet add package Rystem.DependencyInjection --version 10.0.8
NuGet\Install-Package Rystem.DependencyInjection -Version 10.0.8
<PackageReference Include="Rystem.DependencyInjection" Version="10.0.8" />
<PackageVersion Include="Rystem.DependencyInjection" Version="10.0.8" />Directory.Packages.props
<PackageReference Include="Rystem.DependencyInjection" />Project file
paket add Rystem.DependencyInjection --version 10.0.8
#r "nuget: Rystem.DependencyInjection, 10.0.8"
#:package Rystem.DependencyInjection@10.0.8
#addin nuget:?package=Rystem.DependencyInjection&version=10.0.8Install as a Cake Addin
#tool nuget:?package=Rystem.DependencyInjection&version=10.0.8Install as a Cake Tool
Rystem.DependencyInjection extends Microsoft.Extensions.DependencyInjection with a set of practical modules built around the default .NET container.
The package is not a replacement container. It stays on top of IServiceCollection, IServiceProvider, keyed services, and standard lifetimes, then adds higher-level building blocks for:
Most examples below are based on the current source code and repository tests. Short snippets use small sample types such as MyService, MyOptions, or Order when the exact test models would add too much noise.
dotnet add package Rystem.DependencyInjection
The current 10.x package targets net10.0 and builds on top of:
Microsoft.Extensions.DependencyInjectionMicrosoft.Extensions.DependencyInjection.AbstractionsMicrosoft.Extensions.DependencyModelRystemIf you only need the underlying utility layer, start with Rystem. If you need runtime ASP.NET Core container rebuilding on top of these DI helpers, continue with Rystem.DependencyInjection.Web.
The package is organized in independent modules, each mapped to a folder in the source tree.
| Module | Purpose |
|---|---|
Warmup |
Register actions to run after provider build, each executed inside its own scope |
ExecuteServicesBeforeStart |
Build a temporary provider and execute code immediately during setup |
ServiceHelper |
Add, query, remove, override, and try-add services with runtime ServiceLifetime |
KeyedServiceHelper |
Same idea for keyed services |
AbstractFactory |
Named registrations, per-name options, fallback, and name discovery |
Decorator |
Service decoration for both plain registrations and named factories |
Scanner |
Assembly scanning with explicit interfaces or marker interfaces |
System.Population.Random |
Random object generation and population customization |
ProxyService |
Low-level runtime proxy registration API |
System.Threading.Tasks |
Small helper to align NoContext() behavior with the starting thread |
Conceptually, the most important relationship is this:
Use warm-up actions when some initialization should happen only after the provider is built.
There are two overloads:
AddWarmUp(Action<IServiceProvider>)AddWarmUp(Func<IServiceProvider, Task>)builder.Services.AddWarmUp(serviceProvider =>
{
var foo = serviceProvider.GetRequiredService<Foo>();
var foo2 = serviceProvider.GetRequiredService<Foo2>();
Console.WriteLine(foo.Hello() + foo2.Hello());
});
builder.Services.AddWarmUp(async serviceProvider =>
{
await serviceProvider.GetRequiredService<MyService>().InitAsync();
});
After the app or provider is built, execute all registered warm-up actions:
var app = builder.Build();
await app.Services.WarmUpAsync();
WarmUpAsync() returns the same provider instance, so it can be chained.
Implementation detail that matters in practice: each warm-up action runs inside a fresh scope.
ExecuteUntilNowAsync(...) is useful when you want to build a temporary provider, resolve a service, execute some logic immediately, and dispose the temporary scope.
string result = await services.ExecuteUntilNowAsync((Foo foo) =>
{
return Task.FromResult(foo.Hello());
});
You can also resolve from IServiceProvider directly.
string result = await services.ExecuteUntilNowAsync(serviceProvider =>
{
var foo = serviceProvider.GetRequiredService<Foo>();
var foo2 = serviceProvider.GetRequiredService<Foo2>();
return Task.FromResult(foo.Hello() + foo2.Hello());
});
If you want warm-up to run before the callback, use the dedicated variant:
string result = await services.ExecuteUntilNowWithWarmUpAsync((Foo foo) =>
{
return Task.FromResult(foo.Hello());
});
This is especially handy in:
The service helper extensions wrap the standard AddTransient, AddScoped, and AddSingleton methods behind a runtime ServiceLifetime parameter.
services.AddService<MyService>(ServiceLifetime.Singleton);
services.AddService<IMyService, MyService>(ServiceLifetime.Scoped);
services.AddService(typeof(IMyService), typeof(MyService), ServiceLifetime.Transient);
There are also factory-based overloads.
services.AddService<IMyService>(
serviceProvider => new MyService(serviceProvider.GetRequiredService<IDependency>()),
ServiceLifetime.Transient);
Use these helpers when lifetime is only known at runtime and you do not want to branch manually on ServiceLifetime.
Check whether a non-keyed registration already exists.
bool exists = services.HasService<IMyService>(out ServiceDescriptor? descriptor);
bool exact = services.HasService<IMyService, MyService>(out ServiceDescriptor? exactDescriptor);
This is useful for defensive registration code or package bootstrappers.
Remove every non-keyed registration for a given service type.
services.RemoveService<IMyService>();
services.RemoveService(typeof(IMyService));
Replace an existing non-keyed service registration with a new one.
services.AddOrOverrideService<IMyService, MyServiceV2>(ServiceLifetime.Scoped);
services.AddOrOverrideSingleton<IMyService>(new MyServiceV2());
services.AddOrOverrideSingleton<IMyService, MyServiceV2>(new MyServiceV2());
Use this when your package or module must become the effective registration for a given service type.
Register only if the service type is still absent.
services.TryAddService<IMyService, MyService>(ServiceLifetime.Scoped);
services.TryAddService<MyService>(ServiceLifetime.Singleton);
There are additional overloads for:
TypeThese helpers are useful while you are still configuring IServiceCollection and want to keep one shared singleton instance around.
MySettings settings = services.TryAddSingletonAndGetService(new MySettings
{
Environment = "test"
});
MySettings settings = services.TryAddSingletonAndGetService<MySettings>();
IMySettings typed = services.TryAddSingletonAndGetService<IMySettings, MySettings>(new MySettings());
MySettings? existing = services.GetSingletonService<MySettings>();
This pattern is handy for package-level maps, registries, or shared configuration objects that are built directly during registration. GetSingletonService(...) is most useful for singleton instances that were added directly to IServiceCollection, especially through these same helpers.
The keyed helpers provide the same ergonomics for .NET keyed DI registrations.
They are especially useful when you want one service contract with multiple named or keyed implementations, but you do not need the higher-level abstract factory API.
services.AddKeyedService<MyService>("default", ServiceLifetime.Singleton);
services.AddKeyedService<IMyService, MyService>("west", ServiceLifetime.Scoped);
services.AddKeyedService(typeof(IMyService), "east", typeof(MyService), ServiceLifetime.Transient);
Factory-based keyed registration is also supported.
services.AddKeyedService<IMyService>(
"west",
(serviceProvider, key) => new MyService(key?.ToString() ?? "unknown"),
ServiceLifetime.Transient);
bool exists = services.HasKeyedService<IMyService>("west", out ServiceDescriptor? descriptor);
bool exact = services.HasKeyedService<IMyService, MyService>("west", out _);
bool raw = services.HasKeyedService(typeof(IMyService), "west", out _);
services.RemoveKeyedService<IMyService>("west");
services.RemoveKeyedService(typeof(IMyService), "west");
Replace the registration associated with a specific key.
services.AddOrOverrideKeyedService<MyWorker>("main", ServiceLifetime.Singleton);
services.AddOrOverrideKeyedService(typeof(MyWorker), "backup", ServiceLifetime.Scoped);
services.AddOrOverrideKeyedSingleton<IMySettings>("tenant-a", new MySettings());
services.TryAddKeyedService<IMyService, MyService>("west", ServiceLifetime.Scoped);
services.TryAddKeyedService<MyService>("default", ServiceLifetime.Singleton);
As with the non-keyed helper family, there are also overloads for runtime types and keyed implementation factories.
MySettings settings = services.TryAddKeyedSingletonAndGetService(new MySettings(), "tenant-a");
MySettings autoCreated = services.TryAddKeyedSingletonAndGetService<MySettings>("tenant-b");
MySettings? existing = services.GetSingletonKeyedService<MySettings>("tenant-a");
This is the keyed equivalent of the singleton-get helper from the non-keyed service module and is mainly intended for keyed singleton instances stored directly in the collection.
Use the abstract factory module when you need multiple named registrations of the same service contract, each with its own lifetime and optional per-name options.
This is the most important high-level feature in the package.
Factory names can be either:
stringEnumInternally, names are normalized through AnyOf<string?, Enum>. When you use an enum, its display name is used if a [Display(Name = ...)] attribute exists.
The public contracts are:
public interface IFactory<out TService>
where TService : class
{
TService? Create(AnyOf<string?, Enum>? name = null);
TService? CreateWithoutDecoration(AnyOf<string?, Enum>? name = null);
IEnumerable<TService> CreateAll(AnyOf<string?, Enum>? name = null);
IEnumerable<TService> CreateAllWithoutDecoration(AnyOf<string?, Enum>? name = null);
bool Exists(AnyOf<string?, Enum>? name = null);
}
public interface IServiceForFactory
{
void SetFactoryName(string name);
bool FactoryNameAlreadySetup { get; set; }
}
public interface IServiceWithFactoryWithOptions : IServiceForFactory
{
bool OptionsAlreadySetup { get; set; }
}
public interface IServiceWithFactoryWithOptions<in TOptions> : IServiceWithFactoryWithOptions
where TOptions : class, IFactoryOptions
{
void SetOptions(TOptions options);
}
If a factory-created service implements IServiceForFactory, the factory name is injected automatically during creation.
If it also implements IServiceWithFactoryWithOptions<TOptions>, the per-name options are injected automatically too.
Here is the simplest options-aware registration flow, adapted from the tests.
public interface ITestService
{
string Id { get; }
string FactoryName { get; }
}
public sealed class TestOptions : IFactoryOptions
{
public string ClassicName { get; set; } = string.Empty;
}
public sealed class TestService : ITestService, IServiceWithFactoryWithOptions<TestOptions>
{
public string Id => Options.ClassicName;
public string FactoryName { get; private set; } = string.Empty;
public TestOptions Options { get; private set; } = null!;
public bool FactoryNameAlreadySetup { get; set; }
public bool OptionsAlreadySetup { get; set; }
public void SetFactoryName(string name) => FactoryName = name;
public void SetOptions(TestOptions options) => Options = options;
}
services.AddFactory<ITestService, TestService, TestOptions>(
options =>
{
options.ClassicName = "singleton";
},
name: "singleton",
lifetime: ServiceLifetime.Singleton);
services.AddFactory<ITestService, TestService, TestOptions>(
options =>
{
options.ClassicName = "scoped";
},
name: "scoped",
lifetime: ServiceLifetime.Scoped);
services.AddFactory<ITestService, TestService, TestOptions>(
options =>
{
options.ClassicName = "transient";
},
name: "transient",
lifetime: ServiceLifetime.Transient);
The factory supports both string and enum names.
public enum TestKind
{
Singleton,
Scoped,
Transient
}
services.AddFactory<ITestService, TestService, TestOptions>(
options => options.ClassicName = "enum-singleton",
TestKind.Singleton,
ServiceLifetime.Singleton);
You only need options when the same service type must behave differently per factory name.
If you do not need custom options, the basic overload is enough.
services.AddFactory<ITestService, TestService>("default", ServiceLifetime.Scoped);
There are also overload families for:
Type argumentsIf the configuration object must be built through a builder step, implement IOptionsBuilder<TBuiltOptions>.
public sealed class BuiltOptions : IFactoryOptions
{
public string ServiceName { get; set; } = string.Empty;
}
public sealed class BuiltOptionsBuilder : IOptionsBuilder<BuiltOptions>
{
public string Prefix { get; set; } = string.Empty;
public Func<IServiceProvider, BuiltOptions> Build()
=> _ => new BuiltOptions
{
ServiceName = $"{Prefix}-built"
};
}
public sealed class BuiltService : ITestService, IServiceWithFactoryWithOptions<BuiltOptions>
{
public string Id => Options.ServiceName;
public string FactoryName { get; private set; } = string.Empty;
public BuiltOptions Options { get; private set; } = null!;
public bool FactoryNameAlreadySetup { get; set; }
public bool OptionsAlreadySetup { get; set; }
public void SetFactoryName(string name) => FactoryName = name;
public void SetOptions(BuiltOptions options) => Options = options;
}
services.AddFactory<ITestService, BuiltService, BuiltOptionsBuilder, BuiltOptions>(
options => options.Prefix = "sync",
name: "sync-builder",
lifetime: ServiceLifetime.Transient);
If options must be prepared asynchronously, implement IOptionsBuilderAsync<TBuiltOptions> and use AddFactoryAsync(...).
public sealed class AsyncBuiltOptions : IFactoryOptions
{
public string ServiceName { get; set; } = string.Empty;
}
public sealed class AsyncBuiltOptionsBuilder : IOptionsBuilderAsync<AsyncBuiltOptions>
{
public string Prefix { get; set; } = string.Empty;
public Task<Func<IServiceProvider, AsyncBuiltOptions>> BuildAsync()
{
return Task.FromResult<Func<IServiceProvider, AsyncBuiltOptions>>(_ =>
new AsyncBuiltOptions
{
ServiceName = $"{Prefix}-async"
});
}
}
public sealed class AsyncBuiltService : ITestService, IServiceWithFactoryWithOptions<AsyncBuiltOptions>
{
public string Id => Options.ServiceName;
public string FactoryName { get; private set; } = string.Empty;
public AsyncBuiltOptions Options { get; private set; } = null!;
public bool FactoryNameAlreadySetup { get; set; }
public bool OptionsAlreadySetup { get; set; }
public void SetFactoryName(string name) => FactoryName = name;
public void SetOptions(AsyncBuiltOptions options) => Options = options;
}
await services.AddFactoryAsync<ITestService, AsyncBuiltService, AsyncBuiltOptionsBuilder, AsyncBuiltOptions>(
options => options.Prefix = "async",
name: "async-builder",
lifetime: ServiceLifetime.Scoped);
Resolve the factory from DI and create named services on demand.
using var provider = services.BuildServiceProvider();
using var scope = provider.CreateScope();
var factory = scope.ServiceProvider.GetRequiredService<IFactory<ITestService>>();
var singleton = factory.Create("singleton");
var transient = factory.Create("transient");
var scoped = factory.Create("scoped");
bool exists = factory.Exists("singleton");
bool missing = factory.Exists("missing");
IEnumerable<ITestService> all = factory.CreateAll("singleton");
Lifetime semantics are still the standard DI ones.
var factory = scope.ServiceProvider.GetRequiredService<IFactory<ITestService>>();
string singletonId1 = factory.Create("singleton")!.Id;
string singletonId2 = factory.Create("singleton")!.Id;
string transientId1 = factory.Create("transient")!.Id;
string transientId2 = factory.Create("transient")!.Id;
Console.WriteLine(singletonId1 == singletonId2); // true
Console.WriteLine(transientId1 == transientId2); // false
CreateWithoutDecoration(...) and CreateAllWithoutDecoration(...) are useful when decorators are involved and you explicitly want the undecorated pipeline.
You can check a registration before the provider is built.
bool byString = services.HasFactory<ITestService>("singleton");
bool byEnum = services.HasFactory<ITestService>(TestKind.Singleton);
bool raw = services.HasFactory(typeof(ITestService), "singleton");
IFactoryNames<TService> exposes the list of registered names for a service contract.
var names = provider.GetRequiredService<IFactoryNames<ITestService>>().List();
This is useful for discovery scenarios such as settings pages, admin UIs, or dynamic feature selection.
Fallbacks are invoked when Create(name) is called for a name that has no registration.
public sealed class DefaultService : ITestService
{
public DefaultService(string name)
{
Id = name;
FactoryName = name;
}
public string Id { get; }
public string FactoryName { get; }
}
public sealed class MyFallback : IFactoryFallback<ITestService>
{
public ITestService Create(AnyOf<string?, Enum>? name = null)
=> new DefaultService(name?.AsString() ?? "default");
}
services.AddFactoryFallback<ITestService, MyFallback>();
Or use a delegate that also receives the service provider:
services.AddActionAsFallbackWithServiceProvider<ITestService>(builder =>
{
var resolvedName = builder.Name?.AsString() ?? "default";
return new DefaultService(resolvedName);
});
In addition to AddFactory(...), the package also exposes these families:
TryAddFactory(...)AddOrOverrideFactory(...)AddNewFactory(...)TryAddFactoryAsync(...)AddOrOverrideFactoryAsync(...)AddNewFactoryAsync(...)They exist for the same combinations of:
Use:
TryAddFactory when you want a bool success signal and no replacementAddOrOverrideFactory when the latest registration should winAddNewFactory when you want an explicitly new registration path instead of silently reusing an existing oneThe decorator module replaces a service registration with a decorator and keeps the original service accessible through IDecoratedService<TService>.
The correct interface is:
public interface IDecoratorService<in TService> : IServiceForFactory
where TService : class
{
void SetDecoratedServices(IEnumerable<TService> services);
}
The decorator receives all decorated services as an enumerable so it can support multiple layers.
public interface ITestWithoutFactoryService
{
string Id { get; }
}
public sealed class TestWithoutFactoryService : ITestWithoutFactoryService
{
public string Id { get; } = Guid.NewGuid().ToString();
}
public sealed class TestWithoutFactoryServiceDecorator :
ITestWithoutFactoryService,
IDecoratorService<ITestWithoutFactoryService>
{
public string Id { get; } = Guid.NewGuid().ToString();
public ITestWithoutFactoryService Inner { get; private set; } = null!;
public bool FactoryNameAlreadySetup { get; set; }
public void SetFactoryName(string name) { }
public void SetDecoratedServices(IEnumerable<ITestWithoutFactoryService> services)
=> Inner = services.First();
}
services.AddService<ITestWithoutFactoryService, TestWithoutFactoryService>(ServiceLifetime.Scoped);
services.AddDecoration<ITestWithoutFactoryService, TestWithoutFactoryServiceDecorator>(
name: null,
lifetime: ServiceLifetime.Scoped);
Usage:
var decorated = provider.GetRequiredService<ITestWithoutFactoryService>();
var raw = provider.GetRequiredService<IDecoratedService<ITestWithoutFactoryService>>().Service;
decorated is the decorator. raw is the original pre-decoration service.
Decorators also work with named factory registrations.
services.AddFactory<ITestService, TestService, TestOptions>(
options => options.ClassicName = "special",
name: "special",
lifetime: ServiceLifetime.Scoped);
services.AddDecoration<ITestService, DecoratorTestService>(
name: "special",
lifetime: ServiceLifetime.Scoped);
var factory = provider.GetRequiredService<IFactory<ITestService>>();
var decorated = factory.Create("special");
var raw = factory.CreateWithoutDecoration("special");
This is one of the strongest combinations in the package:
IFactory<TService> chooses the named implementationIDecoratorService<TService> layers cross-cutting behavior on topCreateWithoutDecoration(...) lets you bypass the decorator when neededThe scanning module can register implementations explicitly by interface or implicitly through marker interfaces.
It also tracks previously scanned type pairs to avoid duplicate registrations for the same service and implementation.
public interface IAnything { }
internal sealed class ScanModels : IAnything { }
ScanResult result = services.Scan<IAnything>(
ServiceLifetime.Scoped,
typeof(IAnything).Assembly);
Equivalent non-generic overloads exist too.
If the implementation class declares IScannable<TService>, you can use the untyped scan overload.
public interface IAnything { }
internal sealed class ScanModels : IAnything, IScannable<IAnything>
{
}
services.Scan(ServiceLifetime.Scoped, typeof(IAnything).Assembly);
That is useful when the service-to-implementation relationship should stay close to the implementation itself.
You can override the scan lifetime with one of these marker interfaces:
ISingletonScannableIScopedScannableITransientScannableinternal sealed class ScanModels :
IAnything,
IScannable<IAnything>,
ISingletonScannable
{
}
services.Scan(ServiceLifetime.Scoped, typeof(IAnything).Assembly);
In this example the default scan lifetime is Scoped, but ScanModels is registered as Singleton.
The package includes several helpers to decide where assemblies come from.
services.ScanDependencyContext(ServiceLifetime.Scoped);
services.ScanCallingAssembly(ServiceLifetime.Scoped);
services.ScanCurrentDomain(ServiceLifetime.Scoped);
services.ScanEntryAssembly(ServiceLifetime.Scoped);
services.ScanExecutingAssembly(ServiceLifetime.Scoped);
services.ScanFromType<MyMarker>(ServiceLifetime.Scoped);
services.ScanFromTypes<MyMarker1, MyMarker2>(ServiceLifetime.Scoped);
ScanDependencyContext(...) also supports an optional assembly predicate.
services.ScanDependencyContext(
ServiceLifetime.Scoped,
assembly => assembly.GetName().Name!.StartsWith("MyCompany."));
If you want transitive references too, use ScanWithReferences(...).
services.ScanWithReferences(ServiceLifetime.Scoped, typeof(MyMarker).Assembly);
Every scan method returns a ScanResult.
ScanResult result = services.Scan(ServiceLifetime.Scoped, typeof(MyMarker).Assembly);
Console.WriteLine(result.Count);
foreach (var type in result.Implementations)
{
Console.WriteLine(type.FullName);
}
This is convenient for diagnostics or registration reports.
The population module generates random object graphs and lets you override the generated values with patterns, delegates, implementations, and shared random queues.
It is especially useful for:
Register the service once:
IServiceCollection services = new ServiceCollection();
services.AddPopulationService();
Resolve IPopulation<T> and either populate immediately or create a reusable builder with Setup().
using var provider = services.BuildServiceProvider();
using var scope = provider.CreateScope();
var population = scope.ServiceProvider.GetRequiredService<IPopulation<PopulationModelTest>>();
List<PopulationModelTest> items = population.Populate();
IPopulationBuilder<PopulationModelTest> setup = population.Setup();
From the tests, a more realistic setup looks like this:
var population = scope.ServiceProvider.GetRequiredService<IPopulation<PopulationModelTest>>();
var setup = population
.Setup()
.WithPattern(x => x.J!.First().A, "[a-z]{4,5}")
.WithPattern(x => x.Y!.First().Value.A, "[a-z]{4,5}")
.WithImplementation(x => x.I, typeof(MyInnerInterfaceImplementation))
.WithPattern(x => x.I!.A!, "[a-z]{4,5}")
.WithPattern(x => x.II!.A!, "[a-z]{4,5}")
.WithImplementation<IInnerInterface, MyInnerInterfaceImplementation>(x => x.I!);
List<PopulationModelTest> items = setup.Populate();
Populate(...) accepts:
numberOfElements - how many root objects to createnumberOfElementsWhenEnumerableIsFound - how many elements to generate inside collections by defaultList<PopulationModelTest> items = setup.Populate(50, 4);
If a configuration should apply every time IPopulation<T> is resolved, register it during DI setup with AddPopulationSettings<T>().
services
.AddPopulationSettings<PopulationModelTest>()
.WithAutoIncrement(x => x.A, 1);
That configuration becomes the default for IPopulation<PopulationModelTest>.
The tests also show wrapper models using settings during registration time.
services
.AddPopulationSettings<Entity<PopulationModelTest, int>>()
.WithAutoIncrement(x => x.Value.A, 1)
.WithAutoIncrement(x => x.Key, 1)
.WithPattern(x => x.Value.J!.First().A, "[a-z]{4,5}")
.WithImplementation(x => x.Value.I, typeof(MyInnerInterfaceImplementation));
IPopulationBuilder<T> exposes a rich customization surface.
Generate values matching one or more regex patterns.
population.Setup()
.WithPattern(x => x.Name, "[a-z]{4,10}")
.WithPattern(x => x.Code, "[A-Z]{3}[0-9]{2}");
Useful for deterministic ids across repeated generations.
population.Setup()
.WithAutoIncrement(x => x.Id, 1);
Set values explicitly using a local function or an async service-based resolver.
population.Setup()
.WithValue(x => x.Environment, () => "test");
population.Setup()
.WithValue(x => x.UserName, async serviceProvider =>
{
var source = serviceProvider.GetRequiredService<IUserNameSource>();
return await source.NextAsync();
});
Provide a pool of allowed values and let the population engine choose randomly.
services
.AddPopulationSettings<MyEntity>()
.WithRandomValue(x => x.Groups, async serviceProvider =>
{
return new List<Group>
{
new Group { Id = "2", Name = "admin" },
new Group { Id = "3", Name = "user" }
};
});
There are overloads both for scalar properties and enumerable properties.
Override the default number of generated items for a specific collection property.
population.Setup()
.WithSpecificNumberOfElements(x => x.Tags, 3);
Choose the concrete type used for interface or abstract properties.
population.Setup()
.WithImplementation(x => x.I, typeof(MyInnerInterfaceImplementation))
.WithImplementation<IInnerInterface, MyInnerInterfaceImplementation>(x => x.I!);
Share values through the Rystem random queues.
population.Setup()
.WithRandomValueFromRystem(x => x.CountryCode, useTheSameRandomValuesForTheSameType: true)
.WithRandomValueFromRystemWithSpecificQueue(x => x.RegionCode, "shared-region-queue");
This is useful when related properties across many generated entities should reuse the same random pool instead of being fully independent.
The module is extensible through DI.
services.AddPopulationService();
services.AddPopulationService<MyPopulationService>();
services.AddPopulationService<MyEntity, MyPopulationForEntity>();
services.AddPopulationStrategyService<MyEntity, MyPopulationStrategy>();
services.AddRegexService<MyRegexService>();
services.AddRandomPopulationService<MyCustomRandomPopulationService>();
This lets you override:
TThe package includes a tiny bridge to the core RystemTask.WaitYourStartingThread switch.
services.AddWaitingTheSameThreadThatStartedTheTaskWhenUseNoContext();
This simply sets RystemTask.WaitYourStartingThread = true so NoContext() returns to the starting thread when that behavior is needed.
This is useful in UI-style environments where resuming on the original thread matters.
There is also a low-level runtime proxy API:
(Type proxyInterface, Type proxyImplementation) = services.AddProxy(
interfaceType: typeof(IMyService),
implementationType: typeof(MyService),
interfaceName: "IMyServiceProxy",
className: "MyServiceProxy",
lifetime: ServiceLifetime.Scoped);
AddProxy(...) dynamically emits a new interface and implementation type and registers that proxy interface in DI.
This is an advanced API and is not covered by the repository tests as thoroughly as the modules above, so treat it as a source-level power feature rather than the mainline package entry point.
The best example sources for this package are:
This README is intentionally long because Rystem.DependencyInjection is not a single feature. It is a set of composable DI modules that can be adopted independently or together.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 5 NuGet packages that depend on Rystem.DependencyInjection:
| Package | Downloads |
|---|---|
|
Rystem.OpenAi
.Net wrapper for OpenAI with Dependency injection integration, factory integration: you may inject more than one endpoint, azure integration: you may swap among openai endpoint and any azure endpoint quickly and easily. You can calculate tokens and cost for each request (before the request) and for each response. You can access to the dashboard api to retrieve your current or previous billing. |
|
|
Rystem.RepositoryFramework.Abstractions
Rystem.RepositoryFramework allows you to use correctly concepts like repository pattern, CQRS and DDD. You have interfaces for your domains, auto-generated api, auto-generated HttpClient to simplify connection "api to front-end", a functionality for auto-population in memory of your models, a functionality to simulate exceptions and waiting time from external sources to improve your implementation/business test and load test. |
|
|
Rystem.Api
Rystem.Api helps you to integrate Api Server and Automated Client for Aspect-Oriented programming. |
|
|
Rystem.Content.Abstractions
Rystem.Content helps you to integrate with azure services or to create an abstraction layer among your infrastructure and your business. |
|
|
Rystem.Authentication.Social
Rystem.Authentication.Social helps you to integrate with new .Net Identity system and social logins. |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.8 | 6,629 | 5/13/2026 |
| 10.0.7 | 604 | 3/26/2026 |
| 10.0.6 | 434,235 | 3/3/2026 |
| 10.0.5 | 542 | 2/21/2026 |
| 10.0.4 | 3,121 | 2/9/2026 |
| 10.0.3 | 148,554 | 1/28/2026 |
| 10.0.1 | 210,132 | 11/12/2025 |
| 9.1.3 | 1,092 | 9/2/2025 |
| 9.1.2 | 765,678 | 5/29/2025 |
| 9.1.1 | 103,304 | 5/2/2025 |
| 9.0.32 | 187,634 | 4/15/2025 |
| 9.0.31 | 6,613 | 4/2/2025 |
| 9.0.30 | 89,521 | 3/26/2025 |
| 9.0.29 | 9,839 | 3/18/2025 |
| 9.0.28 | 906 | 3/17/2025 |
| 9.0.27 | 842 | 3/16/2025 |
| 9.0.26 | 1,261 | 3/12/2025 |
| 9.0.25 | 52,821 | 3/9/2025 |
| 9.0.23 | 529 | 3/9/2025 |
| 9.0.21 | 1,394 | 3/6/2025 |