C#中的依赖注入是组织依赖项时的救星,尤其是在更复杂的 ASP.NET Core 应用程序中。如果您已经熟悉 IServiceCollection或者只是想尽可能接近已提供的 DI 产品,那么C# 中的 Scrutor是一个很大的增强。
在本文中,我将为您提供 C# 中的依赖注入和 Scrutor 的高级概述。但从这里开始,我们将直接介绍您可以与 Scrutor 一起使用的 3 个技巧,这可能会对您的应用程序非常有帮助!
Scrutor 是 C# 中的一个功能强大的 NuGet 包,可增强依赖项注入。它简化了依赖项的注册和解析,使您可以更轻松地管理和组织代码。
依赖注入是配置应用程序的一种流行方法,它促进松散耦合并提高可测试性和可维护性。它涉及将依赖项注入到类中,而不是直接在类中创建它们。这意味着创建依赖项的责任不属于需要它的类,而是由调用堆栈上的某个人拥有。最终,这将几乎所有依赖项创建推送到应用程序的入口点,使其变得笨重。然而,依赖注入框架有助于清理和组织所有这些逻辑。
Scrutor 通过提供一种简单直观的方法来注册和解决依赖关系,从而进一步发展了这一概念。使用Scrutor,您不再需要手动一一注册每个依赖项。相反,您可以使用约定和属性来自动化该过程。
为了说明如何在 C# 中使用 Scrutor 进行注册和解析,让我们考虑一个简单的场景。想象一下,我们有一个应用程序需要使用不同的数据存储库,例如 UserRepository 和 ProductRepostiory。
首先,我们需要在项目中安装 Scrutor NuGet 包。打开包管理器控制台并运行以下命令:
Install-Package Scrutor
接下来,我们需要定义我们的存储库及其相应的接口。这个例子本质上是空的,但我们稍后会提到这些:
public interface IUserRepository { // Interface methods } public class UserRepository : IUserRepository { // Implementation } public interface IProductRepository { // Interface methods } public class ProductRepository : IProductRepository { // Implementation }
现在,让我们使用 Scrutor 连接我们的依赖项。在启动代码中(例如 ASP.NET Core 中的ConfigureServices方法),添加以下代码:
services.Scan(scan => scan .FromAssemblyOf<Startup>() .AddClasses(classes => classes.AssignableToAny( typeof(IUserRepository), typeof(IProductRepository))) .AsImplementedInterfaces() .WithScopedLifetime());
此代码使用 Scrutor 中的 Scan 方法来扫描包含 Startup 类的程序集。然后,它筛选出可分配给 IUserRepository 或 IProductRepository 接口的类。最后,它映射这些接口的实现并将它们注册到各自的接口中。
现在,我们可以将这些依赖项注入到我们的类中。例如,假设我们有一个需要 IUserRepository 的 UserService 类:
public class UserService { private readonly IUserRepository _userRepository; public UserService(IUserRepository userRepository) { _userRepository = userRepository; } // Rest of the class implementation }
通过在构造函数中将 IUserRepository 声明为依赖项,Scrutor 和 IServiceCollection 将自动为我们解析并注入 UserRepository 实例。
假设您有一个服务接口IService
和一个实现MyService
。您希望记录MyService
中每个方法调用的进入和退出,而又不会因为日志记录问题而污染其业务逻辑。
首先定义IService
接口及其实现:
public interface IService { void DoWork(); } public class MyService : IService { public void DoWork() { // Business logic here } }
接下来,创建一个实现IService
装饰器类LoggingDecorator
。此类将包装实际服务并在委托方法调用周围添加日志记录:
public class LoggingDecorator : IService { private readonly IService _decorated; private readonly ILogger<LoggingDecorator> _logger; public LoggingDecorator(IService decorated, ILogger<LoggingDecorator> logger) { _decorated = decorated; _logger = logger; } public void DoWork() { _logger.LogInformation("Starting work."); _decorated.DoWork(); _logger.LogInformation("Finished work."); } }
现在,使用 Scrutor 在Startup.cs
或您配置服务的任何位置注册您的服务及其装饰器:
public void ConfigureServices(IServiceCollection services) { // Register the base service services.AddScoped<IService, MyService>(); // Use Scrutor to apply the decorator services.Decorate<IService, LoggingDecorator>(); // Make sure to register the ILogger or ILogger<T> dependencies if not already done }
此设置使用 Scrutor 用LoggingDecorator
包装IService
注册。解析IService
时,DI 容器将提供LoggingDecorator
的实例,该实例又包装MyService
的实例。这种方法通过将日志记录逻辑保留在业务逻辑之外来实现关注点分离。
当我们构建复杂的系统时,经常会出现我们想要引入诸如功能标志之类的东西的情况。这些允许我们根据配置采用不同的代码路径,而不必重新编译和部署整个应用程序。在某些情况下,这不仅仅是选择不同的代码路径,而是交换某些内容的整个实现!
让我们一起尝试一个例子!假设您有一个具有多个实现的IFeatureService
接口: NewFeatureService
(一项新的实验性功能)和StandardFeatureService
(当前的稳定功能)。您希望在运行时根据配置标志在这些实现之间进行切换。
首先,定义IFeatureService
接口及其实现:
public interface IFeatureService { void ExecuteFeature(); } public class NewFeatureService : IFeatureService { public void ExecuteFeature() { // New feature logic } } public class StandardFeatureService : IFeatureService { public void ExecuteFeature() { // Standard feature logic } }
接下来,您需要一种方法来根据功能切换设置确定要使用哪种实现。这可以是appsettings.json
中的值、数据库设置或任何其他配置源。
现在,使用 Scrutor 根据功能切换动态注册适当的服务实现:
public void ConfigureServices(IServiceCollection services) { // Assume GetFeatureToggleValue() retrieves the current feature toggle setting var useNewFeature = GetFeatureToggleValue("UseNewFeature"); // Dynamically register the appropriate implementation based on the feature toggle if (useNewFeature) { services.AddScoped<IFeatureService, NewFeatureService>(); } else { services.AddScoped<IFeatureService, StandardFeatureService>(); } // Optional: You could combine this with the previous example // to use the decorator pattern too! // services.Decorate<IFeatureService, FeatureServiceProxy>(); } private bool GetFeatureToggleValue(string featureName) { // This method would typically check your // configuration source to determine if the feature is enabled // For simplicity, this is just a placeholder return false; // or true based on actual configuration }
采取这样的方法可以帮助我们:
在本文中,我为您提供了在 C# 中使用 Scrutor 进行依赖注入的 3 个简单技巧。在简要概述了什么是依赖注入以及 Scrutor 如何融入其中之后,我们直接进入示例。我们必须了解程序集扫描,如何装饰依赖关系,甚至如何考虑对整个实现进行功能标记!
随着应用程序的复杂性不断增加,依赖注入可以极大地简化应用程序,如果您想坚持使用Microsoft 的内置 IServiceCollection 产品, Scrutor 可以提供巨大帮助。如果您发现这很有用并且正在寻找更多学习机会,请考虑订阅我的免费每周软件工程时事通讯并在 YouTube 上观看我的免费视频!加入我和其他社区成员的 Discord 吧!
也发布在这里。