ASP NET Core 依赖注入容器中最常用的五大内置服务

ASP .NET Core 依赖注入容器中最常用的五大内置服务:使用与扩展(含完整示例)

本文面向 .NET 6+ 的 ASP .NET Core 应用,系统梳理依赖注入(DI)容器中“最常用的五大内置服务”,分别是:

  • IConfiguration(配置)
  • ILogger<T>(日志)
  • IOptions<T>/IOptionsMonitor<T>/IOptionsSnapshot<T>(选项配置)
  • IHttpClientFactory(HTTP 客户端工厂)
  • IMemoryCache(内存缓存)

1. 预备知识:ASP .NET Core DI 与生命周期

  • 注册方式:
    • Program.cs 中通过 builder.Services.AddXxx() 注册;
    • 系统默认会注册一批常用的通用基础设施服务(如配置、日志、选项等)。
  • 生命周期(Lifetime):
    • Singleton:应用全局单例,适合无状态且线程安全的服务(如 IConfigurationILoggerFactory)。
    • Scoped:每个请求一个实例(Web 场景中最常用)。
    • Transient:每次注入/解析都创建新实例。
  • 在控制器、Minimal API、BackgroundService、Middleware 中均可通过构造函数或方法参数注入服务。

2. 五大内置服务总览

  • IConfiguration
    • 职责:统一访问配置系统(JSON、环境变量、命令行、用户密钥、远端配置源)。
    • 生命周期:Singleton。
  • ILogger<T>
    • 职责:结构化日志记录,支持多提供程序(Console、Debug、EventSource、文件等第三方)。
    • 生命周期:ILogger<T> 为轻量级工厂产物,按需创建;ILoggerFactory 为 Singleton。
  • IOptions<T>/IOptionsSnapshot<T>/IOptionsMonitor<T>
    • 职责:强类型配置绑定与访问,支持验证、命名 Options、热更新。
    • 生命周期:IOptions<T> 是 Singleton 读取初始值;IOptionsSnapshot<T> ScopedIOptionsMonitor<T> Singleton + 变更回调。
  • IHttpClientFactory
    • 职责:规范、可复用、可配置、可观测的 HttpClient 创建工厂;支持命名/类型化客户端、处理程序管道。
    • 生命周期:工厂 Singleton;HttpClient 本身短生命周期但共享连接池。
  • IMemoryCache
    • 职责:进程内缓存,支持过期策略、优先级、逐出回调。
    • 生命周期:Singleton。

3. IConfiguration:配置系统

3.1 常见用法

  • 自动加载 appsettings.jsonappsettings.{Environment}.json、环境变量、命令行参数等。
  • 可通过索引器或 GetSection 访问。
  • 可与 Options 模式结合进行强类型绑定。

示例(访问配置):

public class HomeController : ControllerBase
{
    private readonly IConfiguration _config;

    public HomeController(IConfiguration config)
    {
        _config = config;
    }

    [HttpGet("/config")]
    public IActionResult Get()
    {
        var appName = _config["App:Name"];                     // 直接键访问
        var connectionString = _config.GetConnectionString("Default"); // 访问连接串
        var sectionValue = _config.GetSection("App:Features:NewUI").Value;

        return Ok(new { appName, connectionString, sectionValue });
    }
}

Program.cs 中,默认已添加配置源,可额外补充:

builder.Configuration
    .AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: true)
    .AddEnvironmentVariables(prefix: "MYAPP_")
    .AddInMemoryCollection(new Dictionary<string, string?>
    {
        ["App:InjectedKey"] = "FromInMemory"
    });

3.2 与 Options 模式配合(强类型绑定)

public class MySettings
{
    public string AppName { get; set; } = "DemoApp";
    public bool EnableBeta { get; set; }
}

builder.Services.Configure<MySettings>(builder.Configuration.GetSection("App"));

3.3 如何扩展:自定义配置源

实现 IConfigurationSourceIConfigurationProvider 可接入自定义配置源(例如远端 HTTP JSON 配置)。

简化示例(只读、无热更新):

