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;
}
复制代码

 

posted @   tuqunfu  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
历史上的今天:
2019-01-13 数据结构-有序单链表的集合运算
点击右上角即可分享
微信分享提示