在.net core中使用配置文件的几个示例和方法

本文基于 .net core 2.2(更新.NET6 示例)

主要包含的示例:

  • 通过DI进行使用配置文件

    • 如何在 ASP.NET 中使用配置文件
  • 直接硬编码使用配置文件

    • 如何在控制台中使用配置文件

示例:ASP.NET MVC

asp.net mvc已经内部实现了对配置appsettings.json文件的使用,builder默认支持热更新。

使用示例:

假设appsettings.json内容为:

1
2
3
4
5
6
7
8
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
  1. 新建一个跟appsettings.json结构保持一致的类,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace webapp.Models
{
public class AppsettingsModel
{
public Logging Logging { get; set; }

public string AllowedHosts { get; set; }
}

public class Logging
{
public LogLevel LogLevel { get; set; }
}

public class LogLevel
{
public string Default { get; set; }
}
}

PS:

  • 需要注意,用于IOptions或者IOptionsSnapshot中的模型的各个属性,其setter必须是公共的,不能是私有。

  • 另外对于该模型,必须要有一个无参构造函数。

  1. 在Startup.cs中进行依赖注入

1
2
3
4
5
6
7
8
9
10
11
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
// 依赖注入
services.Configure<AppsettingsModel>(Configuration);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
  1. 在controller中调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestController : Controller
{
private readonly AppsettingsModel _appsettingsModel;
//若要使用热更新,则入参调整为 IOptionsSnapshot<T>
public TestController(IOptions<AppsettingsModel> appsettingsModel)
{
_appsettingsModel = appsettingsModel.Value;
}

public IActionResult Index()
{
return View("Index", _appsettingsModel.Logging.LogLevel.Default);
}
}
  • 这里需要注意一点,DI时,如果是单例,则无法使用是IOptionsSnapshot,会报错。

如何覆写默认行为?如取消热更新支持,方法如下:

假设测试controller为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestController : Controller
{
private readonly AppsettingsModel _appsettingsModel;
//使用的是:IOptionsSnapshot<T>
public TestController(IOptionsSnapshot<AppsettingsModel> appsettingsModel)
{
_appsettingsModel = appsettingsModel.Value;
}

public IActionResult Index()
{
return View("Index", _appsettingsModel.Logging.LogLevel.Default);
}
}

Program.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) => //1.通过该方法来覆盖配置
{
//2.重新添加json配置文件
config.AddJsonFile("appsettings.json", false, false); //3.最后一个参数就是是否热更新的布尔值
})
.UseStartup<Startup>();
}

853140e9-c2ae-448b-9564-59e27023f387.png

  • 这个时候,人为将热更新给关闭了,此时更新json文件后,修改后的内容不会更新到系统中。

示例:控制台

对于console项目,默认是没有这个dll的,需要自行从nuget安装

从nuget中安装:Microsoft.AspNetCore.All (注意,末尾不是dll,而是all)

在项目中引入:Microsoft.Extensions.Configuration; 并使用ConfigurationBuilder来构建配置。

使用应用程序参数

在控制台项目属性中增加name和class参数:

e4c2f50f-6d3d-4867-b12e-e2a3ede38da9.png

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Program
{
static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.AddCommandLine(args);
var configuration = builder.Build();

Console.WriteLine($"name:{configuration["name"]}"); //name:CLS
Console.WriteLine($"class:{configuration["class"]}"); //class:Class_A

Console.Read();
}
}

使用键值对枚举(这里以字典来说明)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    class Program
{
static void Main(string[] args)
{
var dict = new Dictionary<string, string>
{
{"name","MC"},
{"class","CLASS_MC"}
};
var builder = new ConfigurationBuilder()
// .AddCommandLine(args)
.AddInMemoryCollection(dict);

var configuration = builder.Build();

Console.WriteLine($"name:{configuration["name"]}");//name:MC
Console.WriteLine($"class:{configuration["class"]}"); //class:CLASS_MC

Console.Read();
}
}