public class SimpleDictionaryConfigurationSource : IConfigurationSource
{
    private readonly IDictionary<string, string?> _data;
    public SimpleDictionaryConfigurationSource(IDictionary<string, string?> data) => _data = data;
    public IConfigurationProvider Build(IConfigurationBuilder builder) => new SimpleDictionaryConfigurationProvider(_data);
}

public class SimpleDictionaryConfigurationProvider : ConfigurationProvider
{
    public SimpleDictionaryConfigurationProvider(IDictionary<string, string?> data)
    {
        Data = new Dictionary<string, string?>(data, StringComparer.OrdinalIgnoreCase);
    }
}

// 注册
builder.Configuration.Add(new SimpleDictionaryConfigurationSource(new Dictionary<string, string?>
{
    ["Feature:AutoSave"] = "true",
    ["Feature:MaxItems"] = "100"
}));

要支持热更新,可结合 IChangeToken,自行触发 OnReload()。

4. ILogger<T>:结构化日志

4.1 常见用法

  • 注入 ILogger<T>,使用 LogInformation/LogWarning/LogError 等方法。
  • 结构化日志:使用命名占位符 {Name} 提供模板化字段,方便查询与分析。
  • 日志作用域:使用 BeginScope 增加上下文。
public class OrderService
{
    private readonly ILogger<OrderService> _logger;
    public OrderService(ILogger<OrderService> logger) => _logger = logger;

    public void PlaceOrder(string userId, decimal amount)
    {
        using (_logger.BeginScope(new Dictionary<string, object> { ["UserId"] = userId }))
        {
            _logger.LogInformation("Placing order with amount {Amount}", amount);
            try
            {
                // 业务逻辑
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "PlaceOrder failed for user {UserId}", userId);
                throw;
            }
        }
    }
}

appsettings.json 配置日志级别与输出:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.Hosting.Lifetime": "Information",
      "YourNamespace": "Debug"
    }
  }
}

4.2 如何扩展:自定义日志提供程序

实现 ILoggerProviderILogger,可将日志写入文件、数据库等。

简化文件日志示例:

public class FileLoggerProvider : ILoggerProvider
{
    private readonly string _filePath;
    public FileLoggerProvider(string filePath) => _filePath = filePath;
    public ILogger CreateLogger(string categoryName) => new FileLogger(_filePath, categoryName);
    public void Dispose() { }
}

public class FileLogger : ILogger
{
    private readonly string _filePath;
    private readonly string _category;
    public FileLogger(string filePath, string category) { _filePath = filePath; _category = category; }

    public IDisposable? BeginScope<TState>(TState state) => default;
    public bool IsEnabled(LogLevel logLevel) => logLevel >= LogLevel.Information;

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        if (!IsEnabled(logLevel)) return;
        var line = $"{DateTimeOffset.Now:o} [{logLevel}] {_category} {formatter(state, exception)}{(exception is null ? "" : Environment.NewLine + exception)}";
        File.AppendAllText(_filePath, line + Environment.NewLine);
    }
}

// 注册扩展
public static class FileLoggerExtensions
{
    public static ILoggingBuilder AddSimpleFile(this ILoggingBuilder builder, string filePath)
    {
        builder.AddProvider(new FileLoggerProvider(filePath));
        return builder;
    }
}

// Program.cs
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddSimpleFile(Path.Combine(AppContext.BaseDirectory, "app.log"));

5. Options 模式:IOptions<T>/IOptionsSnapshot<T>/IOptionsMonitor<T>

5.1 三者区别

  • IOptions<T>:单例快照,适合不变或只在启动时读取一次的配置。
  • IOptionsSnapshot<T>:每次请求生成新实例(Scoped),适合 Web 请求场景下的“每请求最新”。
  • IOptionsMonitor<T>:单例 + 变更通知,适合需要订阅配置变更并即时响应的服务。

5.2 基础使用与命名 Options

public class RedisOptions
{
    public string ConnectionString { get; set; } = default!;
    public int Database { get; set; }
}

// 默认(未命名)
builder.Services.Configure<RedisOptions>(builder.Configuration.GetSection("Redis:Primary"));

// 命名 Options
builder.Services.Configure<RedisOptions>("Secondary", builder.Configuration.GetSection("Redis:Secondary"));

public class CacheService
{
    private readonly RedisOptions _defaultOptions;
    private readonly IOptionsMonitor<RedisOptions> _monitor;

