runtime: Services resolving incorrectly in Development environment but not Production

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

For an IEnumerable<> of services resolved using GetServices, we found that the list of services returned is different when using --environment=Development versus any other values including blank.

We have two service implementations defined. When in Production, service A and B are in the enumerable. In development, service A is in the list twice, without B.

Expected Behavior

Should always resolve service A and B regardless of the enviroment.

Steps To Reproduce

Given types:

public interface IMarker { }
public interface IBaseService<T> { }

public class BaseService : IBaseService<A> { }

public class GenericService<T> : IBaseService<T>
    where T : IMarker
{

}

public class A : IMarker { }

This test passes (I use Shouldly for assertions, sorry):

[Fact]
public void Should_resolve_correctly_directly()
{
    var services = new ServiceCollection();

    services.AddTransient<IBaseService<A>, BaseService>();
    services.AddTransient(typeof(IBaseService<>), typeof(GenericService<>));

    var serviceProvider = services.BuildServiceProvider();
    var handlers = serviceProvider
        .GetServices<IBaseService<A>>()
        .ToList();

    handlers.Count.ShouldBe(2);
    var handlersTypes = handlers
        .Select(h => h.GetType())
        .ToList();
    handlersTypes.ShouldContain(typeof(BaseService));
    handlersTypes.ShouldContain(typeof(GenericService<A>));
}

This test passes using the WebApplication.CreateBuilder method:

[Fact]
public void Should_resolve_correctly_with_no_env_set()
{
    var builder = WebApplication.CreateBuilder();

    builder.Services.AddTransient<IBaseService<A>, BaseService>();
    builder.Services.AddTransient(typeof(IBaseService<>), typeof(GenericService<>));

    var serviceProvider = builder.Services.BuildServiceProvider();
    var handlers = serviceProvider
        .GetServices<IBaseService<A>>()
        .ToList();

    handlers.Count.ShouldBe(2);
    var handlersTypes = handlers
        .Select(h => h.GetType())
        .ToList();
    handlersTypes.ShouldContain(typeof(BaseService));
    handlersTypes.ShouldContain(typeof(GenericService<A>));

    var app = builder.Build();

    var appHandlers = app.Services
        .GetServices<IBaseService<A>>()
        .ToList();

    appHandlers.Count.ShouldBe(2);
    var appHandlersTypes = appHandlers
        .Select(h => h.GetType())
        .ToList();
    appHandlersTypes.ShouldContain(typeof(BaseService));
    appHandlersTypes.ShouldContain(typeof(GenericService<A>));
}

This test fails:

[Fact]
public void Should_resolve_correctly_in_development()
{
    var builder = WebApplication.CreateBuilder(new WebApplicationOptions
    {
        EnvironmentName = Environments.Development
    });

    builder.Services.AddTransient<IBaseService<A>, BaseService>();
    builder.Services.AddTransient(typeof(IBaseService<>), typeof(GenericService<>));

    var serviceProvider = builder.Services.BuildServiceProvider();
    var handlers = serviceProvider
        .GetServices<IBaseService<A>>()
        .ToList();

    handlers.Count.ShouldBe(2);
    var handlersTypes = handlers
        .Select(h => h.GetType())
        .ToList();
    handlersTypes.ShouldContain(typeof(BaseService));
    handlersTypes.ShouldContain(typeof(GenericService<A>));

    var app = builder.Build();

    var appHandlers = app.Services
        .GetServices<IBaseService<A>>()
        .ToList();

    appHandlers.Count.ShouldBe(2);
    var appHandlersTypes = appHandlers
        .Select(h => h.GetType())
        .ToList();
    appHandlersTypes.ShouldContain(typeof(BaseService));
    appHandlersTypes.ShouldContain(typeof(GenericService<A>));
}

The first set of assertions succeeds when I build the service provider directly from builder.Services. Once I call builder.Build and use the app.Services to resolve, it gives me the incorrect set of results. Whether or not I build the service provider does not change the second assertion.

Exceptions (if any)

No response

.NET Version

6.0.101

Anything else?

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 12
  • Comments: 15 (13 by maintainers)

Most upvoted comments

.NET 7.0 is done. This issue is planned for .NET 8.0 now.

Setting EnvironmentName = Environments.Development causes ValidateOnBuild = true to be set on the ServiceContainer. Here’s a reduced repro without web components:

        [Fact]
        public void Should_resolve_correctly_directly()
        {
            var services = new ServiceCollection();

            services.AddTransient<IBaseService<A>, BaseService>();
            services.AddTransient(typeof(IBaseService<>), typeof(GenericService<>));

            var serviceProvider = services.BuildServiceProvider(new ServiceProviderOptions() { ValidateOnBuild = true });
            var handlers = serviceProvider
                .GetServices<IBaseService<A>>()
                .ToList();

            handlers.Count.ShouldBe(2);
            var handlersTypes = handlers
                .Select(h => h.GetType())
                .ToList();
            handlersTypes.ShouldContain(typeof(BaseService));
            handlersTypes.ShouldContain(typeof(GenericService<A>));
        }