注意事项:

  • 这里需要注意下,虽然 AddCommandLine 和 AddInMemoryCollection 可以同时调用,但不同的使用次序,效果是不一样的(后一个会覆盖前一个的内容—浅覆盖),如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 /*
假设 在项目属性中,定义的内容为:name=CLS,class=CLASS_CLS,grade="mygrade"
在代码中,dict的内容为:name=MC,class=CLASS_MC
*/
//对于代码:
var builder = new ConfigurationBuilder()
.AddCommandLine(args)
.AddInMemoryCollection(dict);
var configuration = builder.Build();

Console.WriteLine($"name:{configuration["name"]}");//name:MC
Console.WriteLine($"class:{configuration["class"]}"); //class:CLASS_MC
Console.WriteLine($"grade:{configuration["grade"]}"); //grade:mygrade

//对于代码:
var builder = new ConfigurationBuilder()
.AddInMemoryCollection(dict)
.AddCommandLine(args);
var configuration = builder.Build();

Console.WriteLine($"name:{configuration["name"]}");//name:CLS
Console.WriteLine($"class:{configuration["class"]}"); //class:CLASS_CLS
Console.WriteLine($"grade:{configuration["grade"]}"); //grade:mygrade

  • 另外,需要注意,如果用dotnet命令来执行CommandLineSample.dll,那么“应用程序参数”需要直接跟在命令的后面,如:

    • 另外如果AddInMemoryCollection和AddCommandLine同时使用,那么需要将AddCommandLine最后调用,否则一旦被覆盖了,再用dotnet来调用,会没有效果。
1
dotnet   CommandLineSample.dll   name=111 class=222  grade="my grade"

使用JSON文件

  • 在项目根目录创建“jsconfig1.json”,同时修改该文件的属性:

    • 复制到输出目录:始终复制
    • 生成操作:内容

JSON文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"Class": "Class A",
"PersonInfo": {
"name": "my name",
"age": "12"
},
"Hobbies": [
{
"Type": "Family",
"HobbyName": "Piano"
},
{
"Type": "Personal",
"HobbyName": "Singing"
}
]
}

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("jsconfig1.json");

var configuration = builder.Build();

Console.WriteLine($"name:{configuration["PersonInfo:name"]}");
Console.WriteLine($"class:{configuration["class"]}");
Console.WriteLine($"age:{configuration["PersonInfo:age"]}");
//注意下调用参数时的格式:"{参数Key}:{数组索引}:{子项参数Key}"
Console.WriteLine($"FamilyHobby:{configuration["Hobbies:0:HobbyName"]}");
Console.WriteLine($"PersonalHobby:{configuration["Hobbies:1:HobbyName"]}");
/* 也可以用模型的方式,如下:
var settings = configuration.GetSection("RedisSettings").Get<RedisSettings>();
*/
Console.Read();
}

注册配置文件中的某一个段到一个class模型中

引用上面的json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"Class": "Class A",
"PersonInfo": {
"name": "my name",
"age": "12"
},
"Hobbies": [
{
"Type": "Family",
"HobbyName": "Piano"
},
{
"Type": "Personal",
"HobbyName": "Singing"
}
]
}

如何在注册的时候希望将PersonInfo这个section单独注入到 PersonInfo.cs类中?(以mvc为例)

  • PersonInfo.cs

1
2
3
4
5
public class PersonInfo
{
public string Name {get;set;}
public int Age{get;set;}
}
  • Startup.cs

1
2
3
4
5
6
7
8
9
10
11
12
13

public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
// 依赖注入
services.Configure<PersonInfo>(Configuration.GetSection("PersonInfo"));

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
  • 在controller中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestController : Controller
{
private readonly PersonInfo _personInfo;
public TestController(IOptions<PersonInfo> personInfo)
{
_personInfo = _personInfo.Value;
}

public IActionResult Index()
{
return View("Index", _personInfo.Name);
}
}
  • 在 startup.cs中使用

