首页 > 编程语言 >04-《AspNetCore》-Options

04-《AspNetCore》-Options

时间:2023-02-14 14:57:55浏览次数:54  
标签:选项 Configure 04 Url AspNetCore public var services Options

Options

视频讲解

package说明

ASP.NET Core 中的选项模式 | Microsoft Learn

Microsoft.Extensions.Options:选项的核心包,扩展IServiceCollection接口,只支持内存配置。

Microsoft.Extensions.Options.ConfigurationExtensions:配置文件的扩展,支持IConfiguration进行配置。

Microsoft.Extensions.DependencyInjection:选项必须配合容器使用

Microsoft.Extensions.Options.DataAnnotations:支持数据注解验证

IOptions:用于从容器解析选项。Singleton级别,只会加载一次配置。

IOptionsSnapshot:在每次请求时应重新计算选项的方案中有用,scope级别。支持命名配置

IOptionsMonitor:监听并通知更改,Singleton级别。支持命名配置

IOptionsFactory:管理Options实列,包括验证,配置的委托瞬时的。(工厂模式)

IConfigureOptions:配置选项的委托

IPostConfigureOptions:配置选项的委托,在IConfigureOptions委托执行之后执行

IOptionsChangeTokenSource:用于监听IConfiguration的更改通知

IValidateOptions:配置选项之后的验证

源码解读

注意:选项必须提供无参构造器,具体阅读OptionsFactory的源码底层采用的是反射。选项是.NET中的一个非常重要的概念,通过这节课程大家需要掌握如何灵活更改框架的选项。对于一些需要编写框架的同学也有很大的帮助。但凡以Options结尾的一般都是选项模式。

public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions)
where TOptions : class
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

if (configureOptions == null)
{
throw new ArgumentNullException(nameof(configureOptions));
}

services.AddOptions();
services.AddSingleton<IConfigureOptions<TOptions>>(new ConfigureNamedOptions<TOptions>(name, configureOptions));
return services;
}

//注册Options核心组件
public static IServiceCollection AddOptions(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(UnnamedOptionsManager<>)));
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
return services;
}

注册过程简述:

1.调用Configure方法会将配置选项的委托IConfigureOptions(不是选项本身)添加到容器,同一个选项可以调用多个Configure,注册多个IConfigureOptions实列。

2.调用PostConfigure方法会将配置选项的委托IPostConfigureOptions(不是选项本身)添加到容器,同一个选项可以调用多个PostConfigure,注册多个IPostConfigureOptions实列。和Configure类型都是用于保存配置选项的委托。

3.IConfigureNamedOptions接口继承了IConfigureOptions接口,而ConfigureNamedOptions实现了IConfigureNamedOptions接口。用于保存配置选项的委托和名字。

3.调用Configure方法,会调用AddOptions,AddOptions注册了IOptions、IOptionsSnapshot、IOptionsMonitor、IOptionsFactory、IOptionsMonitorCache到容器里。

4.IOptionsFactory,负责管理,创建,配置、验证选项。源码可以知道IOptionsFactory依赖注入了所有的(同一个选项)的IConfigureOptions和IPostConfigureOptions的委托,先按顺序(注册的顺序)执行所有的Configure委托,然后在按顺序执行所有的PostConfigure委托。因此PostConfigure一定在所有的Configure之后执行。然后在执行所有的验证器。IOptionsFactory是通过反射创建选项的,因此选项不支持依赖注入,必须提供无参数构造器。

5.IOptions接口的实现类是单实列的。通过依赖IOptionsFactory完成选项的管理。不支持命名选项,不支持配置更改计算(单实例)。

6.IOptionsSnapshot是Scope级别的但是有缓存策略,通过依赖IOptionsFactory完成选项的管理。缓存由于是实列的,而实列的生命周期是scope级别,因此缓存也是scope级别。不要通过根容器解析scope级别的实列,因为不会根容器释放。支持命名选项,支持配置的更改计算,每次都会从配置文件读取并绑定到选项。

7.IOptionsMonitor是单实例的,通过依赖IOptionsFactory完成选项的管理。支持命名选项,支持配置更改计算,有缓存机制,可以注册选项更改通知回调。

