EasilyNET.AutoDependencyInjection
6.26.201.257
dotnet add package EasilyNET.AutoDependencyInjection --version 6.26.201.257
NuGet\Install-Package EasilyNET.AutoDependencyInjection -Version 6.26.201.257
<PackageReference Include="EasilyNET.AutoDependencyInjection" Version="6.26.201.257" />
<PackageVersion Include="EasilyNET.AutoDependencyInjection" Version="6.26.201.257" />
<PackageReference Include="EasilyNET.AutoDependencyInjection" />
paket add EasilyNET.AutoDependencyInjection --version 6.26.201.257
#r "nuget: EasilyNET.AutoDependencyInjection, 6.26.201.257"
#:package EasilyNET.AutoDependencyInjection@6.26.201.257
#addin nuget:?package=EasilyNET.AutoDependencyInjection&version=6.26.201.257
#tool nuget:?package=EasilyNET.AutoDependencyInjection&version=6.26.201.257
EasilyNET.AutoDependencyInjection
一个功能强大的自动依赖注入模块系统,提供模块化的服务配置和中间件管理能力。
核心特性
1. 模块化架构 (AppModule)
- 基于
AppModule的模块系统,支持依赖关系声明 (DependsOn) - 模块加载顺序自动解析(拓扑排序),确保依赖模块优先初始化
- 支持通过
GetEnable方法动态控制模块启用/禁用(可从配置文件读取) - 新增循环依赖检测,防止错误的模块依赖声明
2. 同步/异步分离设计
ConfigureServices(context)- 同步方法,用于服务注册(99%场景)ConfigureServicesAsync(context, ct)- 异步方法,用于罕见的异步初始化场景ApplicationInitialization(context)- 异步方法,用于中间件/应用配置- 清晰的生命周期划分,避免死锁风险
3. KeyedService 支持
- 完整支持 .NET 的 KeyedService 功能
- 可在
DependencyInjectionAttribute中使用ServiceKey属性标识服务键值 - 支持通过
ResolveKeyed<T>(key)解析键控服务
4. 高级服务解析器 (IResolver)
- 提供类似 Autofac 的动态解析能力,同时基于
Microsoft.Extensions.DependencyInjection - 支持构造函数参数覆盖 (NamedParameter, TypedParameter, ResolvedParameter)
- 支持可选解析、批量解析、命名解析、键控解析
- 支持创建独立作用域 (
BeginScope)
5. 多平台支持
- Web 应用: ASP.NET Core (WebApplication, IApplicationBuilder)
- 桌面应用: WPF, WinForms, WinUI3 (.NET 项目,不支持 .NET Framework)
- 统一的 API 接口,便于跨平台项目复用模块
6. 模块诊断 API
- 新增
IModuleDiagnostics接口 - 查看已加载模块及其执行顺序
- 查看自动注册的服务列表
- 验证模块依赖关系
示例项目
| 平台 | 示例项目 | 状态 |
|---|---|---|
| WPF | WPF 示例 | ✅ 最新 |
| WinForms | WinForms 示例 | ✅ 最新 |
| WinUI3 | WinUI3 示例 | ⚠️ 待更新 |
Resolver 高级解析器
IResolver 提供比原生 IServiceProvider 更强大的服务解析能力。
核心方法
| 方法 | 说明 |
|---|---|
Resolve<T>() |
解析服务,失败抛异常 |
TryResolve<T>(out var) |
尝试解析服务,失败返回 false |
ResolveOptional<T>() |
解析可选服务,失败返回 null |
ResolveAll<T>() |
解析所有已注册的 T 服务 |
ResolveKeyed<T>(key) |
解析键控服务(KeyedService) |
ResolveNamed<T>(name) |
解析命名服务 |
BeginScope() |
创建子作用域 |
构造函数参数注入
支持三种参数类型:
- NamedParameter: 按参数名匹配
- TypedParameter: 按参数类型匹配
- ResolvedParameter: 自定义匹配逻辑和值提供
使用示例
// 1. 基本解析
var resolver = provider.CreateResolver();
var service = resolver.Resolve<IMyService>();
// 2. 带参数覆盖的解析
var service = resolver.Resolve<IMyService>(
new NamedParameter("connectionString", "Server=localhost"),
new TypedParameter(typeof(ILogger), logger)
);
// 3. 键控服务解析
var keyedService = resolver.ResolveKeyed<ICache>("redis",
new NamedParameter("endpoint", "127.0.0.1:6379")
);
// 4. 批量解析
var allHandlers = resolver.ResolveAll<IEventHandler>();
// 5. 可选解析
var optional = resolver.ResolveOptional<IOptionalService>();
// 6. 作用域解析
using var scopedResolver = resolver.BeginScope();
var scopedService = scopedResolver.Resolve<IScopedService>();
IServiceProvider 扩展方法
也可以直接在 IServiceProvider 上使用这些能力:
// 创建 Resolver(可选择是否创建作用域)
var resolver = provider.CreateResolver(createScope: true);
// 或者直接使用扩展方法
var service = provider.Resolve<IMyService>();
var keyed = provider.ResolveKeyed<ICache>("redis");
var withParams = provider.Resolve<MyService>(
new NamedParameter("config", configuration)
);
性能优化
- 构造函数信息和参数元数据被缓存,避免重复反射
- 优先选择能满足所有参数的构造函数
- 支持
[FromKeyedServices]特性注入键控依赖
WPF/WinForms 桌面应用集成
WPF 项目配置
1. 修改 App.xaml.cs
public partial class App : Application
{
[STAThread]
public static void Main(string[] args)
{
using var host = CreateHostBuilder(args).Build();
host.InitializeApplication();
host.Start();
var app = new App();
app.InitializeComponent();
app.MainWindow = host.Services.GetRequiredService<MainWindow>();
app.MainWindow.Visibility = Visibility.Visible;
app.Run();
}
private static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureServices(sc =>
{
sc.AddApplicationModules<AppServiceModules>();
});
}
}
2. 调整 .csproj 文件
<ItemGroup>
<ApplicationDefinition Remove="App.xaml" />
<Page Include="App.xaml" />
</ItemGroup>
3. 创建模块类 (AppServiceModules.cs)
[DependsOn(typeof(DependencyAppModule))]
internal sealed class AppServiceModules : AppModule
{
// 同步服务注册(推荐)
public override void ConfigureServices(ConfigureServicesContext context)
{
// 注册应用服务
context.Services.AddSingleton<IMyService, MyService>();
}
// 可选:异步初始化
public override Task ConfigureServicesAsync(ConfigureServicesContext context, CancellationToken ct)
{
// 罕见的异步初始化场景
return Task.CompletedTask;
}
}
4. 注册窗口和服务
// 使用特性注册窗口(注意需要 AddSelf = true)
[DependencyInjection(ServiceLifetime.Singleton, AddSelf = true, SelfOnly = true)]
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
⚠️ 桌面应用注意事项
AddSelf 必须设置为 true
- 默认情况下会注册实现类的父类(如 Window),导致无法通过
GetRequiredService<MainWindow>()获取 - 设置
AddSelf = true, SelfOnly = true确保注册具体的窗口类型
- 默认情况下会注册实现类的父类(如 Window),导致无法通过
获取 IHost 的方式不同
// Web 项目 var app = context.GetApplicationHost() as WebApplication; // 或 var app = context.GetApplicationHost() as IApplicationBuilder; // 桌面项目(WPF/WinForms) var host = context.GetApplicationHost() as IHost;
Web 应用集成 (ASP.NET Core)
快速开始
1. 使用特性注入服务
// 标记服务类,自动注入到容器
[DependencyInjection(ServiceLifetime.Scoped)]
public class OrderService : IOrderService
{
private readonly IRepository _repository;
public OrderService(IRepository repository)
{
_repository = repository;
}
}
// 支持 KeyedService
[DependencyInjection(ServiceLifetime.Singleton, ServiceKey = "redis")]
public class RedisCache : ICache
{
// ...
}
2. 创建模块 (AppModule)
// Step 1: 创建功能模块(如 CORS 配置模块)
public class CorsModule : AppModule
{
// 可从配置文件读取是否启用此模块
public override bool GetEnable(ConfigureServicesContext context)
{
var config = context.Configuration; // 直接使用 context.Configuration
return config.GetSection("ServicesEnable").GetValue<bool>("Cors");
}
// 同步服务注册(推荐)
public override void ConfigureServices(ConfigureServicesContext context)
{
var config = context.Configuration; // 直接使用 context.Configuration
var allow = config["AllowedHosts"] ?? "*";
context.Services.AddCors(c =>
c.AddPolicy("AllowedHosts", s =>
s.WithOrigins(allow.Split(","))
.AllowAnyMethod()
.AllowAnyHeader()));
}
// 配置中间件(异步)
public override Task ApplicationInitialization(ApplicationContext context)
{
var app = context.GetApplicationHost() as IApplicationBuilder;
app?.UseCors("AllowedHosts");
return Task.CompletedTask;
}
}
3. 创建根模块
// Step 2: 使用 DependsOn 声明模块依赖关系
[DependsOn(
typeof(DependencyAppModule), // 必须依赖,提供自动注入功能
typeof(CorsModule) // 自定义模块
)]
public class AppWebModule : AppModule
{
// 同步服务注册
public override void ConfigureServices(ConfigureServicesContext context)
{
context.Services.AddHttpContextAccessor();
// 其他服务注册
}
// 可选:异步初始化
public override Task ConfigureServicesAsync(ConfigureServicesContext context, CancellationToken ct)
{
// 罕见的异步初始化场景
return Task.CompletedTask;
}
// 应用初始化(异步)
public override Task ApplicationInitialization(ApplicationContext context)
{
var app = context.GetApplicationHost() as IApplicationBuilder;
app?.UseAuthorization();
// 其他中间件配置
return Task.CompletedTask;
}
}
4. 在 Program.cs 中启用
var builder = WebApplication.CreateBuilder(args);
// 注册模块系统
builder.Services.AddApplicationModules<AppWebModule>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// 初始化模块(执行所有模块的 ApplicationInitialization)
app.InitializeApplication();
// 或使用异步版本
// await app.InitializeApplicationAsync();
app.MapControllers();
app.Run();
模块化架构最佳实践
模块依赖顺序
模块的 DependsOn 顺序决定了初始化顺序。被依赖的模块会先执行(使用拓扑排序算法):
[DependsOn(
typeof(DependencyAppModule), // 第 1 个初始化
typeof(DatabaseModule), // 第 2 个初始化(DependencyAppModule 完成后)
typeof(CachingModule), // 第 3 个初始化
typeof(AuthenticationModule) // 第 4 个初始化
)]
public class AppWebModule : AppModule // 最后初始化
{
// ...
}
重要:如果 DatabaseModule 依赖 DependencyAppModule,则 DependencyAppModule 会先执行,然后是 DatabaseModule,无论它们在 DependsOn 中的声明顺序如何。
模块职责划分
建议按功能领域划分模块:
// 数据库模块
public class DatabaseModule : AppModule
{
// 同步服务注册
public override void ConfigureServices(ConfigureServicesContext context)
{
var connectionString = context.Configuration
.GetConnectionString("Default");
context.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
}
}
// 认证模块
public class AuthenticationModule : AppModule
{
// 同步服务注册
public override void ConfigureServices(ConfigureServicesContext context)
{
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => { /* ... */ });
}
// 异步中间件配置
public override Task ApplicationInitialization(ApplicationContext context)
{
var app = context.GetApplicationHost() as IApplicationBuilder;
app?.UseAuthentication();
app?.UseAuthorization();
return Task.CompletedTask;
}
}
// Swagger 文档模块
public class SwaggerModule : AppModule
{
public override bool GetEnable(ConfigureServicesContext context)
{
// 使用 context.Configuration 直接访问配置
return context.Configuration.GetValue<bool>("Swagger:Enabled");
}
// 同步服务注册
public override void ConfigureServices(ConfigureServicesContext context)
{
context.Services.AddSwaggerGen();
}
// 异步中间件配置
public override Task ApplicationInitialization(ApplicationContext context)
{
var app = context.GetApplicationHost() as IApplicationBuilder;
app?.UseSwagger();
app?.UseSwaggerUI();
return Task.CompletedTask;
}
}
配置驱动的模块启用
在 appsettings.json 中配置模块开关:
{
"ServicesEnable": {
"Cors": true,
"Swagger": true,
"HealthChecks": false
}
}
在模块中读取配置:
public override bool GetEnable(ConfigureServicesContext context)
{
// 推荐使用 context.Configuration
return context.Configuration
.GetSection("ServicesEnable")
.GetValue<bool>("Swagger");
}
DependencyInjection 特性说明
特性属性
| 属性 | 类型 | 说明 | 默认值 |
|---|---|---|---|
Lifetime |
ServiceLifetime | 服务生命周期(Singleton/Scoped/Transient) | Scoped |
ServiceKey |
object? | 键控服务的键值(KeyedService) | null |
AddSelf |
bool | 是否注册实现类自身 | false |
SelfOnly |
bool | 是否仅注册实现类(不注册接口) | false |
使用示例
// 基础用法:注册接口
[DependencyInjection(ServiceLifetime.Scoped)]
public class UserService : IUserService
{
// 会注册 IUserService -> UserService
}
// 键控服务
[DependencyInjection(ServiceLifetime.Singleton, ServiceKey = "primary")]
public class PrimaryDatabase : IDatabase
{
// 会注册 Keyed Service: "primary" -> PrimaryDatabase
}
// 同时注册接口和实现类
[DependencyInjection(ServiceLifetime.Scoped, AddSelf = true)]
public class ProductService : IProductService
{
// 会注册两个:
// 1. IProductService -> ProductService
// 2. ProductService -> ProductService
}
// 仅注册实现类(常用于 Window/Page)
[DependencyInjection(ServiceLifetime.Singleton, AddSelf = true, SelfOnly = true)]
public partial class MainWindow : Window
{
// 仅注册 MainWindow -> MainWindow
// 不注册 Window -> MainWindow
}
中断性变更说明
v4.x → v5.x
ConfigureServices 改为同步方法
ConfigureServices从async Task改为void- 原因:服务注册阶段本身应该是同步的,避免死锁风险
- 迁移:移除
async/await关键字,删除await Task.CompletedTask
新增 ConfigureServicesAsync 方法
- 如需在服务注册阶段执行异步操作,重写
ConfigureServicesAsync - 此方法在
ConfigureServices之后调用
- 如需在服务注册阶段执行异步操作,重写
配置访问方式变更
- 旧:
context.ServiceProvider.GetConfiguration() - 新:
context.Configuration(推荐)或context.ServiceProvider.GetRequiredService<IConfiguration>()
- 旧:
IStartupModuleRunner.Initialize 签名变更
- 旧:
void Initialize() - 新:
void Initialize(IServiceProvider serviceProvider)
- 旧:
新增循环依赖检测
- 如果模块存在循环依赖,将抛出
InvalidOperationException - 确保模块依赖形成有向无环图(DAG)
- 如果模块存在循环依赖,将抛出
新增 IModuleDiagnostics 接口
- 可注入
IModuleDiagnostics查看模块加载情况 - 支持查看模块执行顺序、自动注册服务列表
- 可注入
v3.x → v4.x
异步方法
ConfigureServices和ApplicationInitialization改为异步- 需要返回
Task,使用await Task.CompletedTask结束同步方法
GetEnable 函数
- 移除
Enable属性 - 新增
GetEnable方法,支持运行时动态判断
- 移除
IHost 统一
GetApplicationBuilder()已弃用- 使用
GetApplicationHost()并根据平台转换类型
常见问题 (FAQ)
Q: 如何在模块中使用配置?
// 推荐方式(新)
public override void ConfigureServices(ConfigureServicesContext context)
{
var config = context.Configuration;
var connectionString = config.GetConnectionString("Default");
}
// 兼容方式(旧,仍可用)
public override void ConfigureServices(ConfigureServicesContext context)
{
var config = context.ServiceProvider.GetRequiredService<IConfiguration>();
}
Q: 如何在运行时获取 Scoped 服务?
// 方式 1: 使用 IServiceScopeFactory
var scopeFactory = provider.GetRequiredService<IServiceScopeFactory>();
using var scope = scopeFactory.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IScopedService>();
// 方式 2: 使用 Resolver
using var resolver = provider.CreateResolver(createScope: true);
var service = resolver.Resolve<IScopedService>();
Q: 模块的初始化顺序是怎样的?
使用拓扑排序算法,确保依赖模块先执行:
- 执行所有模块的
ConfigureServices(按依赖顺序,依赖项优先) - 执行所有模块的
ConfigureServicesAsync(按依赖顺序) - 构建 ServiceProvider
- 执行所有模块的
ApplicationInitialization(按依赖顺序)
Q: 如何查看模块加载情况?
// 注入 IModuleDiagnostics
public class MyService
{
private readonly IModuleDiagnostics _diagnostics;
public MyService(IModuleDiagnostics diagnostics)
{
_diagnostics = diagnostics;
// 获取已加载模块
var modules = _diagnostics.GetLoadedModules();
foreach (var module in modules)
{
Console.WriteLine($"{module.Order}: {module.Name}");
}
// 验证依赖关系
var issues = _diagnostics.ValidateModuleDependencies();
if (issues.Count > 0)
{
foreach (var issue in issues)
{
Console.WriteLine($"警告: {issue}");
}
}
}
}
Q: 如何禁用某个模块?
重写 GetEnable 方法返回 false:
public override bool GetEnable(ConfigureServicesContext context) => false;
Q: 出现 "Circular dependency detected" 错误怎么办?
检查 DependsOn 声明,确保没有循环依赖:
// ❌ 错误:循环依赖
[DependsOn(typeof(ModuleB))]
public class ModuleA : AppModule { }
[DependsOn(typeof(ModuleA))] // 循环依赖!
public class ModuleB : AppModule { }
// ✅ 正确:无循环依赖
[DependsOn(typeof(BaseModule))]
public class ModuleA : AppModule { }
[DependsOn(typeof(BaseModule))]
public class ModuleB : AppModule { }
性能优化建议
- 缓存构造函数信息: Resolver 已内置构造函数缓存,避免重复反射
- 合理使用作用域: 避免在 Singleton 中注入 Scoped 服务
- 延迟初始化: 不需要的模块通过
GetEnable返回 false 禁用 - 同步注册优先: 使用
ConfigureServices而非ConfigureServicesAsync,除非确实需要异步操作 - 避免过早 BuildServiceProvider: 框架已优化,通常不需要手动构建
技术支持
- 📖 示例项目: WPF | WinForms | WinUI3
- 🐛 问题反馈: GitHub Issues
- 💡 功能建议: 欢迎提交 Pull Request
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- EasilyNET.AutoDependencyInjection.Core (>= 6.26.201.257)
- EasilyNET.Core (>= 6.26.201.257)
-
net8.0
- EasilyNET.AutoDependencyInjection.Core (>= 6.26.201.257)
- EasilyNET.Core (>= 6.26.201.257)
-
net9.0
- EasilyNET.AutoDependencyInjection.Core (>= 6.26.201.257)
- EasilyNET.Core (>= 6.26.201.257)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 6.26.201.257 | 38 | 1/31/2026 |
| 6.26.114.102 | 88 | 1/14/2026 |
| 6.26.107.173 | 94 | 1/7/2026 |
| 5.25.1212.131 | 138 | 12/12/2025 |
| 5.25.1112.15 | 303 | 11/11/2025 |
| 4.25.1016.112 | 443 | 10/16/2025 |