ASP.NET Core之Options
一、前言
在ASP.NET Core中注入服务的时候会通过委托的方式给服务设置很多配置项,比如日志服务、缓存服务、授权认证服务等。该配置项在ASP.NET Core中定义为选项(Options),如下Action<AuthenticationOptions> configureOptions、Action<MemoryDistributedCacheOptions> setupAction、services.AddOptions(),通过委托或者服务注入方式提供配置信息。
在官方文档中选项模式(Options)使用类来提供对相关设置组的强类型访问,Options 是一种配置管理机制,它允许将应用程序的配置信息从代码中分离出来,以提高代码的可维护性和可测试性。通过使用 Options,我们可以将配置信息封装到一个或多个 Options 类中,并通过依赖注入将其注入到需要使用配置的组件中。遵循两个重要软件工程原则:
①封装:依赖于配置设置的类仅依赖于其使用的配置设置;②分离关注点:应用的不同部件的设置不彼此依赖或相互耦合,选项还提供验证配置数据的机制。
与配置文件相比,Options 提供了一种统一的方式来访问配置信息,而不需要直接访问配置文件或配置提供程序。它还支持配置的热更新,即在应用程序运行时修改配置后,可以自动应用新的配置值,而无需重新启动应用程序。选项(Options)模式是对配置(Configuration)的功能的延伸。
// 缓存 builder.Services.AddDistributedMemoryCache(option => { option.SizeLimit = 1000; option.TrackStatistics = true; }); public static IServiceCollection AddDistributedMemoryCache(this IServiceCollection services, Action<MemoryDistributedCacheOptions> setupAction) { ThrowHelper.ThrowIfNull(services); ThrowHelper.ThrowIfNull(setupAction); services.AddDistributedMemoryCache(); services.Configure(setupAction); return services; } // 日志 builder.Services.AddLogging(option => { option.SetMinimumLevel(LogLevel.Debug); }); public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure) { ThrowHelper.ThrowIfNull(services); services.AddOptions(); services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>()); services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>))); services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<LoggerFilterOptions>>( new DefaultLoggerLevelConfigureOptions(LogLevel.Information))); configure(new LoggingBuilder(services)); return services; } // 授权 builder.Services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(option => { option.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(builder.Configuration["Jwt:Key"])), ValidateIssuer = false, ValidateAudience = false, }; }); public static AuthenticationBuilder AddAuthentication(this IServiceCollection services, Action<AuthenticationOptions> configureOptions) { ArgumentNullException.ThrowIfNull(services); ArgumentNullException.ThrowIfNull(configureOptions); var builder = services.AddAuthentication(); services.Configure(configureOptions); return builder; }
二、Options
1、定义Options类,选项是使用类来提供对相关设置组的强类型访问,通过定义选项类来表示一组选项配置。Options是普通类型,包含属性对应配置的键值对信息。
namespace tqf.optionsDemo.Options { public class OptionDemo { public string? Key1; public string? Key2; } }
2、注册Options,上述完成定义,在使用选项类之前要将Options 类注册到依赖注入容器中,可以通过①读取appsetting.json文件的配置项设置选项;②通过委托来配置选项,同一个选项可以按顺序执行多个Configure;③在所有的Configure执行之后配置选项。同一个选项可以按顺序执行多个PostConfigure;④AddOptions支持链式调用。本质还是执行Configure和PostConfigure。
using Microsoft.Extensions.Options; using Microsoft.Extensions.DependencyInjection; using tqf.optionsDemo.Options; namespace tqf.optionsDemo { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); // Add services to the container. var startService = builder.Services.BuildServiceProvider(); var log = startService.GetRequiredService<ILogger<Program>>(); log.LogInformation("测试Log是否注入"); // 在appsetting.json中配置,读取配置信息 builder.Services.Configure<OptionDemo>(builder.Configuration.GetSection("OptionDemo")); builder.Services.Configure<OptionDemo>(option => { option.Key1 = "value1"; }); builder.Services.PostConfigure<OptionDemo>(option => { option.Key2 = "value2"; }); builder.Services.AddOptions<OptionDemo>().Configure(op => { op.Key1 = "value1"; }).PostConfigure(op => { op.Key2 = "value2"; }); builder.Services.ConfigureAll<OptionDemo>(option => { option.Key1 = "value1"; option.Key2 = "value2"; }); var sp = builder.Services.BuildServiceProvider(); var factoryOption = sp.GetRequiredService<IOptionsFactory<OptionDemo>>(); var option = sp.GetRequiredService<IOptions<OptionDemo>>(); var monitorOption = sp.GetRequiredService<IOptionsMonitor<OptionDemo>>(); var snapshotOption = sp.GetRequiredService<IOptionsSnapshot<OptionDemo>>(); var app = builder.Build(); // Configure the HTTP request pipeline. app.UseAuthorization(); app.MapControllers(); app.Run(); } } }
3、使用Options,上述完成多种方式注入OptionDemo的选项类,在需要使用配置的组件时,通过依赖注入将Options注入(属性、构建函数、或者GetRequiredService获取),并直接访问 Options 类的属性来获取配置值。
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; using tqf.optionsDemo.Options; namespace tqf.optionsDemo.Controllers { [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; private readonly ILogger<WeatherForecastController> _logger; private readonly IOptions<OptionDemo> _options; private readonly IConfiguration _configuration; private readonly IDistributedCache _distributedCache; public WeatherForecastController(ILogger<WeatherForecastController> logger, IOptions<OptionDemo> options, IConfiguration configuration,IDistributedCache distributedCache) { _logger = logger; _options = options; _configuration = configuration; _distributedCache = distributedCache; } [HttpGet] public IEnumerable<WeatherForecast> Get() { _distributedCache.SetStringAsync("key", "value"); var option = _options; var value = _options.Value.Key1; Console.WriteLine(value); var host = _configuration.GetValue<string>("AllowedHosts"); Console.WriteLine(host); var key = _distributedCache.GetStringAsync("key"); Console.WriteLine(key.Result); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); } } }
4、Options接口,ASP.NET Core提供三个主要的Options接口IOptions、IOptionsSnapshot 和 IOptionsMonitor,应用于不同的场景提供了不同获取配置值。通过在控制器注入三种不同接口,获取配置值,检测其配置文件值更新时的变化情况。
①IOptions:在每次调用时返回相同的配置值,适用于获取配置值后不会发生变化的场景。
②IOptionsSnapshot:在每次调用时返回最新的配置值,适用于获取配置值可能会发生变化的场景。
③IOptionsMonitor:实时监控配置值的变化,并在配置值发生变化时提供新的配置值,适用于需要实时响应配置变化的场景。
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; using tqf.optionsDemo.Options; namespace tqf.optionsDemo.Controllers { [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; private readonly ILogger<WeatherForecastController> _logger; private readonly IConfiguration _configuration; private readonly IDistributedCache _distributedCache; private readonly IOptions<OptionDemo> _options; private readonly IOptionsMonitor<OptionDemo> _optionsMonitor; private readonly IOptionsSnapshot<OptionDemo> _optionsSnapshot; public WeatherForecastController(ILogger<WeatherForecastController> logger, IOptions<OptionDemo> options, IOptionsMonitor<OptionDemo> optionsMonitor, IOptionsSnapshot<OptionDemo> optionsSnapshot, IConfiguration configuration, IDistributedCache distributedCache) { _logger = logger; _options = options; _optionsMonitor = optionsMonitor; _optionsSnapshot = optionsSnapshot; _configuration = configuration; _distributedCache = distributedCache; } [HttpGet] public IEnumerable<WeatherForecast> Get() { Console.WriteLine(_options.Value.Key1); Console.WriteLine(_optionsMonitor.CurrentValue.Key1); Console.WriteLine(_optionsSnapshot.Value.Key1); /* _distributedCache.SetStringAsync("key", "value"); var host = _configuration.GetValue<string>("AllowedHosts"); Console.WriteLine(host); var key = _distributedCache.GetStringAsync("key"); Console.WriteLine(key.Result); */ return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); } } }
5、其它接口IConfigureNamedOptions、OptionsBuilder 和 IValidateOptions。
①命名选项(IConfigureNamedOptions)用于配置特定名称的 Options 对象,通过特点名称获取配置项信息。使用场景比如相同的配置项类在不同类型中使用,则使用命名选项的名称区分。所有选项都是命名实例。 IConfigureOptions<TOptions> 实例将被视为面向 Options.DefaultName
实例,即 string.Empty
。 IConfigureNamedOptions<TOptions> 还可实现 IConfigureOptions<TOptions>。 IOptionsFactory<TOptions> 的默认实现具有适当地使用每个实例的逻辑。 null
命名选项用于面向所有命名实例,而不是某一特定命名实例。 ConfigureAll 和 PostConfigureAll 使用此约定。
// 配置命名选项,名称OptionDemo builder.Services.Configure<OptionDemo>("OptionDemo", builder.Configuration.GetSection("OptionDemo")); // 获取命名选项 Console.WriteLine(_optionsSnapshot.Get("OptionDemo").Key1);
②OptionsBuilder:用于配置 Options 对象。可以通过调用 Configure 方法来为 Options 类进行配置。用于配置 TOptions
实例。 OptionsBuilder
简化了创建命名选项的过程,因为它只是初始 AddOptions<TOptions>(string optionsName)
调用的单个参数,而不会出现在所有后续调用中。
builder.Services.AddOptions<OptionDemo>().Configure(op => { op.Key1 = "value1"; }).PostConfigure(op => { op.Key2 = "value2"; });
③IValidateOptions用于验证 Options 对象的配置。可以通过实现该接口来对 Options 进行验证,应用于复杂的验证场景。
// 定义接口 using Microsoft.Extensions.Options; namespace tqf.optionsDemo.Options { public class OptionDemoValidate : IValidateOptions<OptionDemo> { public ValidateOptionsResult Validate(string? name, OptionDemo options) { if (string.IsNullOrEmpty(options.Key1)) { return ValidateOptionsResult.Fail("Key1不能为空"); } return ValidateOptionsResult.Success; } } } // 注入选项配置、选项配置的验证接口 builder.Services.Configure<OptionDemo>("validate",option => { option.Key1 = ""; }); builder.Services.AddSingleton<IValidateOptions <OptionDemo>, OptionDemoValidate>(); // 获取 Console.WriteLine(_optionsSnapshot.Get("validate").Key1);
三、总结
通过使用 Options,我们可以将配置信息从代码中分离出来,提高代码的可维护性和可测试性,同时还能实现配置的热更新和实时响应配置变化。在学习选项模式时对服务注入深入认识①基础的Log服务、权限验证服务已经默认注册,通过查看源代码如builder的构建、builder.Services.AddControllers()=>AddControllersCore,所以在Program中无须在注册,在使用时通过属性或者构造函数注入就可以使用了。②没有注册服务,在注入服务会提示错误信息,或者不生效,如builder.Services.AddSingleton<IValidateOptions<OptionDemo>, OptionDemoValidate>();没有注册验证,则验证不生效。③通过学习这个选项模式,ASP.NET Core框架已经很优雅、完整、强大提供了基础功能,直接注册服务、启用中间件、依赖注入直接使用,配置各种选项就OK。
private static IMvcCoreBuilder AddControllersCore(IServiceCollection services) { // This method excludes all of the view-related services by default. var builder = services .AddMvcCore() .AddApiExplorer() .AddAuthorization() .AddCors() .AddDataAnnotations() .AddFormatterMappings(); if (MetadataUpdater.IsSupported) { services.TryAddEnumerable( ServiceDescriptor.Singleton<IActionDescriptorChangeProvider, HotReloadService>()); } return builder; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
2019-01-13 数据结构-有序单链表的集合运算