依赖注入不仅是支撑整个ASP.NET Core框架的基石,也是开发ASP.NET Core应用采用的基本编程模式,所以依赖注入十分重要。依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中。除了采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对象。

一、将配置绑定为Options对象

Options模式是一种采用依赖注入的方式来提供Options对象的编程方式,但这并不意味着我们会直接利用依赖注入框架来提供Options对象本身,因为利用依赖注入框架获取的是一个能够提供Options对象的IOptions<TOptions>对象,泛型参数TOptions表示的正是Options对象的类型。下面的演示实例利用IOptions<TOptions>服务来提供我们需要的Options对象,该对象由一个承载配置数据的IConfiguration对象绑定而成。简单起见,我们依然沿用《[ASP.NET Core 3框架揭秘] 配置[4]:将配置绑定为对象》定义的Profile作为基础的Options类型,下面先回顾相关类型的定义。

public class Profile : IEquatable<Profile>
{
public Gender Gender { get; set; }
public int Age { get; set; }
public ContactInfo ContactInfo { get; set; }
public Profile() { }
public Profile(Gender gender, int age, string emailAddress, string phoneNo)
{
Gender = gender;
Age = age;
ContactInfo = new ContactInfo
{
EmailAddress = emailAddress,
PhoneNo = phoneNo
};
}
public bool Equals(Profile other)
{
return other == null? false : Gender == other.Gender &&Age == other.Age && ContactInfo.Equals(other.ContactInfo);
}
} public class ContactInfo : IEquatable<ContactInfo>
{
public string EmailAddress { get; set; }
public string PhoneNo { get; set; }
public bool Equals(ContactInfo other)=> other == null ? false : EmailAddress == other.EmailAddress && PhoneNo == other.PhoneNo;
} public enum Gender
{
Male,
Female
}

下面通过一个简单的控制台应用来演示Options编程模式。在演示程序中定义了上面这些类型之后,我们创建承载一个Profile对象的配置文件profile.json。如下所示的代码片段就是这个JSON文件的内容,它提供了构成一个完整Profile对象的所有数据。为了使该文件能够在编译后自动复制到输出目录,我们需要将Copy to Output Directory属性设置为Copy Always。

{
"gender" : "Male",
"age" : "18",
"contactInfo": {
"emailAddress": "foobar@outlook.com",
"phoneNo" : "123456789"
}
}

下面编写代码来演示如何采用Options模式获取由配置文件提供的数据绑定生成的Profile对象。我们调用AddJsonFile扩展方法将针对JSON配置文件(profile.json)的配置源注册到创建的ConfigurationBuilder对象上,并利用它创建对应的IConfigurataion对象。

class Program
{
static void Main()
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("profile.json")
.Build();
var profile = new ServiceCollection()
.AddOptions()
.Configure<Profile>(configuration)
.BuildServiceProvider()
.GetRequiredService<IOptions<Profile>>()
.Value;
Console.WriteLine($"Gender: {profile.Gender}");
Console.WriteLine($"Age: {profile.Age}");
Console.WriteLine($"Email Address: {profile.ContactInfo.EmailAddress}");
Console.WriteLine($"Phone No: {profile.ContactInfo.PhoneNo}");
}
}

上面创建一个ServiceCollection对象,在调用AddOptions扩展方法注册Options编程模式的核心服务后,可以将创建的IConfiguration对象作为参数调用Configure<Profile>扩展方法。Configure<TOptions>扩展方法相当于将提供的IConfiguration对象与指定的TOptions类型做了一个映射,在需要提供对应TOptions对象时,IConfiguration对象承载的配置数据会被提取出来并绑定生成返回的TOptions对象。

在调用IServiceCollection的BuildServiceProvider扩展方法得到作为依赖注入容器的IServiceProvider对象之后,可以直接调用其GetRequiredService<T>扩展方法来提供IOptions<Profile>对象,该对象的Value属性返回的就是指定IConfiguration对象绑定生成的Profile对象。我们将这个Profile对象承载的相关数据直接打印在控制台上,输出结果如下图所示,由此可以看出,通过Options模式得到的Profile对象承载的数据完全来源于配置文件。

