.NET 8分层架构依赖注入:消除样板代码
简化.NET 8中领域层服务的依赖注入注册
问题背景
我正在构建一个基于.NET 8的解决方案,包含API(Microsoft.NET.Sdk.Web)、领域(Microsoft.NET.Sdk)和MVC(Microsoft.NET.Sdk.Web)项目。
领域层中有以下两个类:
public class ApplicationUserQuery { private readonly string _connectionString; public ApplicationUserQuery(string connectionString) { _connectionString = connectionString; } // ... } public class ApplicationUserService { private readonly ApplicationUserQuery _applicationUserQuery; public ApplicationUserService(ApplicationUserQuery applicationUserQuery) { _applicationUserQuery = applicationUserQuery; } // ... }
目前在API项目的Program.cs中手动注册这些类:
var applicationDatabaseConnectionString = builder.Configuration.GetConnectionString("ApplicationDatabase"); if (applicationDatabaseConnectionString == null) { throw new ArgumentNullException(nameof(applicationDatabaseConnectionString)); } builder.Services.AddScoped(_ => new ApplicationUserQuery(applicationDatabaseConnectionString)); builder.Services.AddScoped<ApplicationUserService>();
这种方式可行,但随着领域层服务/查询类增多,手动注册的样板代码会持续膨胀,希望用.NET内置功能简化,避免使用第三方DI库。
解决方案
1. 优化连接字符串注入,减少重复实例化代码
先把连接字符串注册为单例服务,让DI自动解析依赖,不用手动创建类实例:
// 注册连接字符串为单例,确保全局唯一且可被自动注入 builder.Services.AddSingleton(sp => { var connStr = sp.GetRequiredService<IConfiguration>().GetConnectionString("ApplicationDatabase") ?? throw new ArgumentNullException("ApplicationDatabase connection string not found"); return connStr; }); // 直接注册类,DI会自动匹配构造函数参数 builder.Services.AddScoped<ApplicationUserQuery>(); builder.Services.AddScoped<ApplicationUserService>();
后续新增的需要连接字符串的类,只要构造函数接受string类型的连接字符串,就能自动被DI解析,不用再写手动实例化的代码。
2. 批量扫描领域层程序集,自动注册服务
如果领域层的服务/查询类遵循统一命名规则(比如以Query或Service结尾),可以通过反射批量扫描注册,避免逐个添加:
先创建一个扩展方法:
public static class ServiceCollectionExtensions { public static IServiceCollection AddDomainServices(this IServiceCollection services, Assembly domainAssembly) { // 筛选出领域层中所有非抽象、非接口、以Query/Service结尾的类 var serviceTypes = domainAssembly.GetTypes() .Where(t => !t.IsAbstract && !t.IsInterface && (t.Name.EndsWith("Query") || t.Name.EndsWith("Service"))); // 批量注册为Scoped服务 foreach (var type in serviceTypes) { services.AddScoped(type); } return services; } }
然后在Program.cs中调用:
// 先注册连接字符串(同方案1) builder.Services.AddSingleton(sp => { var connStr = sp.GetRequiredService<IConfiguration>().GetConnectionString("ApplicationDatabase") ?? throw new ArgumentNullException("ApplicationDatabase connection string not found"); return connStr; }); // 获取领域层程序集,批量注册所有符合规则的服务 var domainAssembly = typeof(ApplicationUserQuery).Assembly; builder.Services.AddDomainServices(domainAssembly);
后续新增的Query或Service类,只要放在领域层程序集中,就会自动被注册,无需手动修改Program.cs。
3. 结合Options模式封装配置(适合多配置项场景)
如果后续需要管理更多数据库配置项,可以用Options模式封装,让配置更清晰:
首先定义配置类:
public class DatabaseSettings { public string ApplicationDatabase { get; set; } = string.Empty; }
在Program.cs中绑定配置:
// 绑定ConnectionStrings节点到DatabaseSettings builder.Services.Configure<DatabaseSettings>(builder.Configuration.GetSection("ConnectionStrings"));
修改ApplicationUserQuery的构造函数,依赖IOptions<DatabaseSettings>:
public class ApplicationUserQuery { private readonly string _connectionString; public ApplicationUserQuery(IOptions<DatabaseSettings> settings) { _connectionString = settings.Value.ApplicationDatabase ?? throw new ArgumentNullException(nameof(settings.Value.ApplicationDatabase)); } // ... }
最后直接注册服务即可:
builder.Services.AddScoped<ApplicationUserQuery>(); builder.Services.AddScoped<ApplicationUserService>();
这种方式同样可以和批量扫描方案结合,进一步减少样板代码。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

