dotnet / dotnet/runtime

Add IServiceCollectionValidator for optional DI validation at BuildServiceProvider

Open
#119,646 8 comments 0 reactions 0 assignees Claimed by @copilot-swe-agent View on GitHub
api-suggestion area-Extensions-DependencyInjection
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### 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

```csharp

namespace Microsoft.Extensions.DependencyInjection;

public interface IServiceCollectionValidator
{
ValidationResult Validate(IServiceCollection services, object? options);
}

public static class ServiceCollectionValidationExtensions
{
public static IServiceCollection AddValidator(this IServiceCollection services)
where TValidator : IServiceCollectionValidator, new();

public static IServiceCollection AddValidator(this IServiceCollection services, IServiceCollectionValidator validator);
}

public record ValidationResult(IReadOnlyList Errors)
{
bool IsSuccess { get; }
static ValidationResult Success { get; }
static ValidationResult operator +(ValidationResult left, ValidationResult right);
}

```

### API Usage

```csharp
var services = new ServiceCollection();

// Register services
services.AddSingleton();
services.AddSingleton();

// Register validators
services.AddValidator();

// Build the provider — validators run automatically
var provider = services.BuildServiceProvider();

// If validation fails, BuildServiceProvider throws with aggregated errors
```

### Alternative Designs

```csharp
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();
services.AddSingleton();

// Consumer depends on single IService
services.AddSingleton();

// Register validator
services.AddValidator();

// 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()
.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](https://github.com/iatsuta/CommonFramework/blob/main/src/CommonFramework.DependencyInjection.Tests/DuplicateServiceUsageValidatorTests.cs)

### 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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.