说到配置,绝大部分系统都会有配置,不需要配置的系统是非常少的,想想以前做.net 开发时,我们常常将配置放到web.config中,然后使用ConfigurationManager去读取。

  初次接触到.net core 的同学,在项目中看到有一个appsettings.json文件,确实这个appsettings.json文件是做配置用的,所以想当然的把它看做.net 开发中的web.config一样,但是我们要清除,.net core并不依赖appsettings.json文件中的配置。

  .net core 提供了一种非常灵活的配置方式,大部分时候,我们只需要关注DI容器中的IConfiguration接口实例对象就可以了,下面具体介绍。

  这里介绍的.net core版本是3.1,源码地址:https://github.com/dotnet/extensions/tree/v3.1.12/src/Configuration

  一、原理

  要介绍原理,先看与配置相关的几个接口及它们的实现类:

  IConfigurationBuilder

  配置建造者接口,我们使用它去创建配置对象,有一个实现类:ConfigurationBuilder

  IConfiguration

  表示配置集合的接口,一般的,程序通过从DI获取IConfiguration接口的实例来获取配置

  IConfigurationRoot

  IConfiguration的子接口,表示配置的根节点,换句话说,IConfigurationBuilder创建的第一个配置对象就是IConfigurationRoot接口对象,它的实现类是:ConfigurationRoot

  IConfigurationSection

  IConfiguration的子接口,表示配置的一个节点,包含节点名、节点路径、值等等,配置节点分隔默认是冒号(:),它的实现类是:ConfigurationSection

  IConfigurationSource

  配置来源接口,IConfigurationSource接口的实现类都很简单,主要用于结合Options创建配置提供者IConfigurationProvider,一般的,它的作用可以认为就是接收参数,然后在创建IConfigurationProvider时将参数传进去。

  但是在读取来自文件的配置时,推荐继承抽象类:FileConfigurationSource ,其它的就直接实现 IConfigurationSource 就可以了,然后添加到 IConfigurationBuilder 的配置源中去。

  IConfigurationProvider

  配置信息的具体提供者,这个就是提供配置的获取、更新等等操作的接口,有两个重要的抽象类:ConfigurationProviderFileConfigurationProvider

  一般的,如果我们需要集成自己的配置,需要实现这个 IConfigurationSource 接口和 IConfigurationProvider 接口,如果我们的配置和文件有关,建议通过继承 FileConfigurationSource 两个 FileConfigurationProvider 两个抽象类来实现 IConfigurationSource 和 IConfigurationProvider接口,因为这两个抽象类已经提供了一些我们可能需要的功能,比如,它们可以监听文件状态,如果文件内容被修改,则可以重新加载配置。如果配置不来自文件,配置来源可以直接实现 IConfigurationSource 接口,而通过继承 ConfigurationProvider 来实现 IConfigurationProvider 接口。

  于是乎,将它们串接起来,流程就是这样的:

  1、提供一个实现了 IConfigurationProvider 接口的配置提供类,它需要提供配置的读取以及更新等操作

  2、提供一个 IConfigurationSource 接口实现类,它负责创建 IConfigurationProvider 。

  3、创建一个 IConfigurationBuilder 配置建造者对象,然后将 IConfigurationSource 添加进配置构造者中,这里我们一般都采用 IConfigurationBuilder 的拓展方法来实现。

  4、使用 IConfigurationBuilder 构造一个 IConfigurationRoot ,然后使用这个 IConfigurationRoot 去操作配置。

  这是一般流程,而.net core的配置是一个拓展模块,也就是说我们可以在控制台等其他项目中引用,只需要安装包:Microsoft.Extensions.Configuration

  为了更好的说明,我们可以先看IConfiguration在WebHost中是怎么集成的,已.net core 3.1为例,它的Program是这样的:  

    public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
} public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}

  看看Host.CreateDefaultBuilder()方法(源码),源码是这样的:

    public static IHostBuilder CreateDefaultBuilder(string[] args)
{
... builder.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
} config.AddEnvironmentVariables(); if (args != null)
{
config.AddCommandLine(args);
}
}) ...
}

  现在可以看出为什么appsettings.json是默认的配置文件了,ConfigureAppConfiguration方法就是对配置的构造过程,这里默认最多会加载5个配置源(也就是上面config.AddXXXXX()部分,后面具体介绍)。

  而ConfigureAppConfiguration的实现就是将传进去的委托保存(源码):  

    public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
{
_configureAppConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
return this;
}

  这里保存是将委托放到一个List中,也就是说ConfigureAppConfiguration方法可以多次调用,我们可以添加我们自己的配置了,在Build时就会按顺序来调用(源码):  

    public IHost Build()
{
...
BuildAppConfiguration();
...
}

  而BuildAppConfiguration方法则是最终构造配置的过程(源码):  

    private void BuildAppConfiguration()
{
var configBuilder = new ConfigurationBuilder()
.SetBasePath(_hostingEnvironment.ContentRootPath)
.AddConfiguration(_hostConfiguration, shouldDisposeConfiguration: true); foreach (var buildAction in _configureAppConfigActions)
{
buildAction(_hostBuilderContext, configBuilder);
}
_appConfiguration = configBuilder.Build();
_hostBuilderContext.Configuration = _appConfiguration;
}

  可以看到.net core内部也是直接实例化一个ConfigurationBuilder来构造配置的,而它的Build方法则返回的是一个 IConfigurationRoot 接口对象(源码),剩下的就是使用 IConfigurationRoot 接口对象来读取更新配置了。  

    public IConfigurationRoot Build()
{
var providers = new List<IConfigurationProvider>();
foreach (var source in Sources)
{
var provider = source.Build(this);
providers.Add(provider);
}
return new ConfigurationRoot(providers);
}

  二、内置的配置方式

  官方在配置方法,提供了一些默认的配置源,它们都是通过IConfigurationBuilder的拓展方法来集成配置源,这也很好的给我们展示了如何添加自己的配置源。

  官方默认提供的配置源有:

  Json文件

  NuGet安装包:Microsoft.Extensions.Configuration.Json

  通过 IConfigurationBuilder的AddJsonFile和AddJsonStream两个拓展方法来集成(源码),有多个重载,各参数的含义如下:  

    provider:提供json文件的一些信息及功能操作,比如所在结构目录,监听文件状态等等,默认默认值:new PhysicalFileProvider(AppContext.BaseDirectory ?? string.Empty)
