IoCTools.Generator
1.9.1
dotnet add package IoCTools.Generator --version 1.9.1
NuGet\Install-Package IoCTools.Generator -Version 1.9.1
<PackageReference Include="IoCTools.Generator" Version="1.9.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="IoCTools.Generator" Version="1.9.1" />
<PackageReference Include="IoCTools.Generator"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add IoCTools.Generator --version 1.9.1
#r "nuget: IoCTools.Generator, 1.9.1"
#:package IoCTools.Generator@1.9.1
#addin nuget:?package=IoCTools.Generator&version=1.9.1
#tool nuget:?package=IoCTools.Generator&version=1.9.1
IoCTools
A Roslyn source generator that lets each service declare its own lifetime, dependencies, and registration intent with small attributes. IoCTools emits constructors, service registrations, and analyzers at build time—no reflection, no runtime scanning.
Highlights
- Self-describing services –
[Scoped],[DependsOn<T>],[RegisterAs<…>], and[ConditionalService]live on the class, so intent never leaves the type - Dependency sets – Implement
IDependencySetto reuse dependency bundles across services - Inheritance-aware – Derived services inherit base class lifetime; diagnostics validate across the full chain
- 100+ diagnostics – Build-time validation catches missing lifetimes, circular dependencies, lifetime mismatches, open-generic edge cases, and FluentValidation anti-patterns
- Zero reflection – Everything happens at compile time; generated code is plain C# you can inspect
Authoring Posture
[Inject]is deprecated in 1.6.0 and emitsIOC095. A Roslyn code fix plusioc-tools migrate-injectmigrate in bulk. Scheduled for removal in 2.0.[DependsOn<T>]is the source-of-truth for explicit service dependencies.[DependsOnConfiguration<T>]/[DependsOnOptions<T>]for config dependencies.InjectConfigurationstays supported but is not preferred.- Auto-deps (1.6.0+) declare ambient dependencies once per assembly:
[assembly: AutoDep<T>],[assembly: AutoDepOpen(typeof(ILogger<>))], plusMicrosoft.Extensions.Logging.ILogger<T>is auto-detected with zero configuration. See docs/auto-deps.md.
Installation
dotnet add package IoCTools.Abstractions
dotnet add package IoCTools.Generator
Or directly in your project file:
<ItemGroup>
<PackageReference Include="IoCTools.Abstractions" Version="*" />
<PackageReference Include="IoCTools.Generator" Version="*" PrivateAssets="all" />
</ItemGroup>
What's New in v1.9.0
See CHANGELOG.md for the full history.
Added in 1.9.0
[Cover<T>(ConcreteHandling = ConcreteHandling.ForceMock)]— opt-out for the auto-concrete promotion path. Previously, every concrete-class constructor dependency with an accessible parameterless constructor was silently materialized as a real instance (ParameterRole.ConcreteInstance) with aConfigure{Dep}(Action<T>)helper, which lost depth-2/3 mock coverage when the SUT composed a concrete collaborator from port mocks. The newConcreteHandlingnamed argument (enum:Auto(default, preserves prior behavior),ForceMock) lets a test opt every non-special concrete dependency into the standardMock<T>substitution. Existing fixtures are unchanged.- Constraint:
ForceMockrequiresvirtual(orabstract) public methods on the concrete target type. Moq can only intercept virtual instance methods on classes; concretes with sealed-by-default methods (the C# default) compile cleanly butSetup(...)silently no-ops and the real method body runs against default backing fields. Seedocs/testing.md§ "Concrete Handling Modes" and the XML doc comments onCoverAttribute<>.ConcreteHandlingfor the interface-extraction recommendation.
- Constraint:
Changed in 1.9.0
TestFixturePipelinesyntax predicate tightened — the pipeline now syntactically pre-filters to types whose attribute lists nameCover/CoverAttributebefore invoking the semantic model. IDE responsiveness improvement; no consumer-visible behavior change.TestFixturePipelinenamespace match is now exact — previously aContains("IoCTools.Testing")substring check false-positived on consumer namespaces likeMyCorp.IoCTools.Testing.Extensions.CoverAttribute<T>, emitting a duplicate fixture under the wrong attribute resolution. The check is now an exact comparison againstIoCTools.Testing.Annotations.Cover<T>.Loggernamed argument now parsed by enum-member symbol name rather than raw int value, so future reorderings ofFixtureLoggerProfileenum members cannot silently misclassify. Falls back to the prior integer comparison defensively.
Fixed in 1.9.0
FixtureEmitter.CurrentOptionsProfilemutable-static state removed — Roslyn incremental generators must be pure functions of their inputs; a mutable static property breaks the generator cache and can race when multiple compilations run concurrently in the same generator-host AppDomain. The property was unread outside its own declaration; it has been deleted. No consumer-visible behavior change.
Changed in 1.8.0
[InjectConfiguration]/[DependsOnConfiguration<T>]optional complex-type sections now fall back tonew T()when the section is absent — instead of forwardingnullthrough the null-forgiving operator. Previously the generator emittedconfiguration.GetSection("X").Get<T>()!, which silently assignednullwhen the section was missing and NPE'd on first dereference. The generator now emitsconfiguration.GetSection("X").Get<T>() ?? new T()for complex-type fields whose type has an accessible parameterless constructor. Required sections (Required = true, the default) still throw fast. Interfaces, abstract classes, and types without a parameterless constructor continue to emit the!suppression. Collection bindings and primitiveGetValue<T>bindings are unchanged. Behavior change — consumers who relied on_field == nullto detect an absent optional section will now observe a default-constructed instance instead; this drove the minor-version bump rather than a patch.
Earlier — 1.7.x highlights
AnalysisScopemodel +DiagnosticGate— production-only diagnostics (IOC081/IOC082/IOC086) auto-exempt test projects via theIsTestProjectMSBuild property forwarded fromMicrosoft.NET.Test.Sdk. No naming heuristics.- IOC110 (Warning, configurable) — deterministic multi-impl lifetime diagnostic; replaces the previous non-deterministic single-impl selection that caused IOC012 to fire in Rider but pass silently in CLI.
[RegisterAs<T>]+[RegisterAsAll]compose withIHostedService— concrete registered once at the declared lifetime, companion interfaces bridged viaGetRequiredService<TImpl>(), andIHostedServicebridged to the same instance.- Test fixture generator v2 (
IoCTools.Testing) —FixtureMemberPlannerseparates planning from emission; generated fixtures enable nullable context, emitnewmodifier on derived hidden members, supportGetRequiredSection/binder-style configuration reads, and treatIClockas fixture-provided. - Multitarget CLI —
IoCTools.Tools.Cliships for bothnet9.0andnet10.0.
Getting Started in Three Steps
Annotate a partial service
[DependsOn<ILogger<EmailService>>] public partial class EmailService : IEmailService { public Task SendAsync(string to, string subject, string body) => Task.CompletedTask; }Tip:
[Scoped]is implied for partial classes implementing interfaces. Add[Singleton]/[Transient]only when you want to change that default.Build – IoCTools emits
Add<YourAssembly>RegisteredServices()into<AssemblyName>.Extensions.Generated.Call the extension during startup
using YourAssembly.Extensions.Generated; var builder = WebApplication.CreateBuilder(args); builder.Services.AddYourAssemblyRegisteredServices(builder.Configuration);
Common Open-Generic Pattern
[Scoped]
[RegisterAsAll]
public partial class Repository<T> : IRepository<T> where T : class
{
}
That common open-generic path is supported. If you ask for
InstanceSharing.Shared across open-generic interface aliases, IoCTools
emits the secondary IOC095 descriptor and falls back to valid direct
registrations because Microsoft.Extensions.DependencyInjection does not
support open-generic factory aliases. (In 1.6.0+, the primary IOC095
descriptor is the [Inject] deprecation diagnostic — both ship under the
same ID; see diagnostics.md.)
Platform Support
IoCTools works with .NET Framework 4.6.1+, .NET Core 2.0+, and .NET 5+. The generator targets netstandard2.0 internally, but your service code can use any C# features your framework supports. See platform constraints for details.
Testing with IoCTools
The IoCTools.Testing package auto-generates test fixtures, eliminating mock declaration boilerplate.
using IoCTools.Testing.Annotations;
[Cover<UserService>]
public partial class UserServiceTests
{
[Fact]
public void Test() {
var result = Sut.GetById(1); // Lazy auto-generated SUT
}
}
The CLI includes test scaffold and fixture-aware evidence --test-fixtures
paths for downstream adoption:
dotnet ioc-tools test scaffold --project src/App.csproj --type MyApp.Services.UserService --dry-run
dotnet ioc-tools evidence --project tests/App.Tests.csproj --production-project src/App.csproj --test-fixtures
IoCTools CLI
IoCTools.Tools.Cli ships as a dotnet global/local tool (dotnet ioc-tools …). It interrogates your project with the real IoCTools generator.
Installation
dotnet pack IoCTools.Tools.Cli/IoCTools.Tools.Cli.csproj -c Release -o ./artifacts
dotnet tool install --global --add-source ./artifacts IoCTools.Tools.Cli
Commands
| Command | What it surfaces |
|---|---|
fields --project <csproj> --file <class.cs> [--type ...] [--source] |
Lists generated [DependsOn] fields; outputs constructor source with --source |
services --project <csproj> [--output <dir>] [--source] [--type ...] |
Summarizes registrations (lifetimes, interfaces, factories); outputs source with --source |
explain --project <csproj> --type Namespace.Service |
Explains a single service: dependencies, config bindings, external flags |
graph --project <csproj> [--type ...] [--format json\|puml\|mermaid] |
Emits a service graph in JSON/PlantUML/Mermaid |
why --project <csproj> --type ... --dependency Fully.Qualified.Type |
Shows which generated field matches a dependency |
doctor --project <csproj> [--fixable-only] |
Runs generator and prints diagnostics; --fixable-only filters to warnings/infos |
evidence --project <csproj> [--type ...] [--settings ...] |
Emits one correlated evidence bundle across services, diagnostics, configuration, validators, profile, hints, and fingerprinted artifacts |
config-audit --project <csproj> [--settings appsettings.json] |
Lists required config bindings and reports missing keys |
suppress --project <csproj> [--codes IOC035,IOC092] [--json] |
Generates .editorconfig suppression recipes plus structured rule metadata |
validators --project <csproj> [--filter ...] |
Lists discovered FluentValidation validators |
validator-graph --project <csproj> [--why ValidatorName] [--json] |
Shows validator composition tree or structured lifetime explanation |
Before & After: Replacing DI Smells
Legacy (manual, brittle)
public class LegacyBillingService : IBillingService
{
private readonly ILogger<LegacyBillingService> _logger;
private readonly IHttpClientFactory _httpClients;
private readonly BillingOptions _options;
private readonly IConfiguration _config;
public LegacyBillingService(
ILogger<LegacyBillingService> logger,
IHttpClientFactory httpClients,
IOptionsMonitor<BillingOptions> options,
IConfiguration config)
{
_logger = logger;
_httpClients = httpClients;
_options = options.CurrentValue;
_config = config;
}
}
services.AddHttpClient();
services.Configure<BillingOptions>(configuration.GetSection("Billing"));
services.AddScoped<IBillingService, LegacyBillingService>();
Problems: duplicated registrations, runtime config lookups, no analyzer guardrails.
IoCTools (attributes, analyzers, generated DI)
using IoCTools.Abstractions.Annotations;
[Scoped]
[DependsOn<ILogger<BillingService>, IHttpClientFactory, IClock>]
[DependsOnConfiguration<string>("Billing:BaseUrl", Required = true)]
[DependsOnConfiguration<int>("Billing:RetryCount", DefaultValue = "3")]
public partial class BillingService : IBillingService
{
public async Task ChargeAsync(BillingRequest request)
{
using var client = _httpClientFactory.CreateClient("billing");
// _logger, _httpClientFactory, _clock, _baseUrl, _retryCount all available
}
}
Generated code creates the constructor, binds configuration, and registers everything via builder.Services.AddYourAssemblyRegisteredServices(configuration).
Attribute Reference
Complete attribute reference: docs/attributes.md
Key attributes: [Scoped], [Singleton], [Transient], [DependsOn<T>], [DependsOnConfiguration<T>], [DependsOnOptions<T>], [RegisterAs<T>], [ConditionalService], and the 1.6.0 auto-deps surface ([assembly: AutoDep<T>], [assembly: AutoDepOpen(...)], [AutoDeps<TProfile>], [NoAutoDeps], …)
Diagnostics Reference
IoCTools provides core diagnostics through IOC110, plus testing diagnostics (TDIAG01 through TDIAG08) and FluentValidation diagnostics (IOC100-IOC102). See diagnostics.md for the full list.
Error-Severity Diagnostics
| Rule | Summary |
|---|---|
| IOC001 | Service depends on unimplemented interface |
| IOC002 | Implementation missing lifetime attribute |
| IOC003 | Circular dependency detected |
| IOC004 | [RegisterAsAll] requires lifetime |
| IOC011 | Background service must be partial |
| IOC012 | Singleton depends on Scoped |
| IOC014 | Background service with non-Singleton lifetime |
| IOC015 | Lifetime mismatch in inheritance chain |
| IOC016 | Invalid configuration key |
| IOC018 | [InjectConfiguration] requires partial class |
| IOC021 | [ConditionalService] requires lifetime |
| IOC028 | [RegisterAs] without service indicators |
| IOC029 | [RegisterAs] specifies unimplemented interface |
| IOC031 | [RegisterAs] specifies non-interface type |
| IOC041 | Manual constructor conflicts with IoCTools |
| IOC049 | Dependency set with non-metadata members |
| IOC050 | Dependency set cycle detected |
| IOC051 | Dependency set name collision |
| IOC077 | Manual field shadows generated dependency |
| IOC080 | Code-generating attributes require partial |
| IOC081 | Manual registration duplicates IoCTools |
| IOC082 | Manual registration lifetime differs |
| IOC087 | Transient depends on Scoped |
| IOC088 | Configuration circular reference |
| IOC092 | typeof() registration lifetime mismatch |
| TDIAG04 | [Cover<T>] service has no generated constructor |
| TDIAG05 | Test class with [Cover<T>] must be partial |
| TDIAG07 | Fixture setup helper called after Sut access |
| TDIAG08 | Test class manually constructs a service that could use [Cover<T>] |
View all diagnostics including warnings and info
Configuration
IoCTools reads configuration from MSBuild properties/.editorconfig. Common knobs:
| Property | Purpose | Example |
|---|---|---|
IoCToolsNoImplementationSeverity, IoCToolsManualSeverity, IoCToolsLifetimeValidationSeverity |
Override analyzer severity per category | build_property.IoCToolsNoImplementationSeverity = error |
IoCToolsDisableDiagnostics |
Disable all IoCTools diagnostics | true |
IoCToolsIgnoredTypePatterns |
Patterns for cross-assembly interfaces to ignore | *.Abstractions.*;*.Contracts.*;*.Interfaces.* |
IoCToolsDefaultServiceLifetime |
Sets the implicit lifetime when no explicit attribute | Scoped | Singleton | Transient |
Samples & License
IoCTools.Sampledemonstrates every attribute, diagnostic, and configuration scenario- Licensed under MIT. See
LICENSE
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.9.1 | 68 | 5/23/2026 |
| 1.8.0 | 1,163 | 5/12/2026 |
| 1.7.3 | 1,522 | 5/6/2026 |
| 1.7.2 | 94 | 5/6/2026 |
| 1.7.1 | 88 | 5/6/2026 |
| 1.6.1 | 96 | 4/29/2026 |
| 1.6.0 | 96 | 4/29/2026 |
| 1.5.1 | 138 | 4/12/2026 |
| 1.4.0 | 209 | 3/21/2026 |
| 1.3.0 | 117 | 1/24/2026 |
| 1.2.0 | 405 | 11/18/2025 |
| 1.1.0 | 291 | 11/12/2025 |
| 1.0.0 | 196 | 9/10/2025 |
| 1.0.0-alpha | 229 | 8/28/2025 |
| 0.4.1 | 158 | 11/30/2024 |
| 0.4.0 | 147 | 11/30/2024 |
| 0.3.0 | 223 | 3/18/2024 |
| 0.2.6 | 258 | 2/6/2024 |
| 0.2.5 | 260 | 2/6/2024 |
| 0.2.4 | 265 | 2/6/2024 |
v1.9.0: version-coherence rebuild — no functional changes in this package. The 1.9.0 feature work landed in IoCTools.Testing / IoCTools.Testing.Abstractions ([Cover<T>] ConcreteHandling knob, TestFixturePipeline namespace-exact match, FixtureEmitter static-mutable purge). Carries forward the 1.8.0 [InjectConfiguration] null-fallback behavior change. See CHANGELOG.md.