配置系统与ASP.NET Core

默认添加的配置提供者

1.加载现有的IConfiguration。
2.加载项目根目录下的appsettings.json。
3.加载项目根目录下的appsettings.{Environment}.json。
4.当程序运行在开发环境下,程序会加载“用户机密”配置。
5.加载环境变量中的配置。
6.加载命令行中的配置。

配置的环境问题

1.开发环境、测试环境、生产环境需要进行不同配置。
2.运行环境:ASP.NET Core会从环境变量中读取名字为ASPNETCORE_ENVIRONMENT的值。推荐值:Development(开发环境)、Stagin(测试环境)、Production(开发环境)。
3.读取方法:app.Environment.EnvironmentName、app.Environment.IsDvelopment()…
4.在Windows和VS(推荐开发时用)中设置环境变量的方法。

在Program.cs中设置

1
2
3
Console.WriteLine(app.Environment.EnvironmentName);
Console.WriteLine(app.Environment.IsDevelopment());
Console.WriteLine(app.Environment.IsProduction());

在Controllers中设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestController : ControllerBase
{
private readonly IWebHostEnvironment webhost;

public TestController(IWebHostEnvironment webhost)
{
this.webhost = webhost;
}
[HttpGet]
public string Get()
{
return webhost.EnvironmentName;
}
}

.NET Core防止机密配置外泄

用“用户机密”来避免机密信息的泄露

1.把不方便放到appsettings.json中的机密信息放到一个不在项目中的json文件中。
2.在ASP.NET Core项目上单击鼠标右键,选择【管理用户机密】。
3.secrets.json文件到底保存在哪里?如何和项目建立关系?csproj文件中的<UserSecretsld>

这个节点的值就是一个用来定位用户机密配置的标识。

1
2
3
4
5
6
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>e049b89f-2772-408a-9073-938f00afc514</UserSecretsId>
</PropertyGroup>

读取配置

1
2
string name= app.Configuration.GetSection("name").Value;
Console.WriteLine(name);

在使用用户机密的时候有如下几点需要注意

1.用户机密机制是供开发人员使用的,因此不适合在生成环境中使用。
2.secrets.json中的配置任然是明文存储的,并没有加密。如果想避免连接字符串等机密配置被别人看到,可以采用Azure Key Vault等配置服务器。但是无论什么配置服务器,只要程序能读取这些配置,采用任何配置服务器的“连接字符串加密”只能增加机密信息被发现的难度,不能彻底杜绝机密信息被发现。
3.如果因为重装、就需要重新配置,麻烦。如果影响大的话,还是用集中式配置服务器。

配置系统综合

功能需求

1.系统的主要配置(Redis、Smtp)放到配置专用的数据库中。
2.连接配置数据库的连接字符串配置在“用户机密”中。
3.把Smtp的配置显示到界面上。
4.程序启动的时候就连接Redis,并且把Redis连接对象注册到依赖注入系统中。

第一步:在SQL Server数据库中创建一张保存配置信息的数据库表T_Configs,表包含Id、Name、Value这3列,Id列定义为整数类型的标识列,Name列和Value列都定义为字符串类型,Name列为配置项的名字,Value列为配置项的值。

第二步:在T_Configs表中增加两行数据。

第三步:在项目中创建一个SmtpOptions类用来绑定Smtp的配置值。

1
2
3
4
5
6
public class SmtpOptions
{
public string Host { get; set; } = string.Empty;
public string UserName { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}

第四步:通过NuGet安装从数据库中读取配置的Zack.AnyDBConfigProvider,以及连接Redis的Stack Exchange.Redis。

第五步:在项目上右击,选择【管理用户机密】。配置secrets.json。

1
2
3
4
5
{
"ConnectionStrings": {
"configServer": "Data Source=.;Initial Catalog=demo1;Integrated Security=SSPI;"
}
}

第六步:编写代码进行配置系统的初始化。在Program.cs文件的WebApplication.CreateBuilder(args)的下面添加。

安装NuGet包StackExchange.Redis

1
2
3
4
5
6
7
8
9
10
11
builder.Host.ConfigureAppConfiguration((_, configBuilder) =>
{
string connStr=builder.Configuration.GetConnectionString("ConnectionStrings");
configBuilder.AddDbConfiguration(() => new SqlConnection(connStr),reloadOnChange:true,reloadInterval:TimeSpan.FromSeconds(2));
});
builder.Services.Configure<SmtpOptions>(builder.Configuration.GetSection("Smtp"));
builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
{
string connStr = builder.Configuration.GetValue<string>("Redis:ConnStr");
return ConnectionMultiplexer.Connect(connStr);
});

第七步:在控制器中通过构造方法注入获取SmtpOptions和Redis连接对象。

1
2
3
4
5
6
7
8
9
10
11
12
builder.Host.ConfigureAppConfiguration((_, configBuilder) =>
{
string connStr = builder.Configuration.GetConnectionString("configServer");
configBuilder.AddDbConfiguration(() => new SqlConnection(connStr), reloadOnChange: true, reloadInterval: TimeSpan.FromSeconds(2));
});
builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
{
string connStr = builder.Configuration.GetSection("Redis").Value;
return ConnectionMultiplexer.Connect(connStr);
});
builder.Services.Configure<SmtpOptions>(builder.Configuration.GetSection("Smtp"));

