Background and motivation
The default .NET DI container (Microsoft.Extensions.DependencyInjection) provides minimal validation during BuildServiceProvider, such as ValidateScopes and ValidateOnBuild. However, it lacks a standardized, extensible mechanism for developers to validate service registrations at build time.
Currently, developers must implement custom validation and invoke it manually after configuring services. This approach is error-prone and easy to forget, leading to runtime errors from misconfigured dependencies, such as:
Duplicate singleton services used in constructors expecting a single instance.
Missing required services for dependent constructors.
Other potential DI misconfigurations.
By introducing a standard IServiceCollectionValidator interface, the container could optionally:
Aggregate all validation errors during BuildServiceProvider.
Support multiple custom validators.
Maintain full backward compatibility: projects that do not use validators remain unaffected.
This would provide early feedback on misconfigurations, reduce runtime errors, and align with the existing philosophy of optional validation in the DI container.
API Proposal
namespace Microsoft.Extensions.DependencyInjection;
public interface IServiceCollectionValidator
{
ValidationResult Validate(IServiceCollection services, object? options);
}
public static class ServiceCollectionValidationExtensions
{
public static IServiceCollection AddValidator<TValidator>(this IServiceCollection services)
where TValidator : IServiceCollectionValidator, new();
public static IServiceCollection AddValidator(this IServiceCollection services, IServiceCollectionValidator validator);
}
public record ValidationResult(IReadOnlyList<string> Errors)
{
bool IsSuccess { get; }
static ValidationResult Success { get; }
static ValidationResult operator +(ValidationResult left, ValidationResult right);
}
API Usage
var services = new ServiceCollection();
// Register services
services.AddSingleton<IService, ServiceImpl>();
services.AddSingleton<Consumer>();
// Register validators
services.AddValidator<DuplicateServiceUsageValidator>();
// Build the provider — validators run automatically
var provider = services.BuildServiceProvider();
// If validation fails, BuildServiceProvider throws with aggregated errors
Alternative Designs
using Xunit;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace CommonFramework.DependencyInjection.Tests
{
public interface IService { }
public class ServiceImpl : IService { }
public class Consumer
{
public Consumer(IService service) { }
}
public class DuplicateServiceUsageValidatorTests
{
[Fact]
public void BuildServiceProvider_ShouldThrow_WhenDuplicateSingletonUsedInConstructor()
{
// Arrange
var services = new ServiceCollection();
// Duplicate singleton
services.AddSingleton<IService, ServiceImpl>();
services.AddSingleton<IService, ServiceImpl>();
// Consumer depends on single IService
services.AddSingleton<Consumer>();
// Register validator
services.AddValidator<DuplicateServiceUsageValidator>();
// Act
Action act = () =>
{
// Instead of manually calling Validate, we simulate
// a "BuildServiceProvider" step that internally calls Validate
services.Validate();
var provider = services.BuildServiceProvider();
};
// Assert
act.Should().Throw<InvalidOperationException>()
.Where(ex => ex.Message.Contains("has been registered many times"));
}
}
}
Currently, developers must manually implement and invoke custom validators after configuring services, which is error-prone and scattered. A reference implementation with unit tests is available here: DuplicateServiceUsageValidatorTests
Risks
Backward compatibility: Fully preserved. Projects that do not register any IServiceCollectionValidator continue to work exactly as before.
Breaking changes: None. No existing APIs are modified.
Performance: Minimal impact. Validators are only invoked if explicitly registered, before BuildServiceProvider.
Error reporting: Any exceptions are limited to scenarios where custom validators detect misconfigurations. They do not affect projects that do not use validators.
This ensures that the proposal introduces optional, safe, and isolated functionality without breaking existing applications.
Background and motivation
The default .NET DI container (Microsoft.Extensions.DependencyInjection) provides minimal validation during BuildServiceProvider, such as ValidateScopes and ValidateOnBuild. However, it lacks a standardized, extensible mechanism for developers to validate service registrations at build time.
Currently, developers must implement custom validation and invoke it manually after configuring services. This approach is error-prone and easy to forget, leading to runtime errors from misconfigured dependencies, such as:
Duplicate singleton services used in constructors expecting a single instance.
Missing required services for dependent constructors.
Other potential DI misconfigurations.
By introducing a standard IServiceCollectionValidator interface, the container could optionally:
Aggregate all validation errors during BuildServiceProvider.
Support multiple custom validators.
Maintain full backward compatibility: projects that do not use validators remain unaffected.
This would provide early feedback on misconfigurations, reduce runtime errors, and align with the existing philosophy of optional validation in the DI container.
API Proposal
API Usage
Alternative Designs
Currently, developers must manually implement and invoke custom validators after configuring services, which is error-prone and scattered. A reference implementation with unit tests is available here: DuplicateServiceUsageValidatorTests
Risks
Backward compatibility: Fully preserved. Projects that do not register any IServiceCollectionValidator continue to work exactly as before.
Breaking changes: None. No existing APIs are modified.
Performance: Minimal impact. Validators are only invoked if explicitly registered, before BuildServiceProvider.
Error reporting: Any exceptions are limited to scenarios where custom validators detect misconfigurations. They do not affect projects that do not use validators.
This ensures that the proposal introduces optional, safe, and isolated functionality without breaking existing applications.