如何在 ASP.NET Core 中向 IoC 容器注册接口的多个实现并在运行时检索特定服务
ASP.NET Core 中对依赖注入的内置支持非常棒。 但是,在 ASP.NET Core 中使用依赖注入时处理接口的多个实现有点棘手。 在本文中,我将向您展示如何从 ASP.NET Core 中的此类实现中动态选择服务。
首先,让我们在 Visual Studio 2019 中创建一个 ASP.NET Core Web 应用程序 MVC 项目。假设您的系统中安装了 Visual Studio 2019,请按照下面概述的步骤在 Visual Studio 中创建一个新的 ASP.NET Core MVC 项目。
按照这些步骤应该在 Visual Studio 2019 中创建一个新的 ASP.NET Core MVC 项目。我们将在下面的部分中使用这个项目来说明我们如何在 ASP.NET Core 3.1 中注册一个接口的多个实现。
ASP.NET Core 中内置的 IoC(控制反转)容器可能不像 AutoFac
或 Unity
或其他一些流行的 IoC
容器那样广泛,但在大多数情况下你应该能够使用它来满足你的需求。 但是,内置的 IoC 容器不允许我们注册多个服务,然后在运行时检索特定的服务实例。
有一些 IoC 容器使您能够使用区分这些类型实例的唯一键来注册具体类型。 然而,ASP.NET Core 中内置的 IoC 容器缺乏对此的支持。 因此,注册具有公共接口的服务并在运行时解析它们并不简单。 让我们用一个例子来理解这一点。
假设您有一个名为 ICustomLogger
的接口,其代码如下:
public interface ICustomLogger
{
public bool Write(string data);
}
ICustomLogger
接口由以下三个类实现:
public class FileLogger : ICustomLogger
{
public bool Write(string data)
{
throw new System.NotImplementedException();
}
}
public class DbLogger : ICustomLogger
{
public bool Write(string data)
{
throw new System.NotImplementedException();
}
}
public class EventLogger : ICustomLogger
{
public bool Write(string data)
{
throw new System.NotImplementedException();
}
}
FileLogger
类用于将数据记录到文件中,DbLogger
类用于将数据记录到数据库中,EventLogger
类用于将数据记录到事件日志中,以便可以使用事件查看器工具查看日志。 请注意,由于记录数据不是本文的目标,因此与这些类中的每一个相关的 Write
方法不包含任何实现。
现在假设您已经在 Startup
类的 ConfigureServices
方法中将这些类的实例添加为作用域服务,如下所示:
services.AddScoped<ICustomLogger, FileLogger>();
services.AddScoped<ICustomLogger, DbLogger>();
services.AddScoped<ICustomLogger, EventLogger>();
以下代码片段说明了如何在 HomeController
类的构造函数中利用依赖注入来使用这些服务。
public class HomeController : Controller
{
public HomeController(ICustomLogger fileLogger, ICustomLogger dbLogger, ICustomLogger eventLogger)
{
var obj = fileLogger; //This code has been written for debugging
}
//Action methods go here
}
When you run this application and the breakpoint is hit, you’ll observe that all three parameters are instances of type EventLogger because EventLogger has been injected last. This is shown in Figure 1 below.
当您运行此应用程序并命中断点时,您会发现所有三个参数都是 EventLogger
类型的实例,因为最后注入了 EventLogger
。 这在下面的图 1 中显示。
我们如何克服 ASP.NET Core 中内置 IoC 容器的这种限制? 在下面概述的解决方案中,我们将使用 IEnumerable
服务集合来注册服务,并使用委托来检索特定服务实例。
下图还将包含示例代码,用于在控制器类的构造函数中使用依赖注入,解析服务实例,并根据运行时选择的服务类型返回三个类(FileLogger
、DbLogger
和 EventLogger
)的实例。
IEnumerable
服务实例集合在 ConfigureServices
方法中编写以下代码以添加每个 ICustomLogger
实现的作用域服务。
services.AddScoped<ICustomLogger, FileLogger>();
services.AddScoped<ICustomLogger, DbLogger>();
services.AddScoped<ICustomLogger, EventLogger>();
接下来,在控制器类的构造函数中利用依赖项注入和 ICustomLogger
实现的 IEnumerable
集合,如下面给出的代码片段所示。
public HomeController(IEnumerable<ICustomLogger> loggers)
{
foreach(var logger in loggers)
{
var objType = logger.GetType();
}
}
请考虑以下枚举,其中包含对应于三种类型 FileLogger
、DbLogger
和 EventLogger
的整数常量。
public enum ServiceType
{
FileLogger,
DbLogger,
EventLogger
}
接下来,声明一个共享委托,如下面的代码片段所示。
public delegate ICustomLogger ServiceResolver(ServiceType serviceType);
现在将以下范围内的服务添加到服务集合实例中,如下一个代码片段所示。
services.AddScoped<FileLogger>();
services.AddScoped<DbLogger>();
services.AddScoped<EventLogger>();
以下代码片段显示了如何根据运行时选择的服务类型返回 FileLogger
、DbLogger
和 EventLogger
类的实例。
services.AddTransient<ServiceResolver>(serviceProvider => serviceTypeName =>
{
switch (serviceTypeName)
{
case ServiceType.FileLogger:
return serviceProvider.GetService<FileLogger>();
case ServiceType.DbLogger:
return serviceProvider.GetService<DbLogger>();
case ServiceType.EventLogger:
return serviceProvider.GetService<EventLogger>();
default:
return null;
}
});
最后,这里介绍了如何在控制器类的构造函数中使用依赖注入,然后解析服务实例。
public HomeController(Func<ServiceType, ICustomLogger> serviceResolver)
{
var service = serviceResolver(ServiceType.FileLogger);
}
您也可以在运行时使用反射来解析类型,但这不是推荐的解决方案。 另一种可能的解决方案是在接口上使用泛型类型参数。