1
2
3
4
5
6
//jwt           
services.Configure<JwtSettings>(Configuration.GetSection("JwtSettings"));

var jwtSettings = new JwtSettings();
Configuration.Bind("JwtSettings", jwtSettings);
services.AddSanbenTechJwtService(jwtSettings.Issuer, jwtSettings.Audience);

示例:在单元测试中使用 配置文件

首先确保配置文件的属性:

nuget安装、引入必要的库:

1
2
3
Microsoft.Extensions.Configuration
Microsoft.Extensions.Configuration.Binder
Microsoft.Extensions.Configuration.Json

在构造函数中使用配置文件:

1
2
3
4
5
6
7
8
public UnitTest1()
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
var configuration = builder.Build();
var settings = configuration.GetSection("RedisSettings").Get<RedisSettings>();
_mock.Setup(p => p.Value).Returns(settings);
}

自定义 ConfigProvider:对数据库连接字符串等敏感信息进行加密

Startup.cs

1
2
3
4
5
6
7
8
9
10
public Startup(IWebHostEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEncryptedProvider($"appsettings.{env.EnvironmentName}.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}

AddEncryptedProvider 扩展方法

1
2
3
4
5
6
7
8
9
using Microsoft.Extensions.Configuration;

public static class CustomConfigProviderExtensions
{
public static IConfigurationBuilder AddEncryptedProvider(this IConfigurationBuilder builder, string path)
{
return builder.Add(new CustomConfigProvider(path));
}
}

CustomConfigProvider.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;


public class CustomConfigProvider : ConfigurationProvider, IConfigurationSource
{
private string JsonFilePath { get; }
private const string EncryptKey = "your encryptedKEY";

public CustomConfigProvider()
{

}

public CustomConfigProvider(string path)
{
JsonFilePath = path;
}

public override void Load()
{
Data = UnencryptMyConfiguration();
}

private IDictionary<string, string> UnencryptMyConfiguration()
{
var configValues = new Dictionary<string, string>();
if (!File.Exists(JsonFilePath))
return configValues;
var jsonRoot = JObject.Parse(File.ReadAllText(JsonFilePath));

var key = "ConnectionStrings";
var jObject = jsonRoot.GetValue(key);
foreach (var x in jObject.Children())
{
var hasChild = ((JContainer)x).First.HasValues;
if (hasChild)
{
var i = 0;
foreach (var y in ((JArray)((JContainer)x).First).Children())
{
configValues.Add($"{ key}:{((JProperty)x).Name}:{i++}", AesDecrypt(((JValue)y).Value.ToString(),EncryptKey));
}
}
//
else
{
configValues.Add($"{ key}:{((JProperty)x).Name}", AesDecrypt(((JValue)((JProperty)x).First).Value.ToString(),EncryptKey));
}
}
return configValues;
}

public IDictionary<string, string> CreateAndSaveDefaultValues(IDictionary<string, string> defaultDictionary = null)
{
var configValues = new Dictionary<string, string>();
if (defaultDictionary == null)
{
if (!File.Exists(JsonFilePath))
return configValues;
var jsonRoot = JObject.Parse(File.ReadAllText(JsonFilePath));

var jObject = jsonRoot.GetValue("ConnectionStrings");
foreach (var x in jObject.Children())
{
var encryptValue = AesEncrypt(((JValue)((JProperty)x).First).Value.ToString(), EncryptKey);
configValues.Add(((JProperty)x).Name, encryptValue);
}
}
else
{
foreach (var x in defaultDictionary)
{
configValues.Add(x.Key, AesEncrypt(x.Value, EncryptKey));
}
}
return configValues;
}
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new CustomConfigProvider(JsonFilePath);
}

public string Decrypt(string str)
{
return AesDecrypt(str, EncryptKey);
}
public string Encrypt(string str)
{
return AesEncrypt(str, EncryptKey);
}

private string AesEncrypt(string str, string key)
{
if (string.IsNullOrEmpty(str)) return null;
var toEncryptArray = Encoding.UTF8.GetBytes(str);

var rm = new RijndaelManaged
{
Key = Encoding.UTF8.GetBytes(key),
Mode = CipherMode.ECB,
Padding = PaddingMode.PKCS7
};

var cTransform = rm.CreateEncryptor();
var resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
return Convert.ToBase64String(resultArray);
}
private string AesDecrypt(string str, string key)
{
if (string.IsNullOrEmpty(str)) return null;
var toEncryptArray = Convert.FromBase64String(str);

var rm = new RijndaelManaged
{
Key = Encoding.UTF8.GetBytes(key),
Mode = CipherMode.ECB,
Padding = PaddingMode.PKCS7
};

var cTransform = rm.CreateDecryptor();
var resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

return Encoding.UTF8.GetString(resultArray);
}
}

一个官方例子

https://docs.microsoft.com/zh-cn/aspnet/core/security/app-secrets?view=aspnetcore-2.2&tabs=windows

1
2
3
4
5
6
{
"Movies": {
"ServiceApiKey": "12345",
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}

配置模型:

1
2
3
4
5
6
7
8
9
10
public class MovieSettings
{
public string ConnectionString { get; set; }

public string ServiceApiKey { get; set; }
}

//使用
var moviesConfig = Configuration.GetSection("Movies").Get<MovieSettings>();
_moviesApiKey = moviesConfig.ServiceApiKey;

.NET 6 下使用 Configuration

JSON 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"DockItemOptions": {
"ProductGroups": {
"GroupA": [
{
"Id": "A_1",
"Name": "na_1"
},
{
"Id": "A_2",
"Name": "na_2"
}
],
"GroupB": [
{
"Id": "B_1",
"Name": "nb_1"
},
{
"Id": "B_2",
"Name": "nb_2"
}
]
}
}
}

Program.cs 中设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Text.Encodings.Web;
using Weather.Web;
using Weather.Web.Models;
using Microsoft.Extensions.DependencyInjection;

IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();

var builder = WebApplication.CreateBuilder(args);

// other codes ...

builder.Services.Configure<DockItemOptions>(configuration.GetSection(DockItemOptions.SettingsKey));

var app = builder
.Build();
// other codes ...

Controller 中调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Weather.Web.Models;

namespace Weather.Web.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherController : ControllerBase
{
private readonly DockItemOptions _dockItemOptions;
public WeatherController(IOptions<DockItemOptions> dockOptions)
{
_dockItemOptions = dockOptions.Value;
}

[HttpGet("Test")]
public async Task<string> Test()
{
_dockItemOptions.ProductGroups.TryGetValue("GroupA", out var items);
return items.Count;
}
}
}

