当前位置:首页 > 技术 > 正文内容

.NET依赖注入高级应用实践

访客 技术 2026年5月6日 14:44 6

依赖注入在.NET中初看简单,但随着应用规模扩大,复杂性也随之增加。服务生命周期管理、多实现方案、配置验证等问题逐渐显现。

现代.NET提供了功能完善的内置容器,支持常见生命周期、构造函数注入、开放泛型、键控服务等特性。C# 12的主构造函数语法进一步简化了依赖声明。

services.AddScoped<IOrderProcessor, OrderProcessor>();

容器不应成为架构核心

内置容器设计简洁是优势而非缺陷。关键在于将服务注册视为组合过程,而非设计决策的堆积场。

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddApiDefaults()
    .AddOrderModule(builder.Configuration)
    .AddPaymentModule(builder.Configuration)
    .AddNotificationModule(builder.Configuration);

var app = builder.Build();
app.MapOrderEndpoints();
app.MapPaymentEndpoints();
app.Run();

模块化注册示例:

public static class OrderModuleRegistration
{
    public static IServiceCollection AddOrderModule(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        services.AddScoped<IOrderStore, SqlOrderStore>();
        services.AddScoped<IOrderNumberProvider, OrderNumberProvider>();
        services.AddScoped<IOrderCreationHandler, OrderCreationHandler>();

        services.AddOptions<OrderSettings>()
            .Bind(configuration.GetSection(OrderSettings.SectionName))
            .ValidateDataAnnotations()
            .ValidateOnStart();

        return services;
    }
}

主构造函数优化依赖声明

主构造函数减少模板代码,使依赖关系更加清晰:

public sealed class OrderCreationHandler(
    IOrderStore orderStore,
    IPaymentProcessor paymentProcessor,
    ITimeProvider timeProvider,
    ILogger<OrderCreationHandler> logger)
{
    public async Task ExecuteAsync(
        CreateOrderCommand command,
        CancellationToken cancellationToken)
    {
        logger.LogInformation(
            "Creating order for customer {CustomerId}",
            command.CustomerId);

        var order = Order.Create(
            command.CustomerId,
            command.Items,
            timeProvider.UtcNow);

        await paymentProcessor.ProcessPaymentAsync(command.Payment, cancellationToken);
        await orderStore.SaveAsync(order, cancellationToken);
    }
}

生命周期设计决策

生命周期错误是常见问题。基本原则是长生命周期服务不应依赖短生命周期服务。

错误示例:

public sealed class OrderCache(OrderDbContext dbContext)
{
    public Task<Order?> RetrieveAsync(
        int orderId,
        CancellationToken cancellationToken)
    {
        return dbContext.Orders
            .FindAsync([orderId], cancellationToken)
            .AsTask();
    }
}

// 错误注册
services.AddDbContext<OrderDbContext>(options =>
{
    options.UseSqlServer(connectionString);
});
services.AddSingleton<OrderCache>();

正确解决方案:

public sealed class OrderCache(
    IMemoryCache cache,
    IServiceScopeFactory scopeFactory)
{
    public async Task<OrderSummary?> RetrieveAsync(
        int orderId,
        CancellationToken cancellationToken)
    {
        var cacheKey = $"orders:summary:{orderId}";

        if (cache.TryGetValue(cacheKey, out OrderSummary? cached))
        {
            return cached;
        }

        using var scope = scopeFactory.CreateScope();
        var dbContext = scope.ServiceProvider
            .GetRequiredService<OrderDbContext>();

        var order = await dbContext.Orders
            .Where(x => x.Id == orderId)
            .Select(x => new OrderSummary(
                x.Id,
                x.OrderNumber,
                x.Status,
                x.TotalAmount))
            .SingleOrDefaultAsync(cancellationToken);

        if (order != null)
        {
            cache.Set(cacheKey, order, TimeSpan.FromMinutes(5));
        }

        return order;
    }
}

配置验证最佳实践

配置验证应在应用启动时完成:

public sealed class PaymentGatewaySettings
{
    public const string SectionName = "PaymentGateway";
    public required string BaseAddress { get; init; }
    public required string ApiKey { get; init; }
    public int TimeoutSeconds { get; init; } = 30;
}

// 注册时验证
services.AddOptions<PaymentGatewaySettings>()
    .Bind(configuration.GetSection(PaymentGatewaySettings.SectionName))
    .Validate(settings => Uri.TryCreate(
        settings.BaseAddress,
        UriKind.Absolute,
        out _),
        "PaymentGateway:BaseAddress must be valid absolute URL.")
    .Validate(settings => !string.IsNullOrWhiteSpace(settings.ApiKey),
        "PaymentGateway:ApiKey is required.")
    .Validate(settings => settings.TimeoutSeconds >= 1 && settings.TimeoutSeconds <= 300,
        "PaymentGateway:TimeoutSeconds must be between 1 and 300.")
    .ValidateOnStart();

HttpClient工厂模式

使用类型化客户端管理HTTP连接:

