问题
I am struggling with a weird case. I have a .NET Core console application that is setup like this:
private static async Task Main(string[] args)
{
var runAsService = !(Debugger.IsAttached || args.Contains("console"));
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddLogging(loggingBuilder => { loggingBuilder.AddConsole(); });
services.AddGatewayServers();
services.AddHostedService<GatewayService>();
});
if (runAsService)
await builder.RunServiceAsync();
else
await builder.RunConsoleAsync();
}
I then have extensions on IServiceCollection
that sets up AddGatewayServers()
like this:
public static void AddGatewayServers(this IServiceCollection services)
{
services.AddTransient<IGatewayServer, Server1>();
services.AddTransient<IGatewayServer, Server2>();
services.AddTransient<Func<ServerType, IGatewayServer>>(provider => key =>
{
switch (key)
{
case ServerType.Type1: return provider.GetService<Server1>();
case ServerType.Type2: return provider.GetService<Server2>();
default: return null;
}
});
}
And then in my class I inject the dependency like this:
private readonly Func<ServerType, IGatewayServer> _gatewayAccessor;
public GatewayServerCollection(Func<ServerType, IGatewayServer> gatewayAccessor)
{
_gatewayAccessor = gatewayAccessor;
}
But when I call the _gatewayAccessor
later on in GatewayServerCollection
to get me an instance of the IGatewayServer
it returns null
. I call it like:
var server = _gatewayAccessor(ServerType.Type1);
What am i missing?
回答1:
Change your registration to the following:
public static void AddGatewayServers(this IServiceCollection services)
{
services.AddTransient<Server1>();
services.AddTransient<Server2>();
services.AddScoped<Func<ServerType, IGatewayServer>>(provider => (key) =>
{
switch (key)
{
case ServerType.Type1: return provider.GetRequiredService<Server1>();
case ServerType.Type2: return provider.GetRequiredService<Server2>();
default: throw new InvalidEnumArgumentException(
typeof(ServerType), (int)key, nameof(key));
}
});
}
The most important change is from this:
services.AddTransient<IGatewayServer, Server1>();
services.AddTransient<IGatewayServer, Server2>();
To this:
services.AddTransient<Server1>();
services.AddTransient<Server2>();
The registrations in MS.DI from a simple dictionary mapping from a service type (IGatewayServer
) to an implementation (Server1
or Server2
respectively). When you request Server1
, it can't find typeof(Server1)
in its dictionary. The solution, therefore, is to register those types by their concrete type.
On top of that, I made use of the GetRequiredService
method:
provider.GetRequiredService<Server1>()
Instead of GetService
:
provider.GetService<Server1>()
GetRequiredService
will throw an exception when a registration does not exist, which allows your code to fail-fast.
I changed the registration of the delegate from Transient
:
services.AddTransient<Func<ServerType, IGatewayServer>>
to Scoped
:
services.AddScoped<Func<ServerType, IGatewayServer>>
This prevents it from being injected into any Singleton
consumer, as MS.DI only prevents Scoped
services to be injected into Singleton
consumers, but does not prevent Transient
instances from being injected into Scoped
or Singleton
consumers (but do make sure validation is enabled). In case you register it as Transient
, the delegate would be injected into Singleton
consumers, but this would eventually fail at runtime when you call GetRequiredService
when the requested service depends on a Scoped
lifestyle, as that would cause Captive Dependencies. Or it could even cause memory leaks, when you resolve Transient
components that implement IDisposable
(yuck!). Registering the delegate as Singleton
, however, would also cause the same issues with Captive Dependencies. So Scoped
is the only sensible option.
Instead of returning null
for an unknown ServerType
:
default:
return null;
I throw an exception, allowing the application to fail fast:
default:
throw new InvalidEnumArgumentException(...);
来源:https://stackoverflow.com/questions/55494896/net-core-di-resolving-keyed-services-using-custom-delegate-returns-null