I have a problem in regard to understand asp net core service provider. I register a service type with a non-generic concrete and an open generic registration,
AddTransient(typeof(IBase<Config>), typeof(Base));
AddTransient(typeof(IBase<>), typeof(BaseGeneric<>));
when I try to resolve all services, the behavior is not what I expected.
I create an empty web app with:
dotnet new web
and change the program.cs like following
ServiceCollection sc = new ServiceCollection();
sc.AddTransient(typeof(IBase<Config>), typeof(Base));
sc.AddTransient(typeof(IBase<>), typeof(BaseGeneric<>));
var serviceProvider = sc.BuildServiceProvider();
var services2 = serviceProvider.GetServices(typeof(IBase<Config>));
foreach (var s in services2){
Console.WriteLine(s.GetType());
}
Console.WriteLine();
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTransient(typeof(IBase<Config>), typeof(Base));
builder.Services.AddTransient(typeof(IBase<>), typeof(BaseGeneric<>));
var app = builder.Build();
var services = app.Services.GetServices(typeof(IBase<Config>));
foreach (var s in services){
Console.WriteLine(s.GetType());
}
app.MapGet("/", () => "Hello World!");
app.Run();
public class Config { }
public interface IBase<T> { }
public class BaseGeneric<T> : IBase<T> { }
public class Base : IBase<Config> { }
the output of two foreach is
Base
BaseGeneric`1[Config]
Base
Base
I expect two foreach print same result.but as you can see they return different results. Is this a valid assumption that they should work like each other? if yes so what is the problem here? and another thing that I found is if you change the order of builder.Services registration the result is OK.
CodePudding user response:
It's a bug in "Microsoft.Extensions.DependencyInjection" from version 6.0.0 :
https://github.com/dotnet/runtime/issues/65145
When the option ValidateOnBuild
is enable in the service provider, this produce a weird behavior to retrieve generic dependency.
The correction is planned to .NET 8.
It's reproducing with :
>dotnet new console -f net7.0
>dotnet add package Microsoft.Extensions.DependencyInjection --version 6.0.0
Modify "Program.cs" by :
using Microsoft.Extensions.DependencyInjection;
ServiceCollection sc = new ServiceCollection();
sc.AddTransient(typeof(IBase<Config>), typeof(Base));
sc.AddTransient(typeof(IBase<>), typeof(BaseGeneric<>));
LogServices(sc, false);
LogServices(sc, true);
void LogServices(ServiceCollection sc, bool validate)
{
Console.WriteLine("ValidateOnBuild: " validate);
var serviceProvider = sc.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = validate });
var services = serviceProvider.GetServices(typeof(IBase<Config>));
foreach (var s in services)
{
Console.WriteLine(s.GetType());
}
}
public class Config { }
public interface IBase<T> { }
public class BaseGeneric<T> : IBase<T> { }
public class Base : IBase<Config> { }
This bug is reproduced :
dotnet run
ValidateOnBuild: False
Base
BaseGeneric`1[Config]
ValidateOnBuild: True
Base
Base
If we downgrade the "Microsoft.Extensions.DependencyInjection" version to the precedent :
>dotnet add package Microsoft.Extensions.DependencyInjection --version 5.0.2
>dotnet run
ValidateOnBuild: False
Base
BaseGeneric`1[Config]
ValidateOnBuild: True
Base
BaseGeneric`1[Config]
This is the expected behavior.
In ASP.NET Core, when the environment is "Development", the service builder is build witht the option ValidateOnBuild
and you have this behavior. Test in production and you have the expected behavior.