RonSijm.Syringe.Fluxor.Lib
1.1.0
Prefix Reserved
dotnet add package RonSijm.Syringe.Fluxor.Lib --version 1.1.0
NuGet\Install-Package RonSijm.Syringe.Fluxor.Lib -Version 1.1.0
<PackageReference Include="RonSijm.Syringe.Fluxor.Lib" Version="1.1.0" />
<PackageVersion Include="RonSijm.Syringe.Fluxor.Lib" Version="1.1.0" />
<PackageReference Include="RonSijm.Syringe.Fluxor.Lib" />
paket add RonSijm.Syringe.Fluxor.Lib --version 1.1.0
#r "nuget: RonSijm.Syringe.Fluxor.Lib, 1.1.0"
#:package RonSijm.Syringe.Fluxor.Lib@1.1.0
#addin nuget:?package=RonSijm.Syringe.Fluxor.Lib&version=1.1.0
#tool nuget:?package=RonSijm.Syringe.Fluxor.Lib&version=1.1.0
RonSijm.Syringe
A powerful dependency injection library for .NET that extends Microsoft's built-in DI container with advanced features like implicit wiring, dynamic service registration, optional dependencies, and extensive extensibility points.
Get it? A Syringe is used to inject things. Yes jokes are better when you have to explain them...
See the architecture diagrams for the package boundaries and runtime registration flow.
Features Overview
- Implicit Wiring - Automatically register all classes in an assembly with a single line of code
- Dynamic Service Registration - Add services at runtime after the container is built
- Optional Dependencies -
Optional<T>wrapper for dependencies that may not be available - Lazy Resolution -
Lazy<T>support for deferred service instantiation - Attribute-Based Registration - Control registration behavior with attributes like
[DontRegister]and[Singleton] - Configuration-Based Registration - Configure service registration via
appsettings.json - Assembly Bootstrapping -
IBootstrapperinterface for library initialization - Extensibility Points - Hook into service resolution and build events
- Property Injection - Populate properties marked with
[Inject], with optionalRequiredandKeysupport - Method Injection Validation - Verify
[FromServices]parameters on Minimal API handlers at build time - Keyed Services - Full support for .NET 8+ keyed services
- Scoped Services - Proper scope management with
CreateScope()
Installation
dotnet add package RonSijm.Syringe
Quick Start
The simplest way to use Syringe is with implicit wiring:
var services = new ServiceCollection();
services.WireImplicit<MyClass>(); // Registers all classes in the assembly containing MyClass
var provider = services.BuildServiceProvider();
Default ServiceCollection Extensions:
<a id='snippet-CodeExample-DefaultServiceCollection-WireByType'></a>
var serviceCollection = new ServiceCollection();
serviceCollection.WireImplicit(typeof(ClassA));
var serviceProvider = serviceCollection.BuildServiceProvider();
<sup><a href='/src/Tests/RonSijm.Syringe.Tests/Features/Tests/MicrosoftServiceProvider/Registration/DefaultServiceCollection/InitiationMethods/TestWireImplicit_Type.cs#L10-L16' title='Snippet source file'>snippet source</a> | <a href='#snippet-CodeExample-DefaultServiceCollection-WireByType' title='Start of snippet'>anchor</a></sup>
<a id='snippet-CodeExample-DefaultServiceCollection-WireByTypeGeneric'></a>
var serviceCollection = new ServiceCollection();
serviceCollection.WireImplicit<ClassA>();
var serviceProvider = serviceCollection.BuildServiceProvider();
<sup><a href='/src/Tests/RonSijm.Syringe.Tests/Features/Tests/MicrosoftServiceProvider/Registration/DefaultServiceCollection/InitiationMethods/TestWireImplicit_GenericType.cs#L10-L16' title='Snippet source file'>snippet source</a> | <a href='#snippet-CodeExample-DefaultServiceCollection-WireByTypeGeneric' title='Start of snippet'>anchor</a></sup>
<a id='snippet-CodeExample-DefaultServiceCollection-WireByAssembly'></a>
var serviceCollection = new ServiceCollection();
var assembly = typeof(ClassA).Assembly;
serviceCollection.WireImplicit(assembly, ServiceLifetime.Transient, []);
var serviceProvider = serviceCollection.BuildServiceProvider();
<sup><a href='/src/Tests/RonSijm.Syringe.Tests/Features/Tests/MicrosoftServiceProvider/Registration/DefaultServiceCollection/InitiationMethods/TestWireImplicit_Assembly.cs#L10-L15' title='Snippet source file'>snippet source</a> | <a href='#snippet-CodeExample-DefaultServiceCollection-WireByAssembly' title='Start of snippet'>anchor</a></sup>
Scoped: (default)
<a id='snippet-CodeExample-DefaultScope'></a>
return typeof(ClassWithGuidA).WireImplicit().BuildServiceProvider();
<sup><a href='/src/Tests/RonSijm.Syringe.Tests/Features/Tests/MicrosoftServiceProvider/Registration/Attributes/LifetimeTests/LifetimeTests_Scoped.cs#L10-L12' title='Snippet source file'>snippet source</a> | <a href='#snippet-CodeExample-DefaultScope' title='Start of snippet'>anchor</a></sup>
Transient:
<a id='snippet-CodeExample-TransientScope'></a>
return typeof(ClassWithGuidA).WireImplicit().WithLifetime(ServiceLifetime.Transient).BuildServiceProvider();
<sup><a href='/src/Tests/RonSijm.Syringe.Tests/Features/Tests/MicrosoftServiceProvider/Registration/Attributes/LifetimeTests/LifetimeTests_Transient.cs#L10-L12' title='Snippet source file'>snippet source</a> | <a href='#snippet-CodeExample-TransientScope' title='Start of snippet'>anchor</a></sup>
Singleton:
<a id='snippet-CodeExample-SingletonScope'></a>
return typeof(ClassWithGuidA).WireImplicit().WithLifetime(ServiceLifetime.Singleton).BuildServiceProvider();
<sup><a href='/src/Tests/RonSijm.Syringe.Tests/Features/Tests/MicrosoftServiceProvider/Registration/Attributes/LifetimeTests/LifetimeTests_Singleton.cs#L10-L12' title='Snippet source file'>snippet source</a> | <a href='#snippet-CodeExample-SingletonScope' title='Start of snippet'>anchor</a></sup>
<a id='snippet-CodeExample-Parameters-registerAsTypesOnly'></a>
var serviceCollection = new ServiceCollection();
serviceCollection.WireImplicit<ClassA>(registerAsTypesOnly: [typeof(ClassWithInterfaceA)]);
var serviceProvider = serviceCollection.BuildServiceProvider();
<sup><a href='/src/Tests/RonSijm.Syringe.Tests/Features/Tests/MicrosoftServiceProvider/Registration/DefaultServiceCollection/Parameters/TestWireImplicit_RegisterAsTypesOnly_ClassWithInterface.cs#L11-L15' title='Snippet source file'>snippet source</a> | <a href='#snippet-CodeExample-Parameters-registerAsTypesOnly' title='Start of snippet'>anchor</a></sup>
<a id='snippet-CodeExample-Parameters-registerBothTypeAndInterfaces'></a>
var serviceCollection = new ServiceCollection();
serviceCollection.WireImplicit<ClassA>(registerBothTypeAndInterfaces: [typeof(ClassWithInterfaceA)]);
var serviceProvider = serviceCollection.BuildServiceProvider();
<sup><a href='/src/Tests/RonSijm.Syringe.Tests/Features/Tests/MicrosoftServiceProvider/Registration/DefaultServiceCollection/Parameters/TestWireImplicit_RegisterBothTypeAndInterfaces_ClassWithInterface.cs#L10-L14' title='Snippet source file'>snippet source</a> | <a href='#snippet-CodeExample-Parameters-registerBothTypeAndInterfaces' title='Start of snippet'>anchor</a></sup>
Extension Methods
By doing a WireImplicit - The extension will return a SyringeServiceCollectionAndRegistration object.
This is an object that keeps track of the last registion that you've made, allowing you to append extra configuration to that specific registration:
<a id='snippet-CodeExample-DontRegisterTypesExtension'></a>
var serviceCollection = new SyringeServiceCollection();
serviceCollection.WireImplicit<ClassA>().DontRegisterTypes(typeof(ClassA));
<sup><a href='/src/Tests/RonSijm.Syringe.Tests/Features/Tests/MicrosoftServiceProvider/CustomCollection/Parameters/TestWireImplicit_DontRegisterAsTypes_Class.cs#L11-L14' title='Snippet source file'>snippet source</a> | <a href='#snippet-CodeExample-DontRegisterTypesExtension' title='Start of snippet'>anchor</a></sup>
<a id='snippet-CodeExample-DontRegisterTypesWithInterfaces'></a>
var serviceCollection = new SyringeServiceCollection();
serviceCollection.WireImplicit<ClassA>().DontRegisterTypesWithInterfaces(typeof(InterfaceFor_ClassWithInterfaceA));
<sup><a href='/src/Tests/RonSijm.Syringe.Tests/Features/Tests/MicrosoftServiceProvider/CustomCollection/Parameters/TestWireImplicit_DontRegisterTypesWithInterface_InterfaceFor_ClassWithInterface.cs#L11-L14' title='Snippet source file'>snippet source</a> | <a href='#snippet-CodeExample-DontRegisterTypesWithInterfaces' title='Start of snippet'>anchor</a></sup>
<a id='snippet-CodeExample-RegisterAsTypesOnly'></a>
var serviceCollection = new SyringeServiceCollection();
serviceCollection.WireImplicit<ClassA>().RegisterAsTypesOnly(typeof(ClassWithInterfaceA));
<sup><a href='/src/Tests/RonSijm.Syringe.Tests/Features/Tests/MicrosoftServiceProvider/CustomCollection/Parameters/TestWireImplicit_RegisterAsTypesOnly_ClassWithInterface.cs#L11-L14' title='Snippet source file'>snippet source</a> | <a href='#snippet-CodeExample-RegisterAsTypesOnly' title='Start of snippet'>anchor</a></sup>
<a id='snippet-CodeExample-RegisterBothTypeAndInterfaces'></a>
var serviceCollection = new SyringeServiceCollection();
serviceCollection.WireImplicit<ClassA>().RegisterBothTypeAndInterfaces(typeof(ClassWithInterfaceA));
<sup><a href='/src/Tests/RonSijm.Syringe.Tests/Features/Tests/MicrosoftServiceProvider/CustomCollection/Parameters/TestWireImplicit_RegisterBothTypeAndInterfaces_ClassWithInterface.cs#L10-L13' title='Snippet source file'>snippet source</a> | <a href='#snippet-CodeExample-RegisterBothTypeAndInterfaces' title='Start of snippet'>anchor</a></sup>
Registration Extensions
A SyringeServiceCollection contains a internal List<IWireSyringeServiceDescriptorExtension> Extensions { get; set; } = []; - which lets you add your own BeforeBuildServiceProvider Extensions
Example
You can put these kind of configurations in your appsettings.json:
Don't register classes with this interface:
Usecase:
Imagine you have an IProcessor (Interface), DatabaseProcessor and APIProcessor that implement this interface - In your appsettings.json you can register or unregister a specific implementation for this interface.
<a id='snippet-CodeExample-ConfigurationNotWithInterfaces'></a>
var appsetting = """
{
"DependencyInjection": {
"Assembly:RonSijm.Syringe.ExamplesC": {
"Type:ExampleCInterface": "None"
}
}
}
""".ToConfiguration();
<sup><a href='/src/Tests/RonSijm.Syringe.Tests/Features/Tests/MicrosoftServiceProvider/Registration/Config/ExamplesC_InterfaceRegistrationNone.cs#L11-L23' title='Snippet source file'>snippet source</a> | <a href='#snippet-CodeExample-ConfigurationNotWithInterfaces' title='Start of snippet'>anchor</a></sup>
Don't register these specific classes:
Usecase:
Imagine you have an IProcessor (Interface), DatabaseProcessor and APIProcessor and UserProcessor - In your appsettings.json you can register or unregister a specific implementations for this interface.
When your consumer takes an IEnumerable<IProcessor> - you can register or unregister specific processors, and only run selected processors.
<a id='snippet-CodeExample-ConfigurationNotSpecificClass'></a>
var appsetting = """
{
"DependencyInjection": {
"Assembly:RonSijm.Syringe.ExamplesC": {
"Type:ExampleC2": "None",
"Type:ExampleC3": "None",
}
}
}
""".ToConfiguration();
<sup><a href='/src/Tests/RonSijm.Syringe.Tests/Features/Tests/MicrosoftServiceProvider/Registration/Config/ExamplesC_TypeRegistrationNone.cs#L11-L24' title='Snippet source file'>snippet source</a> | <a href='#snippet-CodeExample-ConfigurationNotSpecificClass' title='Start of snippet'>anchor</a></sup>
SyringeServiceProvider
The SyringeServiceProvider is an enhanced service provider that wraps Microsoft's DI container and adds powerful features like dynamic service registration at runtime.
Basic Usage
// Create with options
var provider = new SyringeServiceProvider(options =>
{
options.BuildOnConstruct = true; // Build immediately (default)
options.Services.WireImplicit<MyClass>();
});
// Or create from an existing service collection
var services = new ServiceCollection();
services.AddSingleton<IMyService, MyService>();
var provider = new SyringeServiceProvider(services);
Dynamic Service Registration
One of the most powerful features is the ability to add services after the container is built:
var provider = new SyringeServiceProvider();
// Later, dynamically load more services
var newServices = new ServiceCollection();
newServices.AddSingleton<INewService, NewService>();
await provider.LoadServiceDescriptors(newServices);
provider.Build(); // Rebuild to include new services
This is particularly useful for plugin architectures or lazy-loading scenarios.
Optional Dependencies
The Optional<T> wrapper allows you to inject dependencies that may not be registered. This is especially useful in Blazor where injecting null throws an exception.
public class MyComponent
{
private readonly Optional<IOptionalService> _optionalService;
public MyComponent(Optional<IOptionalService> optionalService)
{
_optionalService = optionalService;
}
public void DoWork()
{
if (_optionalService.HasValue)
{
_optionalService.Value.DoSomething();
}
}
}
Optional<T> is automatically registered when using SyringeServiceProvider.
Lazy Resolution
Syringe supports Lazy<T> for deferred service instantiation:
public class MyService
{
private readonly Lazy<IExpensiveService> _expensiveService;
public MyService(Lazy<IExpensiveService> expensiveService)
{
_expensiveService = expensiveService;
}
public void DoWork()
{
// Service is only created when first accessed
_expensiveService.Value.Process();
}
}
Attribute-Based Registration
Control how classes are registered using attributes:
Don't Register Attribute
Prevent a class from being automatically registered:
[Registration.DontRegister]
public class InternalHelper
{
// This class won't be registered by WireImplicit
}
Lifetime Attributes
Specify the service lifetime:
[Lifetime.Singleton]
public class MySingletonService : IMyService
{
// Will be registered as singleton
}
IBootstrapper Interface
The IBootstrapper interface allows libraries to define their own service registrations that are automatically discovered and executed:
public class MyLibraryBootstrapper : IBootstrapper
{
public Task<IEnumerable<ServiceDescriptor>> Bootstrap()
{
var services = new ServiceCollection();
services.AddSingleton<IMyLibraryService, MyLibraryService>();
services.AddScoped<IRepository, Repository>();
return Task.FromResult<IEnumerable<ServiceDescriptor>>(services);
}
}
When an assembly is loaded, Syringe automatically discovers and executes all IBootstrapper implementations.
ILoadAfterExtension
Hook into the assembly loading process with ILoadAfterExtension:
public class MyLoadExtension : BaseLoadAfterExtension
{
public override void AssembliesLoaded(List<Assembly> loadedAssemblies)
{
// Called after assemblies are loaded
Console.WriteLine($"Loaded {loadedAssemblies.Count} assemblies");
}
public override void DescriptorsLoaded(List<ServiceDescriptor> loadedDescriptors)
{
// Called after service descriptors are registered
Console.WriteLine($"Registered {loadedDescriptors.Count} services");
}
}
Extensibility Points
ISyringeAfterBuildExtension
Execute code after the service provider is built:
public class MyAfterBuildExtension : ISyringeAfterBuildExtension
{
private SyringeServiceProvider _provider;
public void SetReference(SyringeServiceProvider serviceProvider)
{
_provider = serviceProvider;
}
public void Process(List<ServiceDescriptor> loadedDescriptors, bool isInitialBuild)
{
// Called after Build() is called
}
}
// Register the extension
var provider = new SyringeServiceProvider(options =>
{
options.WithAfterBuildExtension<MyAfterBuildExtension>();
});
ISyringeServiceProviderAfterServiceExtension
Decorate or modify services after they are resolved:
public class MyAfterServiceExtension : ISyringeServiceProviderAfterServiceExtension
{
public void SetReference(SyringeServiceProvider serviceProvider) { }
public void Decorate(Type serviceType, object service)
{
// Called after every service resolution
}
}
// Register the extension
var provider = new SyringeServiceProvider(options =>
{
options.WithAfterGetService<MyAfterServiceExtension>();
});
Property Injection
The PropertyInjectionAfterServiceExtension populates public writable properties marked with an InjectAttribute after a service is resolved. The attribute is matched by name, so you can define your own:
public class InjectAttribute : Attribute
{
public bool Required { get; set; }
public object Key { get; set; }
}
Then mark the properties you want injected:
public class MyService
{
[Inject]
public IOptionalDependency Optional { get; set; }
[Inject(Required = true)]
public IMandatoryDependency Mandatory { get; set; }
[Inject(Required = true, Key = "primary")]
public IKeyedDependency Primary { get; set; }
}
Enable property injection by adding the extension:
var provider = new SyringeServiceProvider(options =>
{
options.WithAfterGetService<PropertyInjectionAfterServiceExtension>();
});
Required = truethrows anInvalidOperationExceptionwhen the property cannot be resolved.Keyis typed asobject, so strings, enums, ints, or any other type accepted by Microsoft's keyed-service APIs all work.- Both
RequiredandKeyare discovered reflectively, so they remain optional on your ownInjectAttribute.
Validate Property Injection on Build
PropertyInjectionAfterServiceExtension also plugs into ValidateOnBuild. Any registered service with [Inject(Required = true)] properties (including keyed ones) is verified during the build, without instantiating the services:
var provider = new SyringeServiceProvider(options =>
{
options.WithAfterGetService<PropertyInjectionAfterServiceExtension>();
options.ValidateOnBuild = true;
});
If any required property is missing, the build fails with the standard AggregateException("Some services are not able to be constructed", …) containing one InvalidOperationException per missing dependency.
Method Injection Validation
ASP.NET Core Minimal APIs let you inject services straight into static endpoint handlers via [FromServices]:
public static class FootballEndpoints
{
public static async Task<IResult> GetFootballAsync(
int footballId,
[FromServices] IGetFootballService service,
CancellationToken cancellationToken)
{
// ...
}
}
These handlers are not registered as services, so a missing dependency only surfaces at request time. MethodInjectionValidationExtension lets you validate them during ValidateOnBuild:
var extension = new MethodInjectionValidationExtension()
.AddMethod(FootballEndpoints.GetFootballAsync);
var provider = new SyringeServiceProvider(options =>
{
options.WithAfterGetService(extension);
options.ValidateOnBuild = true;
});
For every registered method, each parameter marked with [FromServices] or [FromKeyedServices(key)] is checked against the container. Missing parameters are aggregated into a single InvalidOperationException that is bundled into the standard AggregateException thrown by ValidateOnBuild.
Like InjectAttribute, both attributes are matched by name, so Microsoft's FromKeyedServicesAttribute works out of the box and you can supply your own FromServicesAttribute without referencing ASP.NET Core.
Works with the default Microsoft DI provider
The same validation is also exposed as a ValidateMethodInjection extension method that works on any IServiceProvider — including the stock ServiceProvider returned by IServiceCollection.BuildServiceProvider() — without needing SyringeServiceProvider at all:
var services = new ServiceCollection();
services.AddSingleton<IGetFootballService, GetFootballService>();
var provider = services.BuildServiceProvider();
provider.ValidateMethodInjection(FootballEndpoints.GetFootballAsync);
Internally it queries Microsoft's public IServiceProviderIsService / IServiceProviderIsKeyedService introspection contracts (no reflection into framework internals), and throws the same AggregateException("Some services are not able to be constructed", …) shape that ValidateOnBuild produces.
Validating every registered Minimal API endpoint
You usually don't want to list handlers by hand. Every endpoint registered through MapGet / MapPost / etc. stores its handler's MethodInfo on the endpoint metadata, so you can hand the whole set to ValidateMethodInjection in one go:
var app = builder.Build();
app.MapGet("/Footballs/{FootballId:int}", FootballEndpoints.GetFootballAsync);
// ...other MapXxx calls...
var handlerMethods = app.DataSources
.SelectMany(ds => ds.Endpoints)
.Select(e => e.Metadata.GetMetadata<MethodInfo>())
.Where(m => m != null)
.ToArray();
app.Services.ValidateMethodInjection(handlerMethods);
app.Run();
This catches missing [FromServices] dependencies on application start instead of at the first request that hits the endpoint.
Keyed Services
Syringe fully supports .NET 8+ keyed services:
var provider = new SyringeServiceProvider(services =>
{
services.AddKeyedSingleton<IMyService, ServiceA>("A");
services.AddKeyedSingleton<IMyService, ServiceB>("B");
});
var serviceA = provider.GetKeyedService<IMyService>("A");
var serviceB = provider.GetKeyedService<IMyService>("B");
Scoped Services
Create and manage scopes properly:
var provider = new SyringeServiceProvider();
using (var scope = provider.CreateScope())
{
var scopedService = scope.ServiceProvider.GetService<IMyScopedService>();
// Use scoped service
}
// Scope is disposed, scoped services are cleaned up
Global Settings
Configure default behavior globally:
// Set default service lifetime (default is Scoped)
SyringeGlobalSettings.DefaultServiceLifetime = ServiceLifetime.Transient;
// Control whether types with interfaces are also registered as themselves
SyringeGlobalSettings.RegisterAsTypeWhenTypeHasInterfaces = true;
Fluxor Integration
Syringe includes optional Fluxor integration for state management:
var provider = new SyringeServiceProvider(options =>
{
options.UseFluxor(fluxorOptions =>
{
fluxorOptions.ScanAssemblies(typeof(Program).Assembly);
});
});
Install the Fluxor integration package:
dotnet add package RonSijm.Syringe.Fluxor
Related Projects
- RonSijm.Blazyload - Lazy-loading for Blazor WebAssembly using Syringe
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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. |
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on RonSijm.Syringe.Fluxor.Lib:
| Package | Downloads |
|---|---|
|
RonSijm.Syringe.Fluxor
A Fluxor library for to make it easier to do dependency injection. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.1.0 | 86 | 10/5/2026 |
| 1.0.0 | 207 | 2/11/2026 |
| 1.0.0-RC1.7 | 84 | 2/1/2026 |
| 1.0.0-RC1.6 | 76 | 1/30/2026 |
| 1.0.0-Alpha1.5 | 303 | 4/9/2025 |
| 1.0.0-Alpha1.4 | 310 | 4/7/2025 |
| 1.0.0-Alpha1.3 | 192 | 4/7/2025 |
| 1.0.0-Alpha1.2 | 197 | 4/7/2025 |
| 1.0.0-Alpha1.1 | 193 | 4/6/2025 |
| 1.0.0-Alpha1 | 278 | 3/31/2025 |