前言

之前就写过 Asp.net core 学习笔记 ( Configuration 配置 ) 只是有点乱, 这篇作为整理版.

项目中会有许许多多的 Config 要设定. 比较好的管理方式是把它们放到 json file 里. 这样想修改时就不需要改动源码, 改 json file 就行了.

ASP.NET Core 提供了一套管理 Config 的方式. 这篇主要就是介绍这个.

参考:

docs – Configuration in ASP.NET Core

appsetting.json

appsetting.json 就是 ASP.NET Core 的 config file, 我们可以把所有模块用到的 config 都写在里面.

真实情况大概长这样

For 下面的测试, 我做一个简单的就好了

{
"MyConfig": {
"Child1": {
"Key": "Value",
"Secret": "secret"
},
"Child2": {
"Key": "Value"
}
}
}

另外, 它还支持多环境 config

appsettings.json 是抽象, appsettings.Development.json 是具体, 具体可以 override 和 extend 抽象.

"MyConfig": {
"Child2": {
"Key": "New Value"
},
"Child3": {
"Key": "New Value"
}
}

注: Child2 的 Key 是 override, MyConfig.Child3 是 extend, 没办法 override 整个 MyConfig 对象的.

User Secrets

想深入了解请看这篇 ASP.NET Core – User Secret & Azure Key Vault .

有一些 config 比较敏感, 比如密码. ASP.NET Core 提供了一个叫 User Secrets 的方案来解决这个问题.

上面例子中, MyConfig.Child.Secret 是敏感数据. 不应该直接把 value 写到 json file 里面, 必须使用 User Secrets.

dotnet user-secrets init
dotnet user-secrets set "MyConfig:Child1:Secret" "password"

注意, 它的分隔符是分号 ":" 而不是点 "." 哦, 如果是 Array 就写号码, 比如: MyConfig:Array:0

这个 password 会被存到另一个 local file, git checkin 只会把 appsetting.json checkin, User Secrets local file 则不会, 所以密码只会留在电脑中.

项目发布时, 则会通过 Azure KeyVault 来充当这个 User Secrets, 所以在 Web Server appsetting.json 依然不会有任何敏感数据.

好了, 这样我们的 config 定义就完成了. 接下来看看如何在项目中获取这些 config.

Get Config Value

GetValue

program.cs

var builder = WebApplication.CreateBuilder(args);
var configValue1 = builder.Configuration.GetValue<string>("MyConfig:Child1:Key");
var configValue2 = builder.Configuration.GetValue<string>("MyConfig:Child1:Secret");

.NET 6 以前, 想在 program.cs 或者 config 是很难的, 但是现在很简单直观了.

CreateBuilder 会把 appsettings.Development.json, appsettings.json, User Secrets 弄好好.

通过 builder.Configuration.GetValue("path") 就可以获取到任何 value 了.

注意, path 的分隔符是分号 ":" 而不是点 "." 哦.

GetValue 一定要声明类型, 如果不清楚类型可以这样获取

var value = builder.Configuration["MyConfig:Child1:Key"];

value 的值一定是 string?,

如果是 null 那么就是 empty string.

如果是 boolean 那么就是 "False" or "True"

如果是 object 或 array 那么是 null

注: 最好能清楚 config 结构和类型, 不然会很乱的

GetSection

获取整个对象.

public class Child
{
public string Key { get; set; } = "";
} var childObject = builder.Configuration.GetSection("MyConfig:Child1").Get<Child>();

Section != Child 对象哦, 所以要记得 .Get() 才能获取到 Child 对象.

在 Console 创建和获取 Configuration

虽然 WebApplication.CreateBuilder 已经帮我们弄美美了, 但为了能理解多一点底层, 我们也看看 Console 的版本吧

创建项目和安装各做 package

dotnet new console -o SecretConsole

dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.FileExtensions
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Configuration.UserSecrets
dotnet add package Microsoft.Extensions.Configuration.Binder

program.cs