    public CacheService(IOptions<RedisOptions> options, IOptionsMonitor<RedisOptions> monitor)
    {
        _defaultOptions = options.Value;
        _monitor = monitor;
        _monitor.OnChange((opt, name) =>
        {
            Console.WriteLine($"Redis options changed. Name={name}, Conn={opt.ConnectionString}");
        });
    }

    public RedisOptions GetSecondary() => _monitor.Get("Secondary");
}

5.3 验证与后置配置

  • DataAnnotations 验证:使用 ValidateDataAnnotations。
  • 自定义验证:实现 IValidateOptions<T>
  • 后置配置:IPostConfigureOptions<T> 用于在绑定后追加逻辑。
public class MySettings
{
    [Required, MinLength(3)]
    public string AppName { get; set; } = default!;

    [Range(1, 100)]
    public int MaxItems { get; set; } = 10;
}

builder.Services
    .AddOptions<MySettings>()
    .Bind(builder.Configuration.GetSection("App"))
    .ValidateDataAnnotations()
    .Validate(o => o.MaxItems >= 5, "MaxItems must be >= 5")
    .ValidateOnStart(); // 启动时即验证,避免运行期才暴雷

// 自定义验证
public class MySettingsValidator : IValidateOptions<MySettings>
{
    public ValidateOptionsResult Validate(string? name, MySettings options)
    {
        if (options.AppName?.Contains("bad", StringComparison.OrdinalIgnoreCase) == true)
            return ValidateOptionsResult.Fail("AppName cannot contain 'bad'.");
        return ValidateOptionsResult.Success;
    }
}
builder.Services.AddSingleton<IValidateOptions<MySettings>, MySettingsValidator>();

6. IHttpClientFactory:标准化 HttpClient

6.1 基础用法

  • 使用 AddHttpClient 注册;
  • 支持命名客户端与类型化客户端;
  • 支持 DelegatingHandler 处理管道(重试、熔断、鉴权、指标收集等);
  • 内置连接池,避免 socket 耗尽。
// 命名客户端
builder.Services.AddHttpClient("github", client =>
{
    client.BaseAddress = new Uri("https://api.github.com/");
    client.DefaultRequestHeaders.UserAgent.ParseAdd("MyApp/1.0");
    client.Timeout = TimeSpan.FromSeconds(10);
});

// 类型化客户端
public interface IWeatherClient
{
    Task<string> GetAsync(string city, CancellationToken ct = default);
}

public class WeatherClient : IWeatherClient
{
    private readonly HttpClient _http;
    public WeatherClient(HttpClient http) => _http = http;

    public Task<string> GetAsync(string city, CancellationToken ct = default)
        => _http.GetStringAsync($"/weather?city={Uri.EscapeDataString(city)}", ct);
}

builder.Services.AddHttpClient<IWeatherClient, WeatherClient>(client =>
{
    client.BaseAddress = new Uri("https://weather.example.com");
});

使用 IHttpClientFactory 创建命名客户端:

public class GitHubService
{
    private readonly IHttpClientFactory _factory;
    public GitHubService(IHttpClientFactory factory) => _factory = factory;

    public async Task<string> GetUserAsync(string user)
    {
        var client = _factory.CreateClient("github");
        return await client.GetStringAsync($"/users/{user}");
    }
}

6.2 自定义 DelegatingHandler(扩展)

public class AddHeaderHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.TryAddWithoutValidation("X-Correlation-Id", Guid.NewGuid().ToString("N"));
        return base.SendAsync(request, cancellationToken);
    }
}

builder.Services.AddTransient<AddHeaderHandler>();

builder.Services.AddHttpClient("github")
    .AddHttpMessageHandler<AddHeaderHandler>() // 加入管道
    .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
    {
        PooledConnectionLifetime = TimeSpan.FromMinutes(5),
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
    });

提示:

  • 若需重试/超时/熔断,可引入 Microsoft.Extensions.Http.Resilience(.NET 8+)或 Polly 扩展包进行策略化处理。
  • 观察和日志:HttpClientFactory 生成的客户端默认可被日志系统观测。

7. IMemoryCache:进程内缓存