二、提供具名的Options

针对同一个Options类型,通过IOptions<TOptions>服务在整个应用范围内只能提供一个单一的Options对象,但是在很多情况下我们需要利用多个同类型的Options对象来承载不同的配置。就演示实例中用来表示个人信息的Profile类型来说,应用程序中可能会使用它来表示不同用户的信息,如张三、李四和王五。为了解决这个问题,我们可以在添加IConfiguration对象与Options类型映射关系时赋予它们一个唯一标识,这个标识最终会被用来提取对应的Options对象。这种具名的Options对象由IOptionsSnapshot<TOptions>接口表示的服务提供。

同样,针对前面的演示实例,假设的应用需要采用Options模式提取承载不同用户信息的Profile对象,具体应该如何实现?由于采用JSON格式的配置文件来提供原始的用户信息,所以需要将针对多个用户的信息定义在profile.json文件中。我们通过如下形式提供了两个用户(foo和bar)的基本信息。

{
"foo": {
"gender": "Male",
"age": "18",
"contactInfo": {
"emailAddress": "foo@outlook.com",
"phoneNo": "123"
}
},
"bar": {
"gender": "Female",
"age": "25",
"contactInfo": {
"emailAddress": "bar@outlook.com",
"phoneNo": "456"
}
}
}

具名Options的注册和提取体现在如下所示的代码片段中。在调用IServiceCollection接口的Configure<TOptions>扩展方法时,我们将注册的映射关系命名为foo和bar,提供原始配置数据的IConfiguration对象也由原来的ConfigurationRoot对象变成它的两个子配置节。

class Program
{
static void Main()
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("profile.json")
.Build(); var serviceProvider = new ServiceCollection()
.AddOptions()
.Configure<Profile>("foo", configuration.GetSection("foo"))
.Configure<Profile>("bar", configuration.GetSection("bar"))
.BuildServiceProvider(); var optionsAccessor = serviceProvider.GetRequiredService<IOptionsSnapshot<Profile>>();
Print(optionsAccessor.Get("foo"));
Print(optionsAccessor.Get("bar")); static void Print(Profile profile)
{
Console.WriteLine($"Gender: {profile.Gender}");
Console.WriteLine($"Age: {profile.Age}");
Console.WriteLine($"Email Address: {profile.ContactInfo.EmailAddress}");
Console.WriteLine($"Phone No: {profile.ContactInfo.PhoneNo}\n");
}
}
}

为了使用指定的用户名来提取对应的Profile对象,可以利用作为依赖注入容器的IServiceProvider对象得到IOptionsSnapshot<TOptions>服务,并将用户名作为参数调用其Get方法得到对应的Profile对象。程序运行后,针对两个不同用户的基本信息将以下图所示的形式输出到控制台上。

三、配置源的同步

通过《配置数据与数据源的实时同步》的介绍可知,配置模型不仅支持对配置源的监控,还可以在检测到更新之后及时加载新的配置数据,并通过一个IChangeToken对象对外发送通知。对于前面演示的两个实例来说,提供的Options对象都是由配置文件提供的数据绑定生成的,如果新的配置数据被重新加载之后能够提供与之匹配的Options对象,那么这将是最理想的编程模式,可以通过IOptionsMonitor<TOptions>服务来实现。

前面演示的第一个实例利用JSON文件定义了一个单一Profile对象的信息,下面对它做相应的修改来演示如何监控这个JSON文件,并在监测到文件改变之后及时提取新的配置信息生成新的Profile对象。如下面的代码片段所示,调用AddJsonFile扩展方法注册对应配置源时应将该方法的参数reloadOnChange设置为True,从而开启对对应配置文件的监控功能。

class Program
{
static void Main()
{
var configuration = new ConfigurationBuilder()
.AddJsonFile(path: "profile.json", optional: false, reloadOnChange: true)
.Build(); new ServiceCollection()
.AddOptions()
.Configure<Profile>(configuration)
.BuildServiceProvider()
.GetRequiredService<IOptionsMonitor<Profile>>()
.OnChange(profile =>
{
Console.WriteLine($"Gender: {profile.Gender}");
Console.WriteLine($"Age: {profile.Age}");
Console.WriteLine($"Email Address: {profile.ContactInfo.EmailAddress}");
Console.WriteLine($"Phone No: {profile.ContactInfo.PhoneNo}\n");
});
Console.Read();
}
}