public sealed class PaymentGatewayClient(HttpClient httpClient)
{
    public async Task<PaymentResult> ProcessPaymentAsync(
        PaymentRequest request,
        CancellationToken cancellationToken)
    {
        using var response = await httpClient.PostAsJsonAsync(
            "payments",
            request,
            cancellationToken);

        response.EnsureSuccessStatusCode();

        var result = await response.Content
            .ReadFromJsonAsync<PaymentResult>(cancellationToken: cancellationToken);

        return result ?? throw new InvalidOperationException(
            "Payment gateway returned empty response.");
    }
}

// 注册类型化客户端
services.AddHttpClient<PaymentGatewayClient>((sp, client) =>
{
    var settings = sp
        .GetRequiredService<IOptions<PaymentGatewaySettings>>()
        .Value;

    client.BaseAddress = new Uri(settings.BaseAddress);
    client.Timeout = TimeSpan.FromSeconds(settings.TimeoutSeconds);
    client.DefaultRequestHeaders.Add("X-Api-Key", settings.ApiKey);
});

后台服务中的作用域管理

后台服务需要正确处理作用域依赖:

public sealed class InvoiceProcessor(
    IServiceScopeFactory scopeFactory,
    ILogger<InvoiceProcessor> logger)
    : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            try
            {
                using var scope = scopeFactory.CreateScope();
                var batchProcessor = scope.ServiceProvider
                    .GetRequiredService<IInvoiceBatchProcessor>();

                await batchProcessor.ProcessPendingAsync(cancellationToken);
            }
            catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
            {
                break;
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Invoice processing failed");
            }

            await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
        }
    }
}

装饰器模式应用

使用装饰器实现横切关注点:

public sealed class LoggingCommandHandler<TCommand>(
    ICommandHandler<TCommand> inner,
    ILogger<LoggingCommandHandler<TCommand>> logger)
    : ICommandHandler<TCommand>
{
    public async Task ExecuteAsync(
        TCommand command,
        CancellationToken cancellationToken)
    {
        var commandName = typeof(TCommand).Name;

        logger.LogInformation("Executing command {CommandName}", commandName);

        try
        {
            await inner.ExecuteAsync(command, cancellationToken);
            logger.LogInformation("Command {CommandName} completed", commandName);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Command {CommandName} failed", commandName);
            throw;
        }
    }
}

键控服务合理使用

键控服务适用于基础设施变体:

public interface IFileStorage
{
    Task StoreAsync(string path, Stream content, CancellationToken cancellationToken);
}

public sealed class PublicFileStorage : IFileStorage
{
    public Task StoreAsync(string path, Stream content, CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}

public sealed class PrivateFileStorage : IFileStorage
{
    public Task StoreAsync(string path, Stream content, CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}

// 注册键控服务
services.AddKeyedScoped<IFileStorage, PublicFileStorage>("public");
services.AddKeyedScoped<IFileStorage, PrivateFileStorage>("private");

开放泛型注册

开放泛型适用于通用基础设施模式:

public interface IRepository<TEntity> where TEntity : class
{
    Task<TEntity?> GetByIdAsync(int id, CancellationToken cancellationToken);
    Task AddAsync(TEntity entity, CancellationToken cancellationToken);
}

public sealed class EfRepository<TEntity>(DbContext dbContext)
    : IRepository<TEntity> where TEntity : class
{
    public async Task<TEntity?> GetByIdAsync(int id, CancellationToken cancellationToken)
    {
        return await dbContext.Set<TEntity>()
            .FindAsync([id], cancellationToken);
    }

    public async Task AddAsync(TEntity entity, CancellationToken cancellationToken)
    {
        await dbContext.Set<TEntity>()
            .AddAsync(entity, cancellationToken);
    }
}

// 泛型注册
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));

模块边界保护

依赖注入应保护模块边界:

public interface IOrderReader
{
    Task<OrderBillingView?> GetBillingViewAsync(int orderId, CancellationToken cancellationToken);
}

internal sealed class OrderReader(OrderDbContext dbContext) : IOrderReader
{
    public Task<OrderBillingView?> GetBillingViewAsync(int orderId, CancellationToken cancellationToken)
    {
        return dbContext.Orders
            .Where(x => x.Id == orderId)
            .Select(x => new OrderBillingView(
                x.Id,
                x.CustomerId,
                x.TotalAmount,
                x.Currency))
            .SingleOrDefaultAsync(cancellationToken);
    }
}

实践性注册结构

中等规模应用推荐结构:

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddPresentationLayer()
    .AddApplicationLayer()
    .AddInfrastructureLayer(builder.Configuration)
    .AddFeatureModules(builder.Configuration);

var app = builder.Build();
app.UseExceptionHandling();
app.UseAuthentication();
app.UseAuthorization();
app.MapApiEndpoints();
app.Run();

各层职责:

  • 表现层:控制器、API端点、认证授权
  • 应用层:命令处理器、验证器、领域服务
  • 基础设施层:数据库、HTTP客户端、存储提供者
  • 功能模块:特性区域组合
标签: .NET

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。