7.1 基础用法

  • Get/Set,或使用 GetOrCreate/GetOrCreateAsync
  • 过期策略:AbsoluteExpiration、SlidingExpiration。
  • 优先级:CacheItemPriority。
  • 逐出回调:PostEvictionCallbacks。
public class ProductCache
{
    private readonly IMemoryCache _cache;
    private readonly ILogger<ProductCache> _logger;

    public ProductCache(IMemoryCache cache, ILogger<ProductCache> logger)
    {
        _cache = cache; _logger = logger;
    }

    public async Task<Product> GetProductAsync(int id, Func<Task<Product>> factory)
    {
        return await _cache.GetOrCreateAsync($"product:{id}", async entry =>
        {
            entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(5));
            entry.SetSlidingExpiration(TimeSpan.FromMinutes(1));
            entry.SetPriority(CacheItemPriority.Normal);
            entry.RegisterPostEvictionCallback((key, value, reason, state) =>
            {
                _logger.LogInformation("Cache evicted. Key={Key}, Reason={Reason}", key, reason);
            });
            return await factory();
        })!;
    }
}

7.2 与变更令牌(IChangeToken)联动失效

可通过 OptionsMonitorOnChange 或自定义 IChangeToken,调用 cache.Remove(key) 主动失效。

public class FeatureFlagService
{
    private readonly IMemoryCache _cache;
    private readonly IOptionsMonitor<MySettings> _settings;

    public FeatureFlagService(IMemoryCache cache, IOptionsMonitor<MySettings> settings)
    {
        _cache = cache; _settings = settings;
        _settings.OnChange(_ => _cache.Remove("feature:beta")); // 配置变更时清理缓存
    }

    public bool IsBetaEnabled()
    {
        return _cache.GetOrCreate("feature:beta", entry =>
        {
            entry.SetSlidingExpiration(TimeSpan.FromSeconds(30));
            return _settings.CurrentValue.EnableBeta;
        });
    }
}

7.3 扩展方式

  • 装饰器:实现一个包装 IMemoryCache 的自定义类用于打点、指标、Tracing。
  • 自定义实现:实现 IMemoryCache 接口(较少必要,注意线程安全)。
  • 扩展方法:封装常见模式(如基于 Key 的统一过期策略)。

8. 完整示例:Program.cs 与多个服务

下列示例展示一个可运行的最小化 API 应用,演示五大内置服务的典型用法与扩展点。

目录结构建议:

  • Program.cs
  • Services/
    • OrderService.cs
    • GitHubService.cs
    • ProductCache.cs
    • Handlers/AddHeaderHandler.cs
    • Logging/FileLoggerProvider.cs
  • Options/
    • MySettings.cs
    • RedisOptions.cs
    • MySettingsValidator.cs

示例代码(可粘贴到 Program.cs 验证,必要时创建相应类文件):

using System.ComponentModel.DataAnnotations;
using System.Net;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

// ---------------* Options classes ----------------
public class MySettings
{
    [Required, MinLength(3)]
    public string AppName { get; set; } = "DemoApp";
    [Range(1, 100)]
    public int MaxItems { get; set; } = 10;
    public bool EnableBeta { get; set; }
}

public class RedisOptions
{
    public string ConnectionString { get; set; } = "localhost";
    public int Database { get; set; } = 0;
}

// ---------------* Custom validator ----------------
public class MySettingsValidator : Microsoft.Extensions.Options.IValidateOptions<MySettings>
{
    public Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, MySettings options)
    {
        if (options.AppName.Contains("bad", StringComparison.OrdinalIgnoreCase))
            return Microsoft.Extensions.Options.ValidateOptionsResult.Fail("AppName cannot contain 'bad'.");
        return Microsoft.Extensions.Options.ValidateOptionsResult.Success;
    }
}

// ---------------* DelegatingHandler ----------------
public class AddHeaderHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.TryAddWithoutValidation("X-Correlation-Id", Guid.NewGuid().ToString("N"));
        return base.SendAsync(request, cancellationToken);
    }
}

// ---------------* File logger (simple) ----------------
public class FileLoggerProvider : ILoggerProvider
{
    private readonly string _filePath;
    public FileLoggerProvider(string filePath) => _filePath = filePath;
    public ILogger CreateLogger(string categoryName) => new FileLogger(_filePath, categoryName);
    public void Dispose() { }
}