在得到作为依赖注入容器的IServiceProvider对象之后,可以利用它得到IOptionsMonitor<TOptions>服务,该对象会接收到配置系统发出的关于配置被重新加载的通知,并在收到通知后重新生成Options对象。我们调用IOptionsMonitor<TOptions>对象的OnChange方法注册了一个类型为Action<TOptions>的委托对象,该委托对象会在接收到Options变化时自动执行,而作为输入的正是重新生成的Options对象。由于注册的委托对象会将新Profile对象的相关属性打印在控制台上,所以程序启动后针对配置文件的任何修改都会导致新的数据被打印在控制台上。例如,我们先后修改了年龄(25)和性别(Female),新的数据将按照下图所示的形式反映在控制台上。

具名Options同样可以采用类似的编程模式来实现配置的同步。在前面演示的提供具名Options的第二个实例的基础上,我们对程序做了如下修改。与之前不同的是,在利用IServiceProvider对象得到IOptionsMonitor<TOptions>服务之后,可以调用其OnChange方法注册的回调是一个Action<TOptions, String>对象,该委托对象的第二个参数表示的正是在注册IConfiguration对象与Options类型应用关系时指定的名称。

class Program
{
static void Main()
{
var configuration = new ConfigurationBuilder()
.AddJsonFile(path: "profile.json", optional: false, reloadOnChange: true)
.Build();
new ServiceCollection()
.AddOptions()
.Configure<Profile>("foo", configuration.GetSection("foo"))
.Configure<Profile>("bar", configuration.GetSection("bar"))
.BuildServiceProvider()
.GetRequiredService<IOptionsMonitor<Profile>>()
.OnChange((profile, name) =>
{
Console.WriteLine($"Name: {name}");
Console.WriteLine($"Gender: {profile.Gender}");
Console.WriteLine($"Age: {profile.Age}");
Console.WriteLine($"Email Address: {profile.ContactInfo.EmailAddress}");
Console.WriteLine($"Phone No: {profile.ContactInfo.PhoneNo}\n");
});
Console.Read();
}
}

由于通过调用OnChange方法注册的委托对象会将Options的名称和承载的数据打印在控制台上,所以控制台上输出的内容总是与配置文件的内容同步。例如,在程序启动后,我们分别修改了用户foo的年龄(25)和用户bar的性别(Male),新的内容将以图7-4所示的形式及时呈现在控制台上。

[ASP.NET Core 3框架揭秘] Options[1]: 配置选项的正确使用方式[上篇]
[ASP.NET Core 3框架揭秘] Options[2]: 配置选项的正确使用方式[下篇]
[ASP.NET Core 3框架揭秘] Options[3]: Options模型[上篇]
[ASP.NET Core 3框架揭秘] Options[4]: Options模型[下篇]
[ASP.NET Core 3框架揭秘] Options[5]: 依赖注入
[ASP.NET Core 3框架揭秘] Options[6]: 扩展与定制
[ASP.NET Core 3框架揭秘] Options[7]: 与配置系统的整合