models:

注意事项见注释部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using Newtonsoft.Json;

namespace Weather.Web.Models
{
public class DockItemOptions
{
public const string SettingsKey = "DockItemOptions";

[JsonProperty("ProductGroups")]
public Dictionary<string, List<DockItem>> ProductGroups { get; set; } = new Dictionary<string, List<DockItem>>(); //务必注意:这里一定要有 get 和 set,否则 configuration.GetSection 的 value 会一直为 null
}

public class DockItem
{
public string Id { get; set; }

public string Name { get; set; }
}
}

FAQ

  1. 在使用AddJsonFile的时候,被添加的json文件需要在项目所处根目录内,否则不会加载(路径默认是到项目文件夹,而非bin目录下)。打包发布后会自动从发布文件夹找对应的配置文件

    • 这里主要是为了应对新建了一个项目A,然后在这个项目A中添加了一个配置文件config.json,项目B引用这个项目A后,虽然最终编译之后会在bin文件夹内自动生成config.json,但是在调试模式下,默认的路径是在项目路径,而非bin下的文件夹路径,这会导致config.json在开发模式下(调试时)不会被加载。
      • 可以手动将改config.json文件复制一份到项目B根目录内。
      • 如果是用nuget发布项目A后,项目B再从nuget安装项目A,则不会有此问题。