public class FileLogger : ILogger
{
    private readonly string _filePath;
    private readonly string _category;
    public FileLogger(string filePath, string category) { _filePath = filePath; _category = category; }
    public IDisposable? BeginScope<TState>(TState state) => default;
    public bool IsEnabled(LogLevel logLevel) => logLevel >= LogLevel.Information;
    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        if (!IsEnabled(logLevel)) return;
        var line = $"{DateTimeOffset.Now:o} [{logLevel}] {_category} {formatter(state, exception)}{(exception is null ? "" : Environment.NewLine + exception)}";
        File.AppendAllText(_filePath, line + Environment.NewLine);
    }
}

public static class FileLoggerExtensions
{
    public static ILoggingBuilder AddSimpleFile(this ILoggingBuilder builder, string filePath)
    {
        builder.AddProvider(new FileLoggerProvider(filePath));
        return builder;
    }
}

// ---------------* Services ----------------
public class OrderService
{
    private readonly ILogger<OrderService> _logger;
    private readonly IOptionsSnapshot<MySettings> _settings;
    public OrderService(ILogger<OrderService> logger, IOptionsSnapshot<MySettings> settings)
    {
        _logger = logger; _settings = settings;
    }
    public string PlaceOrder(decimal amount)
    {
        using (_logger.BeginScope(new Dictionary<string, object> { ["OrderId"] = Guid.NewGuid() }))
        {
            _logger.LogInformation("Placing order with amount {Amount}", amount);
            return $"OK:MaxItems={_settings.Value.MaxItems}";
        }
    }
}

public interface IWeatherClient
{
    Task<string> GetAsync(string city, CancellationToken ct = default);
}

public class WeatherClient : IWeatherClient
{
    private readonly HttpClient _http;
    public WeatherClient(HttpClient http) => _http = http;
    public Task<string> GetAsync(string city, CancellationToken ct = default)
        => _http.GetStringAsync($"/weather?city={Uri.EscapeDataString(city)}", ct);
}

public class GitHubService
{
    private readonly IHttpClientFactory _factory;
    public GitHubService(IHttpClientFactory factory) => _factory = factory;

    public async Task<string> GetUserAsync(string user, CancellationToken ct = default)
    {
        var client = _factory.CreateClient("github");
        return await client.GetStringAsync($"/users/{user}", ct);
    }
}

public record Product(int Id, string Name);

public class ProductCache
{
    private readonly IMemoryCache _cache;
    private readonly ILogger<ProductCache> _logger;

    public ProductCache(IMemoryCache cache, ILogger<ProductCache> logger)
    {
        _cache = cache; _logger = logger;
    }

    public async Task<Product> GetProductAsync(int id, Func<Task<Product>> factory)
    {
        return await _cache.GetOrCreateAsync($"product:{id}", async entry =>
        {
            entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(5));
            entry.SetSlidingExpiration(TimeSpan.FromMinutes(1));
            entry.RegisterPostEvictionCallback((key, value, reason, state) =>
            {
                _logger.LogInformation("Cache evicted. Key={Key}, Reason={Reason}", key, reason);
            });
            return await factory();
        })!;
    }
}

// ---------------* Program ----------------
var builder = WebApplication.CreateBuilder(args);

// 配置源扩展(演示)
builder.Configuration
    .AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: true)
    .AddInMemoryCollection(new Dictionary<string, string?>
    {
        ["App:AppName"] = "MyShop",
        ["Redis:Primary:ConnectionString"] = "localhost:6379",
        ["Redis:Primary:Database"] = "0"
    });

// 日志:控制台 + 简易文件
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddSimpleFile(Path.Combine(AppContext.BaseDirectory, "app.log"));

// Options:强类型绑定 + 验证
builder.Services
    .AddOptions<MySettings>()
    .Bind(builder.Configuration.GetSection("App"))
    .ValidateDataAnnotations()
    .Validate(o => o.MaxItems >= 5, "MaxItems must be >= 5")
    .ValidateOnStart();
