You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 19:43:19