依赖注入
HMX平台基于ASP.NET Core的依赖注入(DI)系统,扩展了自动注册和模块化服务能力。本文档介绍项目中的依赖注入设计和使用方式。
依赖注入架构
整体流程
Program.Main()
│
├─ builder.InitializeModules<T>() // 初始化模块系统
│ │
│ ├─ 注册框架基础服务
│ ├─ 创建引擎(HmxEngine)
│ ├─ 模块 PreInitialize // 各模块注册服务
│ └─ AutoRegister // 自动注册标记接口服务
│
├─ builder.Services.AddXxx() // 手动注册其他服务
│
└─ app.PostInitHiMindApplication() // 完成模块初始化
│
├─ 模块 Initialize
└─ 模块 PostInitialize注册方式
项目支持三种服务注册方式:
| 方式 | 说明 | 适用场景 |
|---|---|---|
| 模块注册 | 在模块 PreInitialize 中手动注册 | 模块特定的服务 |
| 标记接口 | 实现 ISingletonDependency 等接口 | 通用服务,自动发现 |
| 手动注册 | 在 Program.cs 中直接注册 | 框架级别服务 |
标记接口自动注册
接口定义
| 接口 | 命名空间 | 生命周期 | 说明 |
|---|---|---|---|
ISingletonDependency | Hmx.Core.DependencyInjection | Singleton | 单例,全局唯一 |
IScopedDependency | Hmx.Core.DependencyInjection | Scoped | 作用域,每个请求一个实例 |
ITransientDependency | Hmx.Core.DependencyInjection | Transient | 瞬时,每次创建新实例 |
使用示例
csharp
using Hmx.Core.DependencyInjection;
namespace YourProject.Services
{
// 接口定义标记接口
public interface ICacheService : ISingletonDependency
{
T Get<T>(string key);
void Set<T>(string key, T value);
}
// 实现类自动注册
public class RedisCacheService : ICacheService
{
public T Get<T>(string key) { /* ... */ }
public void Set<T>(string key, T value) { /* ... */ }
}
// 作用域服务示例
public interface IOrderRepository : IScopedDependency
{
Task<Order> GetByIdAsync(string id);
}
// 瞬时服务示例
public interface IEmailSender : ITransientDependency
{
Task SendAsync(string to, string message);
}
}自动注册原理
csharp
// 框架内部实现(DependencyInjectionExtensions.AutoRegister)
public static void AutoRegister(this IServiceCollection services)
{
var typeFinder = services.GetSingletonInstanceOrNull<ITypeFinder>();
// 扫描所有实现 ISingletonDependency 的类
var singletonTypes = typeFinder.FindClassesOfType<ISingletonDependency>();
foreach (var type in singletonTypes)
{
// 注册该类实现的所有接口(排除标记接口本身)
foreach (var interfaceType in type.GetInterfaces())
{
if (interfaceType != typeof(ISingletonDependency))
{
services.TryAddSingleton(interfaceType, type);
}
}
}
// 同理处理 IScopedDependency 和 ITransientDependency
}注意:使用
TryAdd方式注册,如果接口已有注册则不会覆盖。
模块内手动注册
在模块的 PreInitialize 方法中可以手动注册服务:
csharp
public class YourModule : HiMindModule
{
public override Task PreInitialize(ApplicationPreInitializeContext context)
{
var services = context.Services;
// 注册具体实现
services.AddScoped<IOrderService, OrderService>();
// 注册工厂模式
services.AddSingleton<IDbConnection>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
var connStr = config.GetConnectionString("Default");
return new SqlConnection(connStr);
});
// 注册配置绑定
services.Configure<YourOptions>(
context.Configuration.GetSection("YourOptions"));
return base.PreInitialize(context);
}
}注册顺序
- 框架基础服务(缓存、对象映射、HttpContext等)
- Redis/消息队列等中间件服务
- 各模块
PreInitialize注册 AutoRegister自动注册Program.cs中手动注册
服务解析方式
AppService 与普通服务的区别
系统中有两种类型的服务,它们的依赖注入方式不同:
| 类型 | 特征 | 注入方式 | 说明 |
|---|---|---|---|
| AppService | 实现 IHmxAppService 接口 | EngineContext.Current.Resolve | 被动态WebAPI机制当作Controller |
| 普通服务 | 不实现 IHmxAppService | 构造函数注入 | 正常的DI服务 |
为什么 AppService 不能用构造函数注入?
IHmxAppService 的实现类会被 HmxDynamicWebApiControllerFeatureProvider 识别为ASP.NET Core的Controller:
csharp
// 框架内部:将 IHmxAppService 实现类标记为Controller
protected override bool IsController(TypeInfo typeInfo)
{
if (typeof(IHmxAppService).IsAssignableFrom(typeInfo)
&& typeInfo is { IsClass: true, IsAbstract: false, IsPublic: true })
{
return true; // 视为Controller
}
return base.IsController(typeInfo);
}由于基类 HmxAppServiceBase<T> 使用 EngineContext.Current.Resolve<T>() 获取依赖(Logger、Cache等),而不是通过构造函数参数,因此AppService类不适合使用构造函数注入。
AppService 服务解析
csharp
// ✅ AppService 的正确写法:使用 EngineContext.Current.Resolve
public class AdminAppService : HmxAppServiceBase<AdminAppService>, IAdminAppService
{
// 直接使用基类提供的属性(内部通过 EngineContext 解析)
protected virtual ILogger<T> Logger { get; }
protected virtual IHmxDistributedCache DistributedCache { get; }
protected virtual IHmxCache MemoryCache { get; }
public async Task<List<HmxUser>> GetUsersAsync(string keywords)
{
// 使用 Logger、Cache 等
Logger.LogInformation("查询用户列表");
// 获取数据库连接
var db = GetDbContext();
return await db.GetTable<HmxUser>().ToListAsync();
}
}csharp
// ❌ 错误:AppService 不要使用构造函数注入
public class AdminAppService : HmxAppServiceBase<AdminAppService>, IAdminAppService
{
private readonly IMyService _myService;
public AdminAppService(IMyService myService) // ❌ 不推荐
{
_myService = myService;
}
}普通服务解析
csharp
// ✅ 普通服务:使用构造函数注入(推荐)
public class AiChatService
{
private readonly ILogger<AiChatService> _log;
private readonly MesDatabaseService _db;
public AiChatService(
ILogger<AiChatService> log,
MesDatabaseService db) // ✅ 构造函数注入
{
_log = log;
_db = db;
}
}通过 EngineContext 解析服务
csharp
// 在任何地方解析服务(适用于静态方法、过滤器等场景)
var cache = EngineContext.Current.Resolve<IHmxCache>();
var logger = EngineContext.Current.Resolve<ILogger<MyClass>>();框架内置服务
以下是框架自动注册的核心服务:
| 服务 | 接口 | 生命周期 | 说明 |
|---|---|---|---|
| 缓存 | IHmxCache | Singleton | 本地内存缓存 |
| 分布式缓存 | IHmxDistributedCache | Singleton | Redis分布式缓存 |
| 对象映射 | IObjectMapper | Singleton | 对象转换 |
| 类型查找 | ITypeFinder | Singleton | 程序集扫描 |
| 配置 | IConfiguration | Singleton | 配置访问 |
| 模块管理 | IHiMindModuleManager | Singleton | 模块生命周期 |
| HTTP上下文 | IHttpContextAccessor | Scoped | HTTP上下文访问 |
| 数据门户 | IDataPortalClient | - | 远程服务调用 |
最佳实践
1. 根据服务类型选择注入方式
| 服务类型 | 推荐方式 | 原因 |
|---|---|---|
AppService(实现 IHmxAppService) | EngineContext.Current.Resolve | 动态WebAPI机制决定 |
普通服务(不实现 IHmxAppService) | 构造函数注入 | 标准DI实践 |
2. 合理选择生命周期
| 场景 | 推荐生命周期 | 原因 |
|---|---|---|
| 无状态服务 | Singleton | 性能最优 |
| 有状态/数据库操作 | Scoped | 避免线程安全问题 |
| 轻量级工具 | Transient | 按需创建 |
3. 避免循环依赖
csharp
// ❌ 错误:循环依赖
public interface IServiceA : ISingletonDependency
{
void DoWork(IServiceB b);
}
public interface IServiceB : ISingletonDependency
{
void DoWork(IServiceA a);
}
// ✅ 正确:通过中介或重构解决
public interface IMessageBus : ISingletonDependency
{
void Publish<T>(T message);
}4. 接口与实现分离
csharp
// ✅ 推荐:接口和实现分开定义
public interface ICacheService : ISingletonDependency
{
T Get<T>(string key);
}
public class RedisCacheService : ICacheService
{
// 实现...
}
// ❌ 避免:直接注册实现类
services.AddSingleton<RedisCacheService>();常见问题
AppService 中能否使用构造函数注入?
答:可以使用,但不推荐。
AppService 类(实现 IHmxAppService 的类)被框架的动态WebAPI机制识别为ASP.NET Core的Controller,技术上支持构造函数注入:
csharp
// ✅ 技术上可行
public class AdminAppService : HmxAppServiceBase<AdminAppService>, IAdminAppService
{
private readonly IMyService _myService;
// 可以添加构造函数注入
public AdminAppService(IMyService myService)
{
_myService = myService;
}
}但不推荐的原因:
| 原因 | 说明 |
|---|---|
| 模式混用 | 基类用EngineContext,子类用构造函数注入,风格不统一 |
| 依赖不透明 | Logger、Cache等基类依赖隐藏在属性中,新依赖通过构造函数注入,维护时容易混淆 |
| 现有代码惯例 | 项目中的AppService类都没有使用构造函数注入 |
推荐写法:
csharp
// ✅ 推荐:使用 EngineContext.Current.Resolve
public class AdminAppService : HmxAppServiceBase<AdminAppService>, IAdminAppService
{
private IMyService GetMyService()
{
return EngineContext.Current.Resolve<IMyService>(); // ✅ 与基类风格一致
}
}服务无法解析
症状:运行时抛出 InvalidOperationException: No service for type
排查:
- 确认服务是否已注册(标记接口或手动注册)
- 检查生命周期是否匹配(Scoped服务不能在Singleton中注入)
- 查看模块是否正确加载
服务生命周期错误
症状:Cannot consume scoped service from singleton
原因:在Singleton服务中注入了Scoped服务
解决:
csharp
// ❌ 错误
public class SingletonService
{
public SingletonService(IScopedService scoped) { } // 错误!
}
// ✅ 正确:使用 IServiceScopeFactory
public class SingletonService
{
private readonly IServiceScopeFactory _scopeFactory;
public SingletonService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public void DoWork()
{
using var scope = _scopeFactory.CreateScope();
var scopedService = scope.ServiceProvider.GetRequiredService<IScopedService>();
scopedService.DoWork();
}
}自动注册不生效
检查项:
- DLL是否在应用程序运行目录
- 类是否为非抽象、非泛型的具体类
- 是否正确继承标记接口
- 程序集是否被排除前缀过滤