using System.Reflection;
using Microsoft.Extensions.Configuration; var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); // Development // 创建 ConfigBuilder
var configurationBuilder = new ConfigurationManager()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{environment ?? ""}.json", optional: true, reloadOnChange: true); // Development 情况下用本地的 UserSecrets
if (environment == "Development")
{
configurationBuilder = configurationBuilder.AddUserSecrets(Assembly.GetExecutingAssembly());
// configurationBuilder = configurationBuilder.AddUserSecrets<Program>(); // 或者用 Program class 也是一样的
} // 到这里就和 WebApplication.CreateBuilder 的 builder.Configuration 同样用法了
var configure = configurationBuilder.Build();
var value = configure.GetValue<string>("Account:Password");
Console.Write(value); // my password

通过 DI 获取 Configuration

上面 program.cs 是通过 builder 获取到 configuration. 想在 Razor Pages, Controllers, Services 获取到 Configuration 就需要通过 DI

public class IndexModel : PageModel
{
public IndexModel(
IConfiguration configuration
)
{
var value = configuration.GetValue<string>("MyConfig:Child1:Key");
} public void OnGet()
{ }
}

注入 IConfiguration 就可以了.

Options

封装的模块通常不会直接通过 appsetting 获取 configuration (唯一例外的是 Log).

绝大部分的模块会通过 Options 来管理 "Config".

这些就是 options

我们先看看 Options 的玩法, 之后在看它如何和 configuration 一起工作.

Service Module

假设想封装一个服务

service class

public class MyService
{
public string GetComputedValue()
{
return "value";
}
}

provide

builder.Services.AddScoped<MyService>();

inject

public IndexModel(
MyService myService
)
{
var result = myService.GetComputedValue();
}

升级为模块

public static class IServiceCollectionExtensions
{
public static void AddMyModule(this IServiceCollection services)
{
services.AddScoped<MyService>();
}
}

provide

builder.Services.AddMyModule();

Service Options

这时, 想加入一些 config, 在 provide 的时候设定.

public class MyServiceOptions
{
public string Value { get; set; } = "";
}

provider

builder.Services.AddMyModule(options =>
{
options.Value = "my value";
});

module

public static class IServiceCollectionExtensions
{
public static void AddMyModule(this IServiceCollection services, Action<MyServiceOptions> optionsBuilder)
{
var options = new MyServiceOptions();
optionsBuilder(options);
var value = options.Value; // my value
services.AddScoped<MyService>();
}
}

这时问题来了, MyService 和 MyServiceOptions 怎样关联起来呢?

既然是用 DI, MyService 依赖 MyServiceOptions, 那么显然 MyServiceOptions 也必须 provide, 这样才能被 MyService 注入.

ASP.NET Core 提供了一个 services.Configure 接口, 让我们 provide 这个 options.

    public static void AddMyModule(this IServiceCollection services, Action<MyServiceOptions> optionsBuilder)
{
services.Configure<MyServiceOptions>(optionsBuilder);
services.AddScoped<MyService>();
}

Configure 是可以 call multiple times 的哦

好, provide 没有问题了, 那怎么注入?

通过 IOptions<MyServiceOptions> 注入.

public class MyService
{
public MyService(
IOptions<MyServiceOptions> myServiceOptions
)
{
var value = myServiceOptions.Value;
}
public string GetComputedValue()
{
return "value";
}
}

Options Work with Configuration

上面 options 是通过 optionsBuilder 来设置的. 那怎样让它和 appsetting 挂钩呢?

方法 1, 把 section 丢进去.

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

方法 2, 挨个挨个 set

builder.Services.Configure<MyServiceOptions>(options =>
{
options.Value = builder.Configuration.GetValue<string>("MyServiceOptions:Value");
});

这样就行了.

Named Options

我是在玩 Identity External Login 时发现的. GoogleOptions

[FromServices] IOptionsSnapshot<GoogleOptions> googleOptionsAccessor

var googleOptions = googleOptionsAccessor.Get("Google").Scope.ToList();