[ASP.NET Core 3框架揭秘] Options[1]: 配置选项的正确使用方式[上篇]的更多相关文章

  1. [ASP.NET Core 3框架揭秘] Options[2]: 配置选项的正确使用方式[下篇]

    四.直接初始化Options对象 前面演示的几个实例具有一个共同的特征,即都采用配置系统来提供绑定Options对象的原始数据,实际上,Options框架具有一个完全独立的模型,可以称为Options ...

  2. 《ASP.NET Core 3框架揭秘》博文汇总

    在过去一段时间内,写了一系列关于ASP.NET Core 3相关的文章,其中绝大部分来源于即将出版的<ASP.NET Core 3框架揭秘>(博文只能算是"初稿",与书 ...

  3. ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式

    依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...

  4. [ASP.NET Core 3框架揭秘] 依赖注入[5]: 利用容器提供服务

    毫不夸张地说,整个ASP.NET Core框架是建立在依赖注入框架之上的.ASP.NET Core应用在启动时构建管道以及利用该管道处理每个请求过程中使用到的服务对象均来源于依赖注入容器.该依赖注入容 ...

  5. [ASP.NET Core 3框架揭秘] 文件系统[1]:抽象的“文件系统”

    ASP.NET Core应用 具有很多读取文件的场景,比如配置文件.静态Web资源文件(比如CSS.JavaScript和图片文件等)以及MVC应用的View文件,甚至是直接编译到程序集中的内嵌资源文 ...

  6. [ASP.NET Core 3框架揭秘] 配置[1]:读取配置数据[上篇]

    提到"配置"二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化 ...

  7. [ASP.NET Core 3框架揭秘] 配置[2]:读取配置数据[下篇]

    [接上篇]提到“配置”二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义 ...

  8. [ASP.NET Core 3框架揭秘] 配置[6]:多样化的配置源[上篇]

    .NET Core采用的这个全新的配置模型的一个主要的特点就是对多种不同配置源的支持.我们可以将内存变量.命令行参数.环境变量和物理文件作为原始配置数据的来源.如果采用物理文件作为配置源,我们可以选择 ...

  9. [ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配

    .NET Core具有一个承载(Hosting)系统,承载需要在后台长时间运行的服务,一个ASP.NET Core应用仅仅是该系统承载的一种服务而已.承载系统总是采用依赖注入的方式来消费它在服务承载过 ...

随机推荐

  1. get_magic_quotes_gpc() PHP转义的真正含义

    如何正确的理解PHP转 义是一个初学者比较困扰的问题.我们今天为大家简要的讲述了PHP转义的具体含义,希望有所帮助.PHP转义一直困扰着我, 今天认真的看了一下PHP手册, 终于解决了. 在PHP中默 ...

  2. 洛谷P1280 尼克的任务 题解 动态规划/最短路

    作者:zifeiy 标签:动态规划.最短路 题目链接:https://www.luogu.org/problem/P1280 题目大意: 有k个任务分布在第1至n这n个时间点,第i个任务的于第 \(P ...

  3. Python--day41--线程队列

    1,普通队列:queue.Queue(),先进先出 import queue q = queue.Queue() #队列 先进先出 q.put(1) q.put(2) q.put(3) q.put(4 ...

  4. CountableThreadPool

    Spider剩下的CountableThreadPool 在上一篇的Spider中我们一定注意到了threadpool这个变量,这个变量是Spider中的线程池,具体代码 public class C ...

  5. React---钩子函数

    钩子函数的状态有4个阶段: <p>1.初始化阶段  (componentWillMount() || componentDidMount()) </p>             ...

  6. P1072 城市轰炸

    题目描述 一个大小为N*M的城市遭到了X次轰炸,每次都炸了一个每条边都与边界平行的矩形. 在轰炸后,有Y个关键点,指挥官想知道,它们有没有受到过轰炸,如果有,被炸了几次,最后一次是第几轮. 输入格式 ...

  7. 关于CPython中set集合的无序研究

    set集合本身是无序的,但是无意间发现set集合中都是数字时set貌似有序了. 无论声明这个set时数字如何摆放,输出结果总是以一种固定的顺序!同样我将dict字典的key值设为int类型,这时候字典 ...

  8. 1119 机器人走方格 V2 (组合数学)

    M * N的方格,一个机器人从左上走到右下,只能向右或向下走.有多少种不同的走法?由于方法数量可能很大,只需要输出Mod 10^9 + 7的结果.   Input 第1行,2个数M,N,中间用空格隔开 ...

  9. Spring Security 学习笔记-securityContext过滤器

    位于过滤器顶端,第一个起作用的过滤器.SecurityContextPersistenceFilter 在执行其他过滤器之前,率先判断用户的session中是否已经存在一个SecurityContex ...

  10. es6笔记 day3---对象简介语法以及对象新增

    以前的老写法↓ 新写法来了↓ 提示:千万不要手贱,在里面去用箭头函数!!! -------------------------------------------------------------- ...