public class MvcOptions
{
public string Uri { get; }
}
static void TestSort()
{
var services = new ServiceCollection();
//name为Empty
services.Configure<MvcOptions>(a =>
{
Console.WriteLine(11);
a.Url = "132";
});
services.PostConfigure<MvcOptions>(a =>
{
Console.WriteLine(33);
a.Url = "132";
});
services.Configure<MvcOptions>(a =>
{
Console.WriteLine(22);
a.Url = "132";
});
services.PostConfigure<MvcOptions>(a =>
{
Console.WriteLine(44);
a.Url = "132";
});
var sp = services.BuildServiceProvider();
var optionsFactory = sp.GetRequiredService<IOptionsFactory<MvcOptions>>();
var options = optionsFactory.Create(Options.DefaultName);
}

核心方法

注意:Configure和AddOptions都不是直接把选择注入到容器,而是把选项的配置委托注入到容器。因此你可以执行无数多个Configre和AddOptions。

//1.通过委托来配置选项,同一个选项可以按顺序执行多个Configure。
services.Configure<MvcOptions>(a =>
{
a.Url = "132";
});
//2.在所有的Configure执行之后配置选项。同一个选项可以按顺序执行多个PostConfigure。
services.PostConfigure<MvcOptions>(a =>
{
a.Url = "132";
});
//AddOptions返回一个OptionsBuilder,可以连续配置选项,本质还是执行Configure和PostConfigure
services.AddOptions<MvcOptions>()
.Bind(configuration.GetSection("MvcOptions"))
.Configure(a=>a.Url="123");

解析选项

var services = new ServiceCollection();
services.Configure<MvcOptions>(a =>
{
a.Url = "132";
});
var sp = services.BuildServiceProvider();
var optionsFactory = sp.GetRequiredService<IOptionsFactory<MvcOptions>>();
var options = sp.GetRequiredService<IOptions<MvcOptions>>();
var optionsMonitor = sp.GetRequiredService<IOptionsMonitor<MvcOptions>>();
var optionsSnapshot = sp.GetRequiredService<IOptionsSnapshot<MvcOptions>>();

注入选项

吧Options直接注入到容器,可以通过IOptions、IOptionsSnapshot、IOptionsMonitor、IOptionsFactory

static void TestDiOptions()
{
var services = new ServiceCollection();
services.Configure<MvcOptions>(a =>
{
Console.WriteLine(22);
a.Url = "132";
});
services.AddSingleton(sp =>
{
return sp.GetRequiredService<IOptions<MvcOptions>>().Value;
});
//必须使用Scoped,因为IOptionsSnapshot是Scoped的,如果是单实列的话,就丢失了自动更改的性质了
//services.AddScoped(sp =>
//{
// return sp.GetRequiredService<IOptionsSnapshot<MvcOptions>>().Value;
//});
var sp = services.BuildServiceProvider();
var options = sp.GetRequiredService<MvcOptions>();
}

监听更改

{
"MvcOptions": {
"Url": "123"
},
"WebOptions": {
"Url": 456
}
}
public class MvcOptions
{
public string? Url { get; set; }
}

static void OnChange()
{
var services = new ServiceCollection();
var configuration = GetConfiguration();
services.Configure<MvcOptions>(configuration.GetSection("MvcOptions"));
var container = services.BuildServiceProvider();
while (true)
{
Thread.Sleep(1000);
using (var scope = container.CreateScope())
{
var o1 = scope.ServiceProvider.GetRequiredService<IOptions<MvcOptions>>();
var o2 = scope.ServiceProvider.GetRequiredService<IOptionsSnapshot<MvcOptions>>();
var o3 = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<MvcOptions>>();
Console.WriteLine("==================");
Console.WriteLine($"IOptions:{o1.Value.Url}");
Console.WriteLine($"IOptionsSnapshot:{o2.Value.Url}");
Console.WriteLine($"IOptionsMonitor:{o3.CurrentValue.Url}");
o3.OnChange(o =>
{
Console.WriteLine("选项发生更改了");
});
}
}
}