参考: Docs – Named options support using IConfigureNamedOptions

public static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
// 做 2 次 Configure<ServiceOptions> 但是用放不同的名字
serviceCollection.Configure<ServiceOptions>("Option1", options => options.Name = "Derrick");
serviceCollection.Configure<ServiceOptions>("Option2", options => options.Name = "Alex"); var serviceProvider = serviceCollection.BuildServiceProvider();
// 获取 options accessor 手法一样
var serviceOptionsAccessor = serviceProvider.GetRequiredService<IOptionsSnapshot<ServiceOptions>>();
// 不同名字可以从 accessor 里拿到不同的 options
Console.WriteLine(serviceOptionsAccessor.Get("Option1").Name); // Derrick
Console.WriteLine(serviceOptionsAccessor.Get("Option2").Name); // Alex
}

看注释理解

Optional Options

假如我忘了在 program.cs register ServiceOptions。

// builder.Services.Configure<ServiceOptions>(options => options.Name = "test");

但我缺尝试去 inject 它

public IndexModel(
IOptionsSnapshot<ServiceOptions> serviceOptionsAccessor
)
{
var serviceOptions = serviceOptionsAccessor.Value;
}

它是不会报错了。它会 new ServiceOptions() 作为 default value。

如果你需要识别出是否有提供 ServiceOptions 会比较麻烦。

参考: Stack Overflow – How to make an IOptions section optional in .NET Core?

在 options 里多加一个 property 来表示。

IOptionsSnapshot vs IOptions vs IOptionsMonitor

上面给的例子是用 IOptions 来注入. 它有个缺点. 就是当 appsetting 修改了以后, 需要重启 app 才能 update.

有时候这个是预期的效果, 但有时候会希望马上更新. 于是有了另外 2 个 IOptions 变种.

参考:

IOptions、IOptionsMonitor、IOptionsSnapshot的区别

ASP.NET Core笔记(4) - 选项模式

它们之间主要是生命周期不同.

IOptions 算是单列, optionsBuilder 只运行一次. 一直到 application 重启,

IOptionsSnapshot 的生命周期是 scope (per request), 它把声明周期从 app 缩小到每个 request.

每一次新的请求就会重跑 optionBuilder 拿到新的 Options 值, 需要注意的是 snapshot 的周期是 scope 也意味着它不能用在单列的 service 哦.

IOptionsMonitor 可以用在单列也可以不需要重启 app, 因为它获取的是 current value. 也就是每一次都拿最新的, 甚至在同一个 request 里面.

我个人的用法是尽可能就用 snapshot 然后少用单列 service.

ASP.NET Core – Configuration & Options的更多相关文章

  1. 理解ASP.NET Core - 选项(Options)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 Options绑定 上期我们已经聊过了配置(IConfiguration),今天我们来聊一聊O ...

  2. asp.net core选项Options模块的笔记

    这篇博客是写给自己看的.已经不止一次看到AddOptions的出现,不管是在.net core源码还是别人的框架里面,都充斥着AddOptions.于是自己大概研究了下,没有深入,因为,我的功力还是不 ...

  3. ASP.Net Core Configuration 理解与源码分析

    Configuration 在ASP.NET Core开发过程中起着很重要的作用,这篇博客主要是理解configuration的来源,以及各种不同类型的configuration source是如何被 ...

  4. ASP.NET Core 中文文档 第二章 指南(4.5)使用 SQL Server LocalDB

    原文:Working with SQL Server LocalDB 作者:Rick Anderson 翻译: 魏美娟(初见) 校对: 孟帅洋(书缘).张硕(Apple).许登洋(Seay) Appl ...

  5. Working with Data » 使用Visual Studio开发ASP.NET Core MVC and Entity Framework Core初学者教程

    原文地址:https://docs.asp.net/en/latest/data/ef-mvc/intro.html The Contoso University sample web applica ...

  6. Prerender Application Level Middleware - ASP.NET Core Middleware

    In the previous post Use Prerender to improve AngularJS SEO, I have explained different solutions at ...

  7. 5. abp集成asp.net core

    一.前言 参照前篇<4. abp中的asp.net core模块剖析>,首先放张图,这也是asp.net core框架上MVC模块的扩展点 二.abp的mvc对象 AbpAspNetCor ...

  8. ASP.NET Core - 选型系统之选型配置

    1. 选项 前面讲完了.NET Core 下的配置系统,我们可以通过 IConfiguration 服务从各种来源的配置中读取到配置信息,但是每次要用的时候都通过 Iconfiguration 读取配 ...

  9. ASP.NET Core - 选项系统之选项验证

      就像 Web Api 接口可以对入参进行验证,避免用户传入非法的或者不符合我们预期的参数一样,选项也可以对配置源的内容进行验证,避免配置中的值与选项类中的属性不对应或者不满足预期,毕竟大部分配置都 ...

  10. ASP.NET Core - 选项系统之源码介绍

    .NET Core 选项系统的主要实现在 Microsoft.Extensions.Options 和 Microsoft.Extensions.Options.ConfigurationExtens ...