path:json文件路径
optional:表示json文件是否是可选的,如果未false,那么当json文件不存在时则会抛出异常
reloadOnChange:表示是否在文件内容修改后重新加载配置,如果未false,表示不重新加载
stream:json文件流

  比如有一个json文件(注意文件位置):  

  

{
"Hello": {
"Microsoft": {
"Extensions": "Configuration"
}
}
}

configuration.json

  我们读取是这样的:  

    static void Main(string[] args)
{
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddJsonFile("configuration.json");
var configuration = builder.Build();
var collections = configuration.AsEnumerable();
foreach (var item in collections)
{
Console.WriteLine("{0}={1}", item.Key, item.Value);
}
}

  结果:

  

  Ini文件

  NuGet安装包:Microsoft.Extensions.Configuration.Ini

  通过 IConfigurationBuilder的AddIniFile和AddIniStream两个拓展方法来集成(源码),有多个重载,各参数的含义同上Json文件。  

  比如我们有一个ini文件(注意文件位置):

  

[SessionName1]
KeyName11=value11
KeyName12=value12 [Section2Name]
KeyName21=value21
KeyName22=value22

iniFile.ini

  我们读取是这样的:  

    static void Main(string[] args)
{
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddIniFile("iniFile.ini");
var configuration = builder.Build();
var collections = configuration.AsEnumerable();
foreach (var item in collections)
{
Console.WriteLine("{0}={1}", item.Key, item.Value);
}
}

  结果:

  

  Xml文件

  NuGet安装包:Microsoft.Extensions.Configuration.Xml

  通过 IConfigurationBuilder的AddXmlFile和AddXmlStream两个拓展方法来集成(源码),有多个重载,各参数的含义同上Json文件(这也是在告诉我们,如果我们要从其他文件添加,只需要类似这些参数就可以了)。

  比如我们有一个xml文件(注意文件位置):    

  

    <?xml version="1.0" encoding="utf-8" ?>

    <node1>
<node2>value2</node2>
<node3>
<node4>value4</node4>
<node5>value5</node5>
</node3>
</node1>

