Я, честно говоря, не могу поверить, насколько это сложно... Прежде всего, требования, к которым я иду:
- Реализация Entity Framework Core 2.0 '
IDesignTimeDbContextFactory
которая переименована в IDbContextFactory, чтобы меньше сбивать с толку разработчиков тем, что она делает - Я не хочу делать загрузку
appsettings.json
более одного раза. Одна из причин заключается в том, что мои миграции выполняются в доменеMyClassLibrary.Data
и в этой библиотеке классов нет файлаappsettings.js
, поэтому мне придетсяCopy to Output Directory
appsettings.js
. Другая причина в том, что это просто не очень элегантно.
Итак, вот что у меня сейчас работает:
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);
}
}
}
А вот и мой 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();
}
}
}
И вот мой 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());
}
}
}
Я хотел бы использовать шаблон 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; }
}
}
Добавил эту строку в Startup.ConfigureServices
:
services.Configure<AppSettings>(options => Configuration.GetSection("AppSettings").Bind(options));
А затем попытался изменить мою реализацию 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);
}
}
К сожалению, это не работает, потому что Ioptions<AppSettings>
аргумент public DesignTimeDbContextFactory(IOptions<AppSettings> appSettings)
конструктор не вводили. Я предполагаю, что это потому, что реализации IDesignTimeDbContextFactory<AppContext>
вызываются во время разработки, а внедрение зависимостей просто не "готово" в приложениях .NET Core во время разработки?
Мне кажется странным, что так сложно внедрить строку подключения для конкретной среды, используя шаблон Entity Framework Core 2.0 для реализации IDesignTimeDbContextFactory
, а также не нужно копировать и загружать файлы настроек, такие как appsettings.json
более одного раза.