在组织依赖关系时,C# 中的依赖关系注入是救命稻草,尤其是在更复杂的 ASP.NET Core 应用程序中。如果你已经熟悉 IServiceCollection,或者只是想尽可能地接近已经提供的 DI 产品,那么 C# 中的 Scrutor 是一个很棒的增强功能。
在本文中,我将简要介绍 C# 中的依赖项注入和 Scitor。但从那里开始,我们将直接进入 3 个技巧,您可以将其与 Scrutor 一起使用,这些技巧可能对您的应用程序非常有帮助!
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 实例。
想象一下,你有一个服务接口和一个实现。您希望记录每个方法调用的入口和出口,而不会因日志记录问题而污染其业务逻辑。IServiceMyServiceMyService
首先,定义接口及其实现:IService
public interface IService
{
void DoWork();
}
public class MyService : IService
{
public void DoWork()
{
// Business logic here
}
}
接下来,创建一个实现 .此类将包装实际服务,并围绕委托方法调用添加日志记录:LoggingDecoratorIService
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 将注册包装为 .解析时,DI 容器将提供 的实例,而 的实例又包装 的实例。此方法通过将日志记录逻辑保留在业务逻辑之外来实现关注点的分离。
当我们构建复杂的系统时,经常会出现一些类似功能标志的东西。这些允许我们根据配置采用不同的代码路径,而不必重新编译和部署整个应用程序。在某些情况下,这不仅仅是选择不同的代码路径,而是交换某些东西的整个实现!
让我们一起尝试一个例子!假设您有一个包含多个实现的接口:(一个新的实验性功能)和(当前的稳定功能)。您希望在运行时根据配置标志在这些实现之间切换。
首先,定义接口及其实现: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
}
采取这样的方法可以帮助我们:
在本文中,我为您提供了 3 个在 C# 中使用 Scrutor 进行依赖注入的简单技巧。在简要概述了什么是依赖注入以及 Scrutor 如何适应它之后,我们直接进入了我们的示例。我们看到了程序集扫描,如何装饰依赖项,甚至如何考虑功能标记整个实现!