xmlFile.xml

  我们读取是这样的:  

    static void Main(string[] args)
{
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddXmlFile("xmlFile.xml");
var configuration = builder.Build();
var collections = configuration.AsEnumerable();
foreach (var item in collections)
{
Console.WriteLine("{0}={1}", item.Key, item.Value);
}
}

  结果:

  

  注:配置的键不包含xml中的根路径

  命令行参数

  NuGet安装包:Microsoft.Extensions.Configuration.CommandLine

  熟悉命令行的朋友知道,经常的,在执行一个命令时,可以携带一些参数,有些使用 - 或者 -- 符号,有些则没有,同样的,dotnet命令在执行可以也可以携带一些运行时参数,.net core可以将这些参数集成到IConfiguration中。

  通过 IConfigurationBuilder的AddCommandLine拓展方法来集成(源码),同样有几个重载,说明如下:  

    args:命令函参数
switchMappings:映射转化,主要是将-开头和--开头的配置转换成执行的键名

  说明一下,参数必须满足一下规则:  

    1、必须以 -、--、/ 作为前缀,其中 -- 与 / 等价
2、如果是以 - 为前缀的参数,则必须使用 switchMappings 做一层映射,否则将抛出 FormatException ,所以自定义的命令行参数建议采用 -- 作为前缀
3、参数名与参数值之间使用 = 或者空格分隔,建议使用 =

  比如:  

    static void Main(string[] args)
{
//dotnet XXXX.dll -a=a --b=b /c=c --e 1 /d 2 var mapper = new Dictionary<string, string>()
{
{ "-a", "mapper-a" },
{ "--b", "mapper-b" },
{ "--c", "mapper-c" }
}; ConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddCommandLine(args, mapper);
var configuration = builder.Build();
var collections = configuration.AsEnumerable();
foreach (var item in collections)
{
Console.WriteLine("{0}={1}", item.Key, item.Value);
}
}

  如果执行命令行:  

    dotnet XXXX.dll -a=a --b=b /c=c --e 1 /d 2

  结果是:

  

  环境变量

  NuGet安装包:Microsoft.Extensions.Configuration.EnvironmentVariables

  .net core允许我们将本地的环境变量集成到IConfiguration配置中,通过IConfigurationBuilder的AddEnvironmentVariables拓展方法来集成(源码),同时可以指定一个前缀(拓展方法中的prefix参数),表示要加载在配置中的哪些环境变量的前缀,而不是全部环境变量。

  例如:  

    static void Main(string[] args)
{
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddEnvironmentVariables("Common");
var configuration = builder.Build();
var collections = configuration.AsEnumerable();
foreach (var item in collections)
{
Console.WriteLine("{0}={1}", item.Key, item.Value);
}
}

  结果:

  

  注:IConfiguration中的键值会去掉环境变量名中的前缀,不如这里我的环境变量名是CommonProgramFiles,但是IConfiguration中的配置名是ProgramFiles

  IConfiguration配置

  有时候,我们已经存在一个IConfiguration配置了,然后我们可以将它集成到另一个IConfiguration中去使用,通过IConfigurationBuilder的AddConfiguration拓展方法来集成(源码)。

  这个很简单,只看个例子就可以了:  

  

    static void Main(string[] args)
{
//Json
ConfigurationBuilder builder1 = new ConfigurationBuilder();
builder1.AddJsonFile("configuration.json");
var configuration1 = builder1.Build();
//Ini
ConfigurationBuilder builder2 = new ConfigurationBuilder();
builder2.AddIniFile("iniFile.ini");
var configuration2 = builder2.Build();
//Xml
ConfigurationBuilder builder3 = new ConfigurationBuilder();
builder3.AddXmlFile("xmlFile.xml");
var configuration3 = builder3.Build();
//EnvironmentVariables
ConfigurationBuilder builder4 = new ConfigurationBuilder();
builder4.AddEnvironmentVariables("Common");
var configuration4 = builder4.Build(); ConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddConfiguration(configuration1);
builder.AddConfiguration(configuration2);
builder.AddConfiguration(configuration3);
builder.AddConfiguration(configuration4);
var configuration = builder.Build();
var collections = configuration.AsEnumerable();
foreach (var item in collections)
{
Console.WriteLine("{0}={1}", item.Key, item.Value);
}
}

  结果:

  

  内存集合

  有时候,我们配置源源自某个集合变量,这是我们同样可以将它集成到IConfiguration中去,通过IConfigurationBuilder的AddInMemoryCollection拓展方法来集成(源码)。

  这个也很简单,看例子就明白了了:  

    static void Main(string[] args)
{
Dictionary<string, string> dict = new Dictionary<string, string>();
dict["Key1"] = "Value1";
dict["Key2"] = "Value2";
dict["Key3"] = "Value3";
dict["Key4"] = "Value4"; ConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(dict);
var configuration = builder.Build();
var collections = configuration.AsEnumerable();
foreach (var item in collections)
{
Console.WriteLine("{0}={1}", item.Key, item.Value);
}
}

  结果:

  

  文件目录

  NuGet安装包:Microsoft.Extensions.Configuration.KeyPerFile

  可能考虑到在某些情况下,有些软件将产生的数据保存着独立的文件中,采用文件名作为区分,因此作者提供了一个将这些数据文件内容作为配置的方法,采用文件名作为key,文件内容作为value。它采用AddKeyPerFile拓展方法集成(源码

  需要注意的是,文件名中的双下划线(__)作为配置节点分隔符。

  比如,我们有一些文件(在属性中输出类型设置成始终复制):

  

  代码:  

    static void Main(string[] args)
{
string directory = Path.Combine(Directory.GetCurrentDirectory(), "files");
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddKeyPerFile(directory, true);
var configuration = builder.Build();
var collections = configuration.AsEnumerable();
foreach (var item in collections)
{
Console.WriteLine("{0}={1}", item.Key, item.Value);
}
}

  结果:

  

  用户私密文件

  NuGet安装包:Microsoft.Extensions.Configuration.UserSecrets

  通过IConfigurationBuilder的AddUserSecrets拓展方法来集成(源码),它主要用来配置一些私密信息文件,这个很少使用,了解一下就行了。

  在集成时,会涉及到一个userSecretsId,其实它的本意是一个独一无二的目录名,一般设置成GUID,我们有两种方式使用它:

  方式一:直接使用  

    builder.AddUserSecrets("userSecretsId");

  方式二:通过 UserSecretsIdAttribute 特性

  首先给某个程序集添加 UserSecretsIdAttribute 特性,比如我这里就是启动项目设置:  

    [assembly: Microsoft.Extensions.Configuration.UserSecrets.UserSecretsId("userSecretsId")]

  然后使用启动项目的程序集去获取:  

    builder.AddUserSecrets(typeof(Program).Assembly);

  上面说到userSecretsId是目录名,那是哪个目录名呢?根据源码,私密文件路径使用 PathHelper.GetSecretsPathFromSecretsId()方法来获取(源码):  

    public static string GetSecretsPathFromSecretsId(string userSecretsId)
{
...
const string userSecretsFallbackDir = "DOTNET_USER_SECRETS_FALLBACK_DIR"; // For backwards compat, this checks env vars first before using Env.GetFolderPath
var appData = Environment.GetEnvironmentVariable("APPDATA");
var root = appData // On Windows it goes to %APPDATA%\Microsoft\UserSecrets\
?? Environment.GetEnvironmentVariable("HOME") // On Mac/Linux it goes to ~/.microsoft/usersecrets/
?? Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
?? Environment.GetEnvironmentVariable(userSecretsFallbackDir); // this fallback is an escape hatch if everything else fails if (string.IsNullOrEmpty(root))
{
throw new InvalidOperationException("Could not determine an appropriate location for storing user secrets. Set the " +
userSecretsFallbackDir +
" environment variable to a folder where user secrets should be stored.");
} return !string.IsNullOrEmpty(appData)
? Path.Combine(root, "Microsoft", "UserSecrets", userSecretsId, SecretsFileName)
: Path.Combine(root, ".microsoft", "usersecrets", userSecretsId, SecretsFileName);
}

  在开发过程中,这个私密文件一般是:C:\Users\[USER]\AppData\Roaming\Microsoft\UserSecrets\[userSecretsId]\secrets.json

  其实AddUserSecrets是基于Json文件配置的一个实现,换句话说,我们只需要在上面的隐私文件secrets.json中配置数据,就可以集成到IConfiguration中。

  比如我在上面的目录下的secrets.json(C:\Users\Administrator\AppData\Roaming\Microsoft\UserSecrets\userSecretsId\secrets.json)内容如下:  

  

    {
"Secret": {
"Key1": {
"Key2": "Value2"
},
"Key3": "Value3"
}
}

secret.json

  我们这样访问:  

    [assembly: Microsoft.Extensions.Configuration.UserSecrets.UserSecretsId("userSecretsId")]

    namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var secretPath = Microsoft.Extensions.Configuration.UserSecrets.PathHelper.GetSecretsPathFromSecretsId("userSecretsId");
Console.WriteLine("secretPath目录在:" + secretPath); ConfigurationBuilder builder = new ConfigurationBuilder();
//builder.AddUserSecrets("userSecretsId");
builder.AddUserSecrets(typeof(Program).Assembly);
var configuration = builder.Build();
var collections = configuration.AsEnumerable();
foreach (var item in collections)
{
Console.WriteLine("{0}={1}", item.Key, item.Value);
}
}
}
}

  结果:

  

  Azure云

  NuGet安装包:Microsoft.Extensions.Configuration.AzureKeyVault

  这个和Azure有关,因为我们基本上不用Azure云,所以就不介绍了,感兴趣的可以看看源码,都挺简单,源码地址:https://github.com/dotnet/extensions/tree/v3.1.12/src/Configuration/Config.AzureKeyVault/src

  三、IConfiguration使用

  一般的,我们要获取IConfiguration或者IConfigurationRoot,都是结合DI容器来使用的,比如我们的Startup:  

    public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } ...
}

  1、在结构上,我们可以将IConfiguration看做一个Key-Value的集合,另一方面,因为Key的特殊性,我们又可以将它看做一种树形结构的数据集合。

  这里说的Key的特殊性,指的就是Key是有层级关系的,层级采用 ConfigurationPath.KeyDelimiter 字段标识来分隔,默认是冒号(:),不建议修改(采用反射可修改)。

  2、当我们配置由改动,需要重新加载时,可以调用 IConfigurationRoot 的 Reload 方法重新加载配置

  3、IConfiguration的GetSection方法可以获取某个节点信息,参数key是相对于当前节点的,其中IConfigurationSection中有三个属性:  

    Key:当前节点名,不包含路径