随机推荐

  1. 推荐王牌远程桌面软件Getscreen,所有的远程桌面软件中使用最简单的一个

    今天要推荐的远程桌面软件就是这款叫Getscreen的,推荐理由挺简单: 简单易用:只需要两步就能轻松连上远程桌面 第一步:在需要被远程连接的机器上下载它的Agent程序并启动,点击Send获得一个链 ...

  2. 浅谈地址,section,vstart

    地址:地址只是数字,描述各种符号在源程序中的位置,它是源代码文件中各符号偏移文件开头的距离.由于指令和变量所占内存大小不同,故他们的偏移量参差不齐.由编译器给各符号编址,编译器给程序中各符号(变量名和 ...

  3. 题解:P10672 【MX-S1-T1】壁垒

    暑期集训=依托答辩. 分析 种类数是奇数一定无解. 否则每种数字先输出一次,在此过程中每增加两个数时,因为每个数字种类数都不一样,所以前缀种类数也同时增加 \(2\),保证一定为偶数. 然后输出完以后 ...

  4. C# 泛型单例工厂

    核心代码,线程安全 class SingletonFactory<T> where T : new() { private static T uniqueInstance; private ...

  5. Windows cifs共享给linux

    Windows 搜索启用或关闭Windows功能 启用cifs 共享文件并添加普通用户 解禁guest用户和设置本地策略 右键计算机 win+r输入secpol.msc 将guest删除 修改网络访问 ...

  6. 【Binary】XShell6 无法使用的解决办法

    感谢博主的解决方案: https://www.cnblogs.com/pinkpolk/articles/13554445.html 首先需要安装VsCode,并且安装一个[Hex Editor]的插 ...

  7. 大语言模型(LLM)的逻辑推理能力的例子 —— 以ChatGPT3.5为例

    例子: PS. 不得不说,这个表现虽然没有那么完美但是已经比较惊艳了,比传统的自然语言对话系列的表现高出很多呀,很神奇,这个LLM的逻辑能力是如何实现的呢?虽然LLM不具备逻辑推理能力,但是LLM确实 ...

  8. 如何查看华为的大模型(AI模型),华为官方的mindspore下的大模型???

    由于华为官方的mindspore网站的设计比较反人性话,操作起来十分的复杂,因此如果想要在华为的官方网站上查找这个华为的官方大模型还是比较困难的,为此直接给出链接地址. PS. 要注意,华为的AI官方 ...

  9. Mybatis-Plus系统化学习之环境准备与简单使用

    1.背景 平时在开发中会经常用到单表的CRUD操作 其实,这些单表的CRUD,完全不需要我们写sql,可以使用mybatis-plus自动生成,不但高效而且不容用出错! 2.mybatis-plus的 ...

  10. rest_framework与django配合使用

    rest_framework与django配合使用 rest_framework与django配合使用   一.构建表单,在这里我们先构建五个表单,分别是 author book publish us ...