.NET Core DI 使用自定义委托解析键控服务 returns null
.NET Core DI resolving keyed services using custom delegate returns null
我正在处理一个奇怪的案例。我有一个 .NET Core 控制台应用程序,其设置如下:
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();
}
然后我在 IServiceCollection
上设置了如下扩展 AddGatewayServers()
:
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;
}
});
}
然后在我的 class 中注入这样的依赖项:
private readonly Func<ServerType, IGatewayServer> _gatewayAccessor;
public GatewayServerCollection(Func<ServerType, IGatewayServer> gatewayAccessor)
{
_gatewayAccessor = gatewayAccessor;
}
但是当我稍后在 GatewayServerCollection
中调用 _gatewayAccessor
来获取 IGatewayServer
的实例时,它 returns null
。我这样称呼它:
var server = _gatewayAccessor(ServerType.Type1);
我错过了什么?
将您的注册更改为以下内容:
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));
}
});
}
最重要的变化来自于:
services.AddTransient<IGatewayServer, Server1>();
services.AddTransient<IGatewayServer, Server2>();
为此:
services.AddTransient<Server1>();
services.AddTransient<Server2>();
MS.DI 中的注册来自从服务类型 (IGatewayServer
) 到实现(分别为 Server1
或 Server2
)的简单字典映射。当您请求 Server1
时,它无法在其字典中找到 typeof(Server1)
。因此,解决方案是通过具体类型注册这些类型。
最重要的是,我使用了 GetRequiredService
方法:
provider.GetRequiredService<Server1>()
而不是GetService
:
provider.GetService<Server1>()
GetRequiredService
将在注册不存在时抛出异常,这允许您的代码快速失败。
我把委托的注册改成了Transient
:
services.AddTransient<Func<ServerType, IGatewayServer>>
到Scoped
:
services.AddScoped<Func<ServerType, IGatewayServer>>
这可以防止它被注入任何 Singleton
消费者,因为 MS.DI 只能防止 Scoped
服务被注入 Singleton
消费者,但不会阻止 Transient
实例被注入 Scoped
或 Singleton
消费者(但一定要确保 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。或者它甚至可能导致内存泄漏,当你解析 Transient
组件时实施 IDisposable
(糟糕!)。然而,将委托注册为 Singleton
也会导致与俘虏依赖项相同的问题。因此 Scoped
是唯一明智的选择。
而不是为未知的 ServerType
返回 null
:
default:
return null;
我抛出异常,让应用程序快速失败:
default:
throw new InvalidEnumArgumentException(...);
我正在处理一个奇怪的案例。我有一个 .NET Core 控制台应用程序,其设置如下:
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();
}
然后我在 IServiceCollection
上设置了如下扩展 AddGatewayServers()
:
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;
}
});
}
然后在我的 class 中注入这样的依赖项:
private readonly Func<ServerType, IGatewayServer> _gatewayAccessor;
public GatewayServerCollection(Func<ServerType, IGatewayServer> gatewayAccessor)
{
_gatewayAccessor = gatewayAccessor;
}
但是当我稍后在 GatewayServerCollection
中调用 _gatewayAccessor
来获取 IGatewayServer
的实例时,它 returns null
。我这样称呼它:
var server = _gatewayAccessor(ServerType.Type1);
我错过了什么?
将您的注册更改为以下内容:
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));
}
});
}
最重要的变化来自于:
services.AddTransient<IGatewayServer, Server1>();
services.AddTransient<IGatewayServer, Server2>();
为此:
services.AddTransient<Server1>();
services.AddTransient<Server2>();
MS.DI 中的注册来自从服务类型 (IGatewayServer
) 到实现(分别为 Server1
或 Server2
)的简单字典映射。当您请求 Server1
时,它无法在其字典中找到 typeof(Server1)
。因此,解决方案是通过具体类型注册这些类型。
最重要的是,我使用了 GetRequiredService
方法:
provider.GetRequiredService<Server1>()
而不是GetService
:
provider.GetService<Server1>()
GetRequiredService
将在注册不存在时抛出异常,这允许您的代码快速失败。
我把委托的注册改成了Transient
:
services.AddTransient<Func<ServerType, IGatewayServer>>
到Scoped
:
services.AddScoped<Func<ServerType, IGatewayServer>>
这可以防止它被注入任何 Singleton
消费者,因为 MS.DI 只能防止 Scoped
服务被注入 Singleton
消费者,但不会阻止 Transient
实例被注入 Scoped
或 Singleton
消费者(但一定要确保 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。或者它甚至可能导致内存泄漏,当你解析 Transient
组件时实施 IDisposable
(糟糕!)。然而,将委托注册为 Singleton
也会导致与俘虏依赖项相同的问题。因此 Scoped
是唯一明智的选择。
而不是为未知的 ServerType
返回 null
:
default:
return null;
我抛出异常,让应用程序快速失败:
default:
throw new InvalidEnumArgumentException(...);