static IConfiguration GetConfiguration()
{
var configuration = new ConfigurationManager();
configuration.SetBasePath(Directory.GetCurrentDirectory());
configuration.AddJsonFile("config.json", false, true);
return configuration;
}

命名选项

{
"MvcOptions": {
"Url": "123"
},
"WebOptions": {
"Url": 456
}
}
public class MvcOptions
{
public string? Url { get; set; }
}

static void TestNamed()
{
var services = new ServiceCollection();
var configuration = GetConfiguration();
//name=Options.DefaultName
services.Configure<MvcOptions>(configuration.GetSection("MvcOptions"));
//name="o1"
services.Configure<MvcOptions>("o1", configuration.GetSection("MvcOptions"));
//name="o2"
services.Configure<MvcOptions>("o2", configuration.GetSection("WebOptions"));
var container = services.BuildServiceProvider();
var o1 = container.GetRequiredService<IOptionsSnapshot<MvcOptions>>();
var o2 = container.GetRequiredService<IOptionsMonitor<MvcOptions>>();
//name="o1"
Console.WriteLine("IOptionsSnapshot:Named:" + o1.Get("o1").Url);
//name=Options.DefaultName
Console.WriteLine("IOptionsSnapshot:Value:" + o1.Value.Url);
//name="o2"
Console.WriteLine("IOptionsMonitor:Named:" + o2.Get("o2").Url);
//name=Options.DefaultName
Console.WriteLine("IOptionsMonitor:Value:" + o2.CurrentValue.Url);
var optionsFactory = sp.GetRequiredService<IOptionsFactory<MvcOptions>>();
var options = optionsFactory.Create(Options.DefaultName);
}

只有IOptionsFactory、IOptionsSnapshot、IOptionsMonitor支持命名配置

接口配置

//通过该源码发现,我们可以通过实现IPostConfigureOptions接口,并注入到容器来进行配置选项
public static IServiceCollection PostConfigure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions)
where TOptions : class
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

if (configureOptions == null)
{
throw new ArgumentNullException(nameof(configureOptions));
}

services.AddOptions();
services.AddSingleton<IPostConfigureOptions<TOptions>>(new PostConfigureOptions<TOptions>(name, configureOptions));
return services;
}
public class MvcOptions
{
public string? Url { get; set; }
}
//你也可以实现IConfigureOptions但是IConfigureOptions的执行顺序优先级比较低(要学会举一反三)
internal class MvcOptionsPostConfigureOptions : IPostConfigureOptions<MvcOptions>
{
public void PostConfigure(string name, MvcOptions options)
{
options.Url = "789";
//可以编写验证逻辑
//if (options.Url == "123")
//{
// throw new InvalidDataException("Url不能等于123");
//}
}
}
public static void TestIPostConfigureOptions()
{
var services = new ServiceCollection();
services.Configure<MvcOptions>(s => s.Url = "111");
services.AddSingleton<IPostConfigureOptions<MvcOptions>, MvcOptionsPostConfigureOptions>();
var container = services.BuildServiceProvider();
var o2 = container.GetRequiredService<IOptions<MvcOptions>>();
}

服务配置

AddOptions会返回可以OptionsBuilder,OptionsBuilder中支持DI的方式来配置选项,最多支持5个。

internal class MvcOptionsDep
{
public void Configure(MvcOptions options)
{
options.Url = "6666";
}
}
public static void TestDep()
{
var services = new ServiceCollection();
//注册di服务
services.AddSingleton<MvcOptionsDep>();
services.AddOptions<MvcOptions>()//注册选项
.Configure(a=>a.Url="123")//第一个配置
.PostConfigure<MvcOptionsDep>((options, dep) => //后续配置
{
dep.Configure(options);//调用MvcOptionsDep中的方法来进行配置
});
var container = services.BuildServiceProvider();
var o2 = container.GetRequiredService<IOptions<MvcOptions>>();
}

验证选项

注意验证是一定是在Configure和PostConfigure之后执行的

1.委托验证

