【问题标题】:How to allow for optional services with Microsoft.Extension.DependencyInjection?如何使用 Microsoft.Extension.DependencyInjection 允许可选服务?
【发布时间】:2020-04-14 08:42:53
【问题描述】:

我在自己的爱好项目中使用 ASP.NET Core,我想创建一个可供开发人员使用的框架,并且我想允许可选服务并在未注册的情况下使用默认值。

我收到Unable to resolve service for type 'XXX' 错误,但我希望 DI 返回null 而不是抛出异常。 我想允许可选服务,所以如果找到服务,在构造函数中使用它,如果没有找到,将 null 传递给构造函数。

在我的实现中,我有:

public IServiceManager(IService service, ...)
{
    _service = service ?? new DefaultService();
    ...
}

如您所见,如果找不到服务(null),则使用默认值。 也许我误解了 DI 的工作原理。也许我可以使用工厂来代替? 但是,在我的系统中,我在未提供时使用默认服务是很常见的,因此我需要一个不需要 API 使用者注册服务的解决方案。

有没有办法将 ASP.NET Core DI 配置为返回 null 而不是抛出异常?

【问题讨论】:

  • 您是否从IServiceCollection 获得您想要解决的服务?

标签: c# dependency-injection asp.net-core


【解决方案1】:

在构造函数中为该参数添加默认值。

public IServiceManager(IService service = null, ...)
{
  _service = service ?? new DefaultService();
  ...
}

【讨论】:

  • 当你在构造函数中实例化它时,这打败了首先拥有依赖注入/IoC容器的想法
  • 默认值将解决 OP 所写的问题。构造函数的实际实现是完全不相关的。顺便说一句,我并不是在争论在另一种类型的构造函数中实例化一种类型是否是 DI 模式中的一个好习惯。
  • 这是我尝试的第一件事,不是null,而是= new Sevice()。但我收到了错误default parameter must be a compile time constant :(
  • @LukeTO'Brien 与 DI 无关...编译错误清楚地说明了 - anyany 参数的默认值C# 中的方法必须是编译时常量——即null,但不是new Sevice()
  • 不起作用,至少在net core 3.1中,启动时仍然出现di错误
【解决方案2】:

就其本质而言,构造函数注入始终被认为是强制性的。

Microsoft DI 的第一个版本(我不喜欢使用术语 ASP.NET Core DI,因为它不依赖于 ASP.NET Core 并且可以在它之外使用)仅支持带有大多数参数。

认为从那时起,这已被更改为允许多个构造函数,并且 IoC 容器将选择一个合适的构造函数。话虽如此,您可能需要定义多个构造函数。

public IServiceManager(IService service, IOtherService otherService)
{
}

public IServiceManager(IOtherService otherService)
{
}

如果IService 未在 IoC 容器中注册,则应调用第二个构造函数。

但这充其量仍然是一个非常值得怀疑的做法,并且会使您的代码更难维护并保持其不变/松散耦合。

您永远不必在服务中实例化您的类型,即使是可选服务也是如此。

相反,您应该提供允许用户使用自己的实现覆盖它们的注册。

public static IServiceCollection AddMyLibrary(this IServiceCollection services)
{
    services.TryAddTransient<IService, Service>();
    services.TryAddTransient<IOtherService, OtherService>();
}

然后用户覆盖它。

services.AddTransient<IService, CustomService>();
services.AddMyLibrary();

现在CustomService 将被注入到请求IService 的地方。

【讨论】:

  • 感谢您的全面回答!我已经使用多个构造函数作为临时解决方案,但将来我需要重新设计并使用 IServiceCollection 扩展方法。我遇到了一些有趣的 DI 问题,我的应用程序主要是数据库配置驱动,所以我使用了很多从数据库读取的工厂
【解决方案3】:

最简单的方法是在 IoC 容器中为 IService service 注册 DefaultService component 本身 - 我使用的是 Castle Windsor 的术语。大多数容器允许为一个服务注册多个组件。如果您没有为服务注册自定义组件(IService 的另一种实现),则会解析并注入 DefaultService;否则您的自定义组件将被解析为服务,只需按正确顺序注册组件(在 Castle Windsor 中,将考虑首先注册的组件:multiple components for a service

WindsorContainer container = new WindsorContainer();

container.Register(Component.For<IServiceManager>().ImplementedBy<ServiceManager>());
container.Register(Component.For<IService>().ImplementedBy<CustomService>());
container.Register(Component.For<IService>().ImplementedBy<DefaultService>());

IServiceManager serviceManager = container.Resolve<IServiceManager>();
IService service = ((ServiceManager)serviceManager).Service; // service is of type CustomService

关于@Tseng 的以下评论:

当您在构造函数中对其进行实例化时,这击败了首先拥有依赖注入/IoC 容器的想法

并非总是如此...如果您有一个可选依赖项,首先,将其定义为具有公共设置器的属性,以便可以注入组件如果注册。如果没有注册组件(因此容器未设置属性),我认为通过“危险”新关键字实例化默认组件是可以接受的。一切都取决于上下文 - 明确地说,我不会手动实例化服务,但总会有例外。

【讨论】:

  • 首先,MSDI 根本不支持属性或方法注入(而且可能永远不会),因此为此需要像 Autofac 这样的第 3 方控制器。根据 IoC 容器,这可能会或可能导致基础设施泄漏到您的服务中(即,某些容器需要使用 [Inject] 或类似的属性在这些属性上声明或放松封装(具有属性的公共设置器) )。
  • 第二个问题来自new的使用。为了能够newing 默认实现,必须放宽封装才能这样做。如果我们假设注入的服务是 DAL 服务(即存储库,例如 SqlServerRepository),为了在域服务中实例化它,您突然必须从您的域中引用 DAL 项目/程序集。现在,这里的警钟应该响起,你离厄运只有一步之遥,现在与其他层有紧密的耦合。这会导致软件设计不当,麻烦会在以后出现
  • 我不确定 Castle Windsor 是否支持 .NET Core。我以前使用过 Windsor,正在寻找使用哪种解决方案。 AutoFrac 为 .NET Core 提供了很好的文档,所以我可以尝试一下。 docs.autofac.org/en/latest/integration/aspnetcore.html 不过话又说回来,我也想尽量减少依赖,我不确定第三方 IoC 与原生 .NET DI 的优势
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-10-13
  • 2012-06-19
  • 2018-04-10
  • 2021-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多