[英]Injecting Env Conn String into .NET Core 2.0 w/EF Core DbContext in different class lib than Startup prj & implementing IDesignTimeDbContextFactory
I honestly cannot believe how hard this is...first off the requirements that I am going for: 老实说,我不敢相信这有多难......首先是我要求的要求:
IDesignTimeDbContextFactory
which is IDbContextFactory renamed to be less confusing to developers as to what it does 实现实体框架Core IDesignTimeDbContextFactory
, IDbContextFactory被重命名为开发人员不会混淆它的作用 appsettings.json
more than once. 我不想不止一次加载appsettings.json
。 One reason is because my migrations are running in the domain of MyClassLibrary.Data
and there is no appsettings.js
file in that class library, I would have to to Copy to Output Directory
appsettings.js
. 一个原因是因为我的迁移在MyClassLibrary.Data
域中运行,并且该类库中没有appsettings.js
文件,我将不得不Copy to Output Directory
appsettings.js
。 Another reason is that it just not very elegant. 另一个原因是它不是很优雅。 So here is what I have that currently works: 所以这就是我目前的工作方式:
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using AppContext = Tsl.Example.Data.AppContext;
namespace Tsl.Example
{
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppContext>
{
public AppContext CreateDbContext(string[] args)
{
string basePath = AppDomain.CurrentDomain.BaseDirectory;
string envName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(basePath)
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{envName}.json", true)
.Build();
var builder = new DbContextOptionsBuilder<AppContext>();
var connectionString = configuration.GetConnectionString("DefaultConnection");
builder.UseMySql(connectionString);
return new AppContext(builder.Options);
}
}
}
And here is my Program.cs: 这是我的Program.cs:
using System.IO;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Tsl.Example
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
//public static IWebHost BuildWebHost(string[] args) =>
// WebHost.CreateDefaultBuilder(args)
// .UseStartup<Startup>()
// .Build();
/// <summary>
/// This the magical WebHost.CreateDefaultBuilder method "unboxed", mostly, ConfigureServices uses an internal class so there is one piece of CreateDefaultBuilder that cannot be used here
/// https://andrewlock.net/exploring-program-and-startup-in-asp-net-core-2-preview1-2/
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
public static IWebHost BuildWebHost(string[] args)
{
return new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
IHostingEnvironment env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
if (env.IsDevelopment())
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
//.UseIISIntegration()
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
})
.UseStartup<Startup>()
.Build();
}
}
}
And here is my Startup.cs: 这是我的Startup.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using ServiceStack;
using Tsl.Example.Interfaces;
using Tsl.Example.Provider;
using AppContext = Tsl.Example.Data.AppContext;
namespace Tsl.Example
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IAppContext, AppContext>();
services.AddTransient<IExampleDataProvider, ExampleDataProvider>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseServiceStack(new AppHost());
}
}
}
What I would like to do is use the IOptions pattern , so I created this class: 我想要做的是使用IOptions模式 ,所以我创建了这个类:
namespace Tsl.Example
{
/// <summary>
/// Strongly typed settings to share in app using the .NET Core IOptions pattern
/// https://andrewlock.net/how-to-use-the-ioptions-pattern-for-configuration-in-asp-net-core-rc2/
/// </summary>
public class AppSettings
{
public string DefaultConnection { get; set; }
}
}
Added this line to Startup.ConfigureServices
: 将此行添加到Startup.ConfigureServices
:
services.Configure<AppSettings>(options => Configuration.GetSection("AppSettings").Bind(options));
And then tried and change my implementation of IDesignTimeDbContextFactory<AppContext>
to: 然后尝试将IDesignTimeDbContextFactory<AppContext>
实现更改为:
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppContext>
{
private readonly AppSettings _appSettings;
public DesignTimeDbContextFactory(IOptions<AppSettings> appSettings)
{
this._appSettings = appSettings.Value;
}
public AppContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<AppContext>();
builder.UseMySql(_appSettings.DefaultConnection);
return new AppContext(builder.Options);
}
}
Unfortunately this did not work because the Ioptions<AppSettings>
argument of public DesignTimeDbContextFactory(IOptions<AppSettings> appSettings)
constructor is not injected. 不幸的是,这不起作用,因为未注入public DesignTimeDbContextFactory(IOptions<AppSettings> appSettings)
构造函数的Ioptions<AppSettings>
参数。 I assume this is because implementations of IDesignTimeDbContextFactory<AppContext>
are called at Design time and dependency injection is just not "ready" in .NET Core apps at design time? 我假设这是因为IDesignTimeDbContextFactory<AppContext>
是在设计时调用的,并且依赖注入在.NET Core应用程序设计时并没有“准备就绪”?
I think it is kind of strange that it is so hard to inject an environment specific connection string using the Entity Framework Core 2.0 pattern of implementing IDesignTimeDbContextFactory
, and also not having to copy and load settings files like appsettings.json
more than once. 我认为使用实现IDesignTimeDbContextFactory
的Entity Framework Core 2.0模式注入特定于环境的连接字符串是如此困难,并且不必IDesignTimeDbContextFactory
复制和加载appsettings.json
等设置文件。
If you are looking for solution to get database connection string from your custom settings class initialized from appsettings.json
file - that is how you can do this. 如果您正在寻找从appsettings.json
文件初始化的自定义设置类中获取数据库连接字符串的解决方案 - 这就是您可以执行此操作的方法。 Unfortunatelly you can't inject IOptions
via DI
to your IDesignTimeDbContextFactory
implementation constructor. 不幸的是,您无法通过DI
将IOptions
注入IDesignTimeDbContextFactory
实现构造函数。
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppContext>
{
public AppContext CreateDbContext(string[] args)
{
// IDesignTimeDbContextFactory is used usually when you execute EF Core commands like Add-Migration, Update-Database, and so on
// So it is usually your local development machine environment
var envName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
// Prepare configuration builder
var configuration = new ConfigurationBuilder()
.SetBasePath(Path.Combine(Directory.GetCurrentDirectory()))
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile($"appsettings.{envName}.json", optional: false)
.Build();
// Bind your custom settings class instance to values from appsettings.json
var settingsSection = configuration.GetSection("Settings");
var appSettings = new AppSettings();
settingsSection.Bind(appSettings);
// Create DB context with connection from your AppSettings
var optionsBuilder = new DbContextOptionsBuilder<AppContext>()
.UseMySql(appSettings.DefaultConnection);
return new AppContext(optionsBuilder.Options);
}
}
Of course in your AppSettings
class and appsettings.json
you could have even more sophisticated logic of building the connection string. 当然,在AppSettings
类和appsettings.json
您可以拥有构建连接字符串的更复杂的逻辑。 For instance, like this: 例如,像这样:
public class AppSettings
{
public bool UseInMemory { get; set; }
public string Server { get; set; }
public string Port { get; set; }
public string Database { get; set; }
public string User { get; set; }
public string Password { get; set; }
public string BuildConnectionString()
{
if(UseInMemory) return null;
// You can set environment variable name which stores your real value, or use as value if not configured as environment variable
var server = Environment.GetEnvironmentVariable(Host) ?? Host;
var port = Environment.GetEnvironmentVariable(Port) ?? Port;
var database = Environment.GetEnvironmentVariable(Database) ?? Database;
var user = Environment.GetEnvironmentVariable(User) ?? User;
var password = Environment.GetEnvironmentVariable(Password) ?? Password;
var connectionString = $"Server={server};Port={port};Database={database};Uid={user};Pwd={password}";
return connectionString;
}
}
With just values stored in appsettings.json
: 只有存储在appsettings.json
值:
{
"Settings": {
"UseInMemory": false,
"Server": "myserver",
"Port": "1234",
"Database": "mydatabase",
"User": "dbuser",
"Password": "dbpassw0rd"
}
}
With password and user stored in environment variables: 密码和用户存储在环境变量中:
{
"Settings": {
"UseInMemory": false,
"Server": "myserver",
"Port": "1234",
"Database": "mydatabase",
"User": "MY-DB-UID-ENV-VAR",
"Password": "MY-DB-PWD-ENV-VAR"
}
}
In this case you should use it this way: 在这种情况下,您应该这样使用它:
// Create DB context with connection from your AppSettings
var optionsBuilder = new DbContextOptionsBuilder<AppContext>();
if(appSettings.UseInMemory) {
optionsBuilder = appSettings.UseInMemory
? optionsBuilder.UseInMemoryDatabase("MyInMemoryDB")
: optionsBuilder.UseMySql(appSettings.BuildConnectionString());
return new AppContext(optionsBuilder.Options);
I am a bit confused with your question. 我对你的问题有点困惑。 Are you using dependency injection for the DbContext
or are you trying to initialize and construct the context ad hoc? 您是否正在为DbContext
使用依赖注入,或者您是否尝试初始化并构建特定的上下文?
I am doing what you have described in one of my solutions. 我正在做我在我的一个解决方案中描述的内容。 Here is my solution structure: 这是我的解决方案结构:
Startup.cs Startup.cs
public Startup(IHostingEnvironment env)
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json")
.AddEnvironmentVariables();
// ...
}
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<MyDbContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
sqlOptions => sqlOptions.EnableRetryOnFailure()));
// SQL configuration for non-injected dbcontext
DbContextOptionsBuilder<MyDbContext> builder = new DbContextOptionsBuilder<MyDbContext>();
builder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
services.AddSingleton(builder.Options);
// ...
}
MyDbContext.cs MyDbContext.cs
public class MyDbContext : IdentityDbContext<ApplicationUser>
{
public MyDbContext(DbContextOptions options) : base(options) { }
}
If you are not using dependency injection to pass the DbContext, you can access the SQL properties by injecting DbContextOptions<MyDbContext>
instead. 如果您没有使用依赖项注入来传递DbContext,则可以通过注入DbContextOptions<MyDbContext>
来访问SQL属性。
In this example, the appsettings file is only every read once and everything just works. 在这个例子中,appsettings文件只是每次读取一次,一切正常。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.