public static void TestValidateDelegate()
{
try
{
var services = new ServiceCollection();
//不是把选项注入到容器,只是注入了该选项的委托
services.Configure<MvcOptions>(a => a.Url = "123");
//得到OptionsBuilder,进行验证,本质还是执行Configure(可以执行无数多个Configre)
services.AddOptions<MvcOptions>()
.Validate(options =>
{
if (options.Url == null)
{
return false;
}
if (!options.Url.StartsWith("aa"))
{
return false;
}
return true;
}, "必须以aa开头");
var container = services.BuildServiceProvider();
var options = container.GetRequiredService<IOptions<MvcOptions>>();
Console.WriteLine(options.Value.Url);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

2.注解验证

需要安装Microsoft.Extensions.Options.DataAnnotations包

public class MvcOptions
{
[RegularExpression(@"^aa",ErrorMessage ="必须以aa开头")]
public string? Url { get; set; }
}

public static void TestValidateDataAnnotations()
{
try
{
var services = new ServiceCollection();
//不是把选项注入到容器,只是注入了该选项的委托
services.Configure<MvcOptions>(a => a.Url = "123");
//得到OptionsBuilder,进行验证,本质还是执行Configure(可以执行无数多个Configre)
services.AddOptions<MvcOptions>()
.ValidateDataAnnotations();
var container = services.BuildServiceProvider();
var options = container.GetRequiredService<IOptions<MvcOptions>>();
Console.WriteLine(options.Value.Url);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

标签:选项,Configure,04,Url,AspNetCore,public,var,services,Options
From: https://www.cnblogs.com/chaeyeon/p/17119558.html

相关文章

  • 02-《AspNetCore》-配置
    Configurationpackage说明Microsoft.Extensions.Configuration.Abstractions:抽象包,一大堆的接口Microsoft.Extensions.Configuration.Binder:提供一大堆的扩展,比如类型转......
  • 3604、猜数字大小
    猜数字游戏的规则如下:每轮游戏,我都会从1到n随机选择一个数字。请你猜选出的是哪个数字。如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。你可以通......
  • VMware 虚拟机安装 Ubuntu 20.04-server
    一、ubuntu镜像下载选择服务器版:https://cn.ubuntu.com/download/server/step1 二、安装步骤1、选择语言:选择语言为English 2、系统更新:选择不更新 3、设置键......
  • Navicat远程连接linux下mysql服务器1045错误解决办法在这儿
    1:首先通过xshell工具或者你熟悉的工具连接远程linux下的服务器mysql-uroot-p   然后输入密码 2.进行授权如果想root用户使用password从任何主机连接到mysql服务器......
  • 104、工单,是否可用?怎么回答
    一、问题crm工单,是否可用(潜在信息,crm工单具体指在“新dts”系统)二、答案1、80分答案可用。有一定的局限性:crm的工单条数,在老dts和新dts都有效,存在2份。后续会优化。后续可......
  • 04 如何进行数据表表分区? | OushuDB 数据库使用入门
    表分区在数据库日渐庞大的今天,为了方便对数据库数据的管理,比如按时间、地区去统计一些数据时,基数过于庞大带来了诸多不便。很多商业数据库都提供分区的概念,按不同的维度去存......
  • python入门学习笔记004--趣学Python算法--第4例百钱百鸡
    中国古代数学家张丘建在他的《算经》中提出了一个著名的“百钱百鸡问题”:一只公鸡值五钱,一只母鸡值三钱,三只小鸡值一钱,现在要用百钱买百鸡,请问公鸡、母鸡、小鸡各多少只? ......
  • 个人文章展示(包括404错误页面提醒)
    效果1:输入错误站点提示404   输入错误站点提示404前端(根据后端创建errors.html填充一下代码):<!DOCTYPEhtml><html><head><metacharset='utf-8'><linkrel......
  • HTML_04_Table
    Table1.Basictablestructure<table></table>​ createatable<tr></tr>​ indicatethestartofeachrow<td></td>​ eachcellinaroll<table><t......
  • GitLab CICD Day 04 - 新增 Pipeline Job
    编写.gitlab-ci.ymlhelloworld:#Jobtags:-shell#Gitlab-runnerbefore_script:-echo"脚本执行前的任务"scrip......