builder.Services.AddSingleton<Microsoft.Extensions.Options.IValidateOptions<MySettings>, MySettingsValidator>();

builder.Services.Configure<RedisOptions>(builder.Configuration.GetSection("Redis:Primary"));
builder.Services.Configure<RedisOptions>("Secondary", builder.Configuration.GetSection("Redis:Secondary"));

// HttpClientFactory:命名客户端与类型化客户端
builder.Services.AddTransient<AddHeaderHandler>();

builder.Services.AddHttpClient("github", client =>
{
    client.BaseAddress = new Uri("https://api.github.com/");
    client.DefaultRequestHeaders.UserAgent.ParseAdd("MyApp/1.0");
    client.Timeout = TimeSpan.FromSeconds(10);
})
.AddHttpMessageHandler<AddHeaderHandler>()
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromMinutes(5),
    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
});

builder.Services.AddHttpClient<IWeatherClient, WeatherClient>(client =>
{
    client.BaseAddress = new Uri("https://weather.example.com");
});

// 内存缓存
builder.Services.AddMemoryCache();

// 业务服务
builder.Services.AddScoped<OrderService>();
builder.Services.AddSingleton<ProductCache>();
builder.Services.AddSingleton<GitHubService>();

var app = builder.Build();

app.MapGet("/", (IConfiguration cfg, IOptions<MySettings> opts) =>
{
    return Results.Ok(new
    {
        Hello = "World",
        AppName = cfg["App:AppName"],
        Options_AppName = opts.Value.AppName
    });
});

app.MapPost("/order", (OrderService svc, [FromBody] decimal amount) =>
{
    var result = svc.PlaceOrder(amount);
    return Results.Ok(result);
});

app.MapGet("/github/{user}", async (GitHubService svc, string user, CancellationToken ct) =>
{
    var json = await svc.GetUserAsync(user, ct);
    return Results.Text(json, "application/json");
});

app.MapGet("/product/{id:int}", async (int id, ProductCache cache) =>
{
    var p = await cache.GetProductAsync(id, async () =>
    {
        await Task.Delay(100); // 模拟 IO
        return new Product(id, $"P-{id}");
    });
    return Results.Ok(p);
});

app.Run();

appsettings.json示例(关键片段):

{
  "App": {
    "AppName": "MyShop",
    "MaxItems": 20,
    "EnableBeta": true
  },
  "Redis": {
    "Primary": {
      "ConnectionString": "localhost:6379",
      "Database": 0
    },
    "Secondary": {
      "ConnectionString": "localhost:6380",
      "Database": 1
    }
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    }
  }
}

9. 最佳实践与常见坑

  • 生命周期选择
    • 配置、日志、HttpClientFactory、内存缓存多为 Singleton。
    • 业务服务大多用 Scoped(Web 请求内共享)。
  • HttpClient 请使用 IHttpClientFactory
    • 避免自己 new HttpClient 的长期单例或频繁创建导致 Socket 耗尽。
    • 使用处理程序管道统一注入鉴权、追踪 ID、重试等横切逻辑。
  • 配置验证尽量在启动时进行
    • 使用 ValidateOnStart,第一时间发现错误(容器启动即失败)。
  • 日志要结构化
    • 使用 {FieldName} 模板,利于后续检索与度量。
  • 缓存键规范与过期策略
    • 使用统一的 key 前缀与过期策略,避免内存膨胀与脏数据。
    • 尽量缓存不可变数据或快速可重建数据,必要时配合 IDistributedCache 做分布式缓存。
  • OptionsSnapshot 与后台线程
    • IOptionsSnapshot 是 Scoped,仅适用于请求上下文;后台服务请使用 IOptionsMonitor。
  • 配置热更新与缓存失效联动
    • 结合 IOptionsMonitor.OnChange 清理相关缓存或重置内部状态。
  • 自定义提供程序注意线程安全和性能
    • 日志、配置源、缓存等自定义实现需充分考虑并发与 IO 锁争用。

10. 延伸阅读(官方文档)

通过以上内容,可以在 ASP .NET Core 中熟练地使用并扩展五大常用内置服务,形成“配置-日志-选项-HTTP-缓存”的稳定基础设施闭环,从而为复杂业务打下坚实的可维护与可观测基石。