依赖注入
MiCake 扩展了 .NET Core 的依赖注入系统,提供了更便捷的服务注册方式,包括自动注册、特性标记等功能。
标准依赖注入
Section titled “标准依赖注入”MiCake 完全兼容 .NET Core 的标准依赖注入:
public class MyModule : MiCakeModule{ public override void ConfigureServices(ModuleConfigServiceContext context) { var services = context.Services;
// 瞬时(Transient)- 每次请求都创建新实例 services.AddTransient<ITransientService, TransientService>();
// 作用域(Scoped)- 每个请求作用域内是同一实例 services.AddScoped<IScopedService, ScopedService>();
// 单例(Singleton)- 整个应用程序生命周期内是同一实例 services.AddSingleton<ISingletonService, SingletonService>();
base.ConfigureServices(context); }}MiCake 提供了多种自动注册服务的方式。
方式 1:实现标记接口
Section titled “方式 1:实现标记接口”实现以下接口的服务会自动注册到 DI 容器:
using MiCake.Core.DependencyInjection;
// 瞬时服务public class EmailService : ITransientService{ public void SendEmail(string to, string subject, string body) { // 发送邮件逻辑 }}
// 作用域服务public class OrderService : IScopedService{ private readonly IRepository<Order, int> _orderRepository;
public OrderService(IRepository<Order, int> orderRepository) { _orderRepository = orderRepository; }
public async Task CreateOrder(CreateOrderDto dto) { // 创建订单逻辑 }}
// 单例服务public class CacheService : ISingletonService{ private readonly Dictionary<string, object> _cache = new();
public void Set(string key, object value) { _cache[key] = value; }
public T Get<T>(string key) { return _cache.TryGetValue(key, out var value) ? (T)value : default; }}无需手动注册,MiCake 会自动扫描并注册这些服务。
方式 2:使用 InjectService 特性
Section titled “方式 2:使用 InjectService 特性”使用 [InjectService] 特性提供更精细的控制:
using MiCake.Core.DependencyInjection;using Microsoft.Extensions.DependencyInjection;
// 基本用法[InjectService(ServiceLifetime.Scoped)]public class ProductService : IProductService{ // ...}
// 指定暴露的服务类型[InjectService(ServiceLifetime.Scoped, ExposeServices = new[] { typeof(IProductService), typeof(IService) })]public class ProductService : IProductService, IService{ // 会注册为 IProductService 和 IService}
// 尝试注册(如果已存在则跳过)[InjectService(ServiceLifetime.Scoped, TryRegister = true)]public class ProductService : IProductService{ // 如果 IProductService 已注册,则跳过}
// 替换已有注册[InjectService(ServiceLifetime.Scoped, ReplaceServices = true)]public class NewProductService : IProductService{ // 替换已注册的 IProductService}自动注册的服务类型
Section titled “自动注册的服务类型”MiCake 会自动将服务注册为以下类型:
public class OrderService : IOrderService, IScopedService{ // 自动注册为: // - OrderService(实现类) // - IOrderService(接口)}
// 使用public class OrderController{ private readonly IOrderService _orderService; // ✅ 可以注入接口 private readonly OrderService _concreteService; // ✅ 也可以注入实现类
public OrderController(IOrderService orderService) { _orderService = orderService; }}Transient(瞬时)
Section titled “Transient(瞬时)”每次请求都创建新实例:
public class GuidService : ITransientService{ public Guid Id { get; } = Guid.NewGuid();}
// 每次注入都是不同的实例public class MyController{ public MyController(GuidService service1, GuidService service2) { Console.WriteLine(service1.Id); // 例如:123e4567-e89b-12d3-a456-426614174000 Console.WriteLine(service2.Id); // 例如:9876-5432-10dc-ba98-765432109876(不同) }}适用场景:
- 轻量级的无状态服务
- 不需要共享状态的服务
Scoped(作用域)
Section titled “Scoped(作用域)”在一个请求作用域内是同一实例:
public class RequestContextService : IScopedService{ public Guid RequestId { get; } = Guid.NewGuid();}
// 同一请求中的所有注入都是同一实例public class MyController{ public MyController(RequestContextService service1, RequestContextService service2) { Console.WriteLine(service1.RequestId); // 例如:123e4567-e89b-12d3-a456-426614174000 Console.WriteLine(service2.RequestId); // 123e4567-e89b-12d3-a456-426614174000(相同) }}适用场景:
- 需要在请求期间共享状态的服务
- 数据库上下文(DbContext)
- 工作单元(Unit of Work)
Singleton(单例)
Section titled “Singleton(单例)”整个应用程序生命周期内是同一实例:
public class ConfigurationService : ISingletonService{ public string AppVersion { get; } = "1.0.0"; public DateTime StartTime { get; } = DateTime.UtcNow;}
// 所有注入都是同一实例// service1.StartTime == service2.StartTime == service3.StartTime适用场景:
- 配置服务
- 缓存服务
- 日志服务
- 无状态的工具类
默认暴露规则
Section titled “默认暴露规则”MiCake 会自动暴露以下类型:
public class OrderService : IOrderService, IService, IScopedService{ // 自动暴露: // 1. 实现类本身: OrderService // 2. 所有公共接口(除了标记接口): IOrderService, IService // 不会暴露: IScopedService(这是标记接口)}使用 [InjectService] 特性自定义暴露的服务类型:
// 只暴露接口[InjectService(ServiceLifetime.Scoped, ExposeServices = new[] { typeof(IOrderService) })]public class OrderService : IOrderService, IService{ // 只注册为 IOrderService}
// 只暴露实现类[InjectService(ServiceLifetime.Scoped, ExposeServices = new[] { typeof(OrderService) })]public class OrderService : IOrderService{ // 只注册为 OrderService}
// 暴露多个类型[InjectService(ServiceLifetime.Scoped, ExposeServices = new[] { typeof(IOrderService), typeof(IService), typeof(OrderService)})]public class OrderService : IOrderService, IService{ // 注册为 IOrderService, IService 和 OrderService}依赖注入最佳实践
Section titled “依赖注入最佳实践”1. 接口优于实现
Section titled “1. 接口优于实现”优先注入接口而不是实现类:
// ✅ 正确:注入接口public class OrderController{ private readonly IOrderService _orderService;
public OrderController(IOrderService orderService) { _orderService = orderService; }}
// ❌ 不推荐:注入实现类public class OrderController{ private readonly OrderService _orderService;
public OrderController(OrderService orderService) { _orderService = orderService; }}2. 避免服务定位器模式
Section titled “2. 避免服务定位器模式”不要注入 IServiceProvider 来获取服务:
// ❌ 错误:服务定位器模式public class OrderService{ private readonly IServiceProvider _serviceProvider;
public OrderService(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; }
public void ProcessOrder() { var productService = _serviceProvider.GetService<IProductService>(); // ... }}
// ✅ 正确:构造函数注入public class OrderService{ private readonly IProductService _productService;
public OrderService(IProductService productService) { _productService = productService; }
public void ProcessOrder() { // 直接使用注入的服务 }}3. 选择正确的生命周期
Section titled “3. 选择正确的生命周期”// ✅ 正确:DbContext 使用 Scopedpublic class MyDbContext : DbContext, IScopedService{ // 每个请求一个实例}
// ✅ 正确:缓存服务使用 Singletonpublic class MemoryCacheService : ISingletonService{ // 全局单一实例}
// ❌ 错误:DbContext 使用 Singletonpublic class MyDbContext : DbContext, ISingletonService{ // 会导致线程安全问题}4. 避免循环依赖
Section titled “4. 避免循环依赖”// ❌ 错误:循环依赖public class ServiceA : IScopedService{ public ServiceA(ServiceB serviceB) { }}
public class ServiceB : IScopedService{ public ServiceB(ServiceA serviceA) { } // 循环依赖!}
// ✅ 正确:提取接口或使用中介者public interface IServiceAProvider{ void DoSomething();}
public class ServiceA : IServiceAProvider, IScopedService{ public void DoSomething() { }}
public class ServiceB : IScopedService{ public ServiceB(IServiceAProvider serviceAProvider) { }}5. 单例服务的线程安全
Section titled “5. 单例服务的线程安全”// ❌ 错误:单例服务不线程安全public class CacheService : ISingletonService{ private Dictionary<string, object> _cache = new();
public void Set(string key, object value) { _cache[key] = value; // 线程不安全 }}
// ✅ 正确:使用线程安全的集合public class CacheService : ISingletonService{ private ConcurrentDictionary<string, object> _cache = new();
public void Set(string key, object value) { _cache[key] = value; // 线程安全 }}手动注册服务
Section titled “手动注册服务”在某些情况下,您可能需要手动注册服务:
public class MyModule : MiCakeModule{ public override void ConfigureServices(ModuleConfigServiceContext context) { var services = context.Services;
// 注册实现 services.AddScoped<IOrderService, OrderService>();
// 注册工厂方法 services.AddScoped<IEmailService>(sp => { var config = sp.GetRequiredService<IConfiguration>(); var smtpServer = config["Email:SmtpServer"]; return new EmailService(smtpServer); });
// 注册已存在的实例 var cacheService = new CacheService(); services.AddSingleton<ICacheService>(cacheService);
// 尝试注册(如果已存在则跳过) services.TryAddScoped<IProductService, ProductService>();
// 替换已有注册 services.Replace(ServiceDescriptor.Scoped<IOrderService, NewOrderService>());
base.ConfigureServices(context); }}通过构造函数注入:
[ApiController][Route("api/[controller]")]public class OrderController : ControllerBase{ private readonly IOrderService _orderService; private readonly ILogger<OrderController> _logger;
public OrderController( IOrderService orderService, ILogger<OrderController> logger) { _orderService = orderService; _logger = logger; }}在模块初始化中
Section titled “在模块初始化中”从 ServiceProvider 获取:
public class MyModule : MiCakeModule{ public override void Initialization(ModuleInitializationContext context) { var serviceProvider = context.ServiceProvider;
// 获取必需的服务(不存在会抛出异常) var dbContext = serviceProvider.GetRequiredService<MyDbContext>();
// 获取可选的服务(不存在返回 null) var cacheService = serviceProvider.GetService<ICacheService>();
base.Initialization(context); }}在非 DI 管理的类中
Section titled “在非 DI 管理的类中”当您需要在非 DI 管理的类中获取服务时:
public class MyHelper{ public static void DoSomething(IServiceProvider serviceProvider) { var orderService = serviceProvider.GetRequiredService<IOrderService>(); // 使用服务 }}TryAdd - 仅在未注册时注册
Section titled “TryAdd - 仅在未注册时注册”services.TryAddScoped<IOrderService, OrderService>();services.TryAddScoped<IOrderService, NewOrderService>(); // 不会生效,因为已注册[InjectService(ServiceLifetime.Scoped, TryRegister = true)]public class OrderService : IOrderService{ // 仅在 IOrderService 未注册时才注册}- 生命周期选择:根据服务的特性选择合适的生命周期
- 避免内存泄漏:不要在长生命周期服务中持有短生命周期服务的引用
- 线程安全:单例服务必须是线程安全的
- 避免循环依赖:设计时注意服务间的依赖关系
- 接口优先:优先通过接口注入服务