EF Core与ASP.NET Core的集成

EF Core可以用于所有.NET Core平台下的程序,ASP.NET Core也不例外。在ASP.NET Core中使用EF Core的时候还有一些需要额外注意的问题。

分层项目中EF Core的用法

在编写简单的演示案例的时候,我们通常会把项目的所有代码放在同一个文件夹中,而对于现实中比较复杂的项目,我们通常是要 其进行分层的,也就是不同的类放到不同的文件夹中。这样的分层项目中使用EF Core的时候有一些问题需要考虑。

1.创建一个.NET类库项目,项目名字为BooksEFCore。
NuGet:Microsoft.EntityFrameworkCore.Relational
并且在项目中增加代表图书的实体类Book和它的实体类的配置类BookConfig.

1
2
3
4
5
6
7
8
public class Book
{
public long Id { get; set; }
public string Title { get; set; }
public string AuthorName { get; set; }
public double Price { get; set; }
public DateTime PuDate { get; set; }
}
1
2
3
4
5
6
7
class BookConfig : IEntityTypeConfiguration<Book>
{
public void Configure(EntityTypeBuilder<Book> builder)
{
builder.ToTable("T_Book");
}
}

这里,这里把Book类声明为一个记录类,而不是普通的类,主要是为了让编译器自动生成ToString方法,帮我们简化对象的输出。

2.在BookEFCore项目中增加上下文类。

数据库的配置1

1.上下文类MyDbContext:为什么正式项目中最好别不要在MyDbContext写数据库配置(连接不同的DB甚至不同类型的DB)。尽量数据库配置的代码写到ASP.NET Core项目中。不重复OnConfiguring方法,而是为MyDbContext类的构造方法增加DbContextOptions<MyDbContext>参数。在ASP.NET Core项目对DbContextOptions的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyDbContext : DbContext
{

public DbSet<Book> Books { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
new BookConfig().Configure(modelBuilder.Entity<Book>());
}
}

2.创建ASP.NET Core项目,添加对“BooksEFCore”项目的引用。NuGet安装
Microsoft.EntityFrameworkCore.SqlServer
在ASP.NET Core项目的appsettings.json中增加对数据库连接字符串的配置。

1
2
3
"ConnectionStrings": {
"Default": "Server=localhost;Database=T_dome;Trusted_Connection=True;MultipleActiveResultSets=true;Encrypt=false;"
}

3.配置文件、配置代码等放到ASP.NET Core项目中。

1
2
3
4
5
builder.Services.AddDbContext<MyDbContext>(opt =>
{
string Default = builder.Configuration.GetConnectionString("Default");
opt.UseSqlServer(Default);
});

使用AddDbContext方法来通过依赖注入的方式让MyDbContext采用我们指定的连接字符串连接数据库。由于AddDbContext方法是泛型的,因此我们可以为同一个项目中的多个不同的上下文设定连接不同的数据库。

4.在ASP.NET Core想买中增加使用MyDbContext进行数据库读写的测试代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TextController : ControllerBase
{
private readonly MyDbContext dbCtx;

public TextController(MyDbContext dbCtx)
{
this.dbCtx = dbCtx;
}
[HttpGet]
public async Task<IActionResult> Index()
{
dbCtx.Add(new Book { Id=Guid.NewGuid(),AuthorName= "AuthorName", Price = 100, PuDate = DateTime.Now, Title = "Title" });
await dbCtx.SaveChangesAsync();
var book=dbCtx.Books.First();
return Content(book.ToString());
}
}

5.生成实体类的迁移脚本。安装Microsoft.EntityFrameworkCore.Tools。然后把EFCoreBook设置为启动项目,并且在【程序包管理器控制台】中也选中EFCoreBook项目后,执行Add-Migration和Update-Database

6.再执行Add-Migration的时候,迁移工具就会提示:Unable to create a ‘DbContext’ of type ‘RuntimeType’.
数据库迁移的时候出现这种错误,采用IDesignTimeDbContextFactory接口来解决这个问题。
当项目中存在一个IDesignTimeDbContextFactory接口的实现类的时候,数据库迁移工具就会调用这个实现类的CreateDbContext方法来获取上下文对象,然后迁移工具会使用这个上下文对象来连接数据库。因此,我们需要在BooksEFCore项目创建一个IDesignTimeDbContextFactory接口的实现类。

1
2
3
4
5
6
7
8
9
10
class MyDesignTimeDbContextFactroy : IDesignTimeDbContextFactory<MyDbContext>
{
public MyDbContext CreateDbContext(string[] args)
{
DbContextOptionsBuilder<MyDbContext> builder = new DbContextOptionsBuilder<MyDbContext>();
string ConnStr="Server=localhost;Database=T_dome3;Trusted_Connection=True;MultipleActiveResultSets=true;Encrypt=false;";
builder.UseSqlServer(ConnStr);
return new MyDbContext(builder.Options);
}
}

但有数据泄露的风险