Path:从根节点到当前节点的路径
Value:当前节点数据

  4、IConfiguration的GetConnectionString方法获取的是当前节点下的ConnectionStrings节点下指定名称子节点的数据(源码):  

        public static string GetConnectionString(this IConfiguration configuration, string name)
{
return configuration?.GetSection("ConnectionStrings")?[name];
}

  5、IConfiguration有两个重要的方法:Get和Bind。

  Get方法将当前节点及其子节点的数据转换并存放到指定类型的实体对象的属性中,并返回改实体对象。

  Bind方法接收一个实体对象,并将当前节点的及其字节点的数据保存到改实体对象的属性中。

  举个简单的例子,比如我们在appsettings.json 中有配置 :  

  {
...
"Data": {
"Value1": 1,
"Value2": 3.14,
"Value3": true,
"Value4": [ 1, 2, 3 ],
"Value5": {
"Value1": 2,
"Value2": 5.20,
"Value3": false,
"Value4": [ 4,5,6,7 ]
}
}
}

  然后相对应的创建一个实体类:  

    public class TestData
{
public int Value1 { get; set; }
public decimal Value2 { get; set; }
public bool Value3 { get; set; }
public int[] Value4 { get; set; }
public TestData Value5 { get; set; }
}

  使用时的区别就是:  

    var section = configuration.GetSection("Data");
var data1= section.Get<TestData>();
var data2 = new TestData();
section.Bind(data2);

  这样一来,经过Get方法或者Bind方法,IConfiguration中的数据就放到我们熟悉的实体对象中去了,再也不用去做那些烦躁的字符串类型转换了!

  

  四、总结

  这一篇就先到这里吧,.net core的配置还是很简单的,随便看看源码就能掌握。

.net core的配置介绍(一):IConfiguration的更多相关文章

  1. .net core的配置介绍(二):自定义配置(Zookeeper,数据库,Redis)

    上一篇介绍了.net core的配置原理已经系统提供的一些常用的配置,但有时我们的配置是存放在Zookeeper,DB,Redis中的,这就需要我们自己去实现集成了. 这里再介绍几个我们用的多的配置集 ...

  2. .net core的配置介绍(三):Options

    前两篇介绍的都是已IConfiguration为基础的配置,这里在说说.net core提供的一种全新的辅助配置机制:Options. Options,翻译成中文就是选项,可选择的意思,它依赖于.ne ...

  3. ASP.NET Core 运行原理解剖[2]:Hosting补充之配置介绍

    在上一章中,我们介绍了 ASP.NET Core 的启动过程,主要是对 WebHost 源码的探索.而本文则是对上文的一个补充,更加偏向于实战,详细的介绍一下我们在实际开发中需要对 Hosting 做 ...

  4. ASP.NET Core的配置(5):配置的同步[设计篇]

    本节所谓的"配置同步"主要体现在两个方面:其一,如何监控配置源并在其变化的时候自动加载其数据,其目的是让应用中通过Configuration对象承载的配置与配置源的数据同步:其二. ...

  5. ASP.NET Core的配置(4):多样性的配置来源[上篇]

    较之传统通过App.config和Web.config这两个XML文件承载的配置系统,ASP.NET Core采用的这个全新的配置模型的最大一个优势就是针对多种不同配置源的支持.我们可以将内存变量.命 ...

  6. ASP.NET Core的配置(3): 将配置绑定为对象[上篇]

    出于编程上的便利,我们通常不会直接利用ConfigurationBuilder创建的Configuration对象读取某个单一配置项的值,而是倾向于将一组相关的配置绑定为一个对象,我们将后者称为Opt ...

  7. ASP.NET Core的配置(2):配置模型详解

    在上面一章我们以实例演示的方式介绍了几种读取配置的几种方式,其中涉及到三个重要的对象,它们分别是承载结构化配置信息的Configuration,提供原始配置源数据的ConfigurationProvi ...

  8. .Net Core 自定义配置源从配置中心读取配置

    配置,几乎所有的应用程序都离不开它..Net Framework时代我们使用App.config.Web.config,到了.Net Core的时代我们使用appsettings.json,这些我们再 ...

  9. ASP.NET Core Identity 配置 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core Identity 配置 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core Identity 配置 上一章节我们简单介绍了下 Id ...

随机推荐

  1. Android 清除本地缓存

    主要功能:清除内.外缓存,清除数据库,清除Sharepreference,清除files和清除自定义目录 public class DataCleanManager { //清除本应用内部缓存(/da ...

  2. 用oracle中的Row_Number实现分页

    Row_Number实现分页   1:首先是 select ROW_NUMBER() over(order by id asc) as 'rowNumber', * from table1 生成带序号 ...

  3. 时间同步——TSN协议802.1AS介绍

    前言之前的主题TSN的发展历史和协议族现状介绍了TSN技术的缘起,最近一期的主题TSN协议导读从定时与同步.延时.可靠性.资源管理四个方面,帮助大家了解TSN协议族包含哪些子协议,以及这些子协议的作用 ...

  4. ASP.NET VS 调试提示:指定的端口正在使用中,建议切换到xxx之外并大于1024的端口

    问题描述 使用 Visual Studio 开发 ASP.NET 网站的过程中,突然提示端口被占用: 解决方式 在启动项目上右键→属性,切换到 Web .直接修改服务器栏目里面的端口号,解决!

  5. Table.ReorderColumns移动…Reorder…(Power Query 之 M 语言)

    数据源: 至少两列 目标: 列顺序重新排列 操作过程: 选取待移动的列>鼠标拖放列标题 选取待移动的列>[转换]>[移动]>选取 M公式:  = Table.ReorderCo ...

  6. 数组基础(Excel函数集团)

    此处文章均为本妖原创,供下载.学习.探讨! 文章下载源是Office365国内版1Driver,如有链接问题请联系我. 请勿用于商业! 谢谢 下载地址:https://officecommunity- ...

  7. Arcpy按属性(字段值)不同将shp分割为多个独立shp_适用点线面矢量

    利用代码可以进行批量处理,安装有10.5及以上版本ArcGIS可以使用工具Split by attributes完成上述任务 # -*- coding: utf-8 -*- # Import syst ...

  8. CF667A Pouring Rain 题解

    Content 一个水桶直径为 \(d\) 厘米,初始时水面高度为 \(h\) 厘米.你每秒钟喝 \(v\) 毫升水,而由于下雨,水桶里面的水在不喝水的时候每秒会上升 \(e\) 厘米.求你最少需要多 ...

  9. CF1491A K-th Largest Value 题解

    Content 你有一个长度为 \(n\),并且仅包含 \(0/1\) 的数组 \(a\).现在对这个序列做以下两种操作之一共 \(q\) 次: \(1\) \(x\):将 \(a_x\) 修改为 \ ...

  10. Django 中间件理解

    中间件 django 中的中间件(middleware),在django中,中间件其实就是一个类,在请求到来和结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法. 应用场景,对所有 ...