且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

当EF迁移不通过Program.cs时,在Program.cs中配置依赖关系(例如日志记录)

更新时间:2022-11-11 22:30:16

好的,所以我找到了一个解决方案。



而不是将ILoggerFactory通过启动.cs构造函数。通过ILogger如下:

 私人ILogger<启动> _logger; 
public Startup(IHostingEnvironment env,ILogger< Startup> logger){
_env = env;
_logger = logger;
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile(appsettings.json,可选:false,reloadOnChange:true)
.AddJsonFile $appsettings。{env.EnvironmentName} .json,可选:true)
.AddEnvironmentVariables();

_configuration = builder.Build();
}

我的Program.cs看起来像:

  var host = new WebHostBuilder()
.UseKestrel()
.UseConfiguration(config)
.ConfigureServices(s => s.AddSingleton< IConfigurationRoot>(config))
.ConfigureLogging(f => {
f.AddConsole()
.AddDebug();
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup< Startup>()
.Build();

host.Run();

这是因为我们的启动构造函数通过DI容器传递ILogger(即使通过EF迁移运行,我们还没有配置一个记录器)。



我在阅读之后想到了这一点: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup (在启动中提供的服务中) / p>

现在...我遇到了这么多令人沮丧的问题,如.NET核心,我猜这可能是因为它还处于起步阶段,***的做法等等仍然在改善,因为我读过的大多数指南都没有这样做。



我觉得这也是部分原因是转向约定配置在.NET核心 - 这显然有缺点。使用类似C#的强类型语言的好处是可以让编译器帮助你在运行时间之前,以确定这样的问题。



(记住,使用ILoggerFactory,所有内容都可以正常运行,直到您使用无法访问添加的服务的EF进行迁移Program.cs)



很高兴听到人们的想法。我相信其他人必须经历同样的挫折。不要误会我,我喜欢.NET的核心,但是在这个应用程序的开发过程中已经有这么多次,在这样的最简单的愚蠢问题上,我已经停留了几个小时(有时更长)写代码。


I will try to explain my situation as best as possible and I hope it makes sense.

My project is a .NET core Web API. With a seperate class library project which contains models including my DbContext stuff.

Problem #1 I want to be able to Log to console from within Startup.cs

Reason: I am currently debugging setting a variable from an environment variable. I want to output what has been set to console.

Example: How do I write logs from within Startup.cs

Now, the solution was to add ILoggerFactory to the Service container in Program.cs as such:

var host = new WebHostBuilder()
        .UseKestrel()
        .ConfigureServices(s => {
            s.AddSingleton<IFormatter, LowercaseFormatter>();
        })
        .ConfigureLogging(f => f.AddConsole(LogLevel.Debug))
        .UseStartup<Startup>()
        .Build();

        host.Run();

Next, change Startup.cs constructor to take in ILoggerFactory that will be taken from the container that we just registered. As follows:

public class Startup {

  ILogger _logger;
  IFormatter _formatter;

  public Startup(ILoggerFactory loggerFactory, IFormatter formatter){
    _logger = loggerFactory.CreateLogger<Startup>();
    _formatter = formatter;
  }

  public void ConfigureServices(IServiceCollection services)  {
    _logger.LogDebug($"Total Services Initially: {services.Count}");

    // register services
    //services.AddSingleton<IFoo, Foo>();
  }

  public void Configure(IApplicationBuilder app, IFormatter formatter) {
    // note: can request IFormatter here as well as via constructor
    _logger.LogDebug("Configure() started...");
    app.Run(async (context) => await context.Response.WriteAsync(_formatter.Format("Hi!")));
    _logger.LogDebug("Configure() complete.");
  }

This solved my problem - I can run the application and it works fine now, logging where I need it.

HOWEVER.

When I then attempt to run dotnet ef database update --startup-project=myAPIProject

It now fails, because EF does not go via Program.cs, it instead attempts to directly instantiate my Startup class. And because now my constructor requires an ILoggerFactory, EF doesn't know what to do and throws an exception.

Does anyone know a way around this issue?

Alright, so I found a solution.

Instead of passing ILoggerFactory to Startup.cs constructor. Pass ILogger as follows:

private ILogger<Startup> _logger;
    public Startup(IHostingEnvironment env, ILogger<Startup> logger) {
        _env = env;
        _logger = logger;
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();

            _configuration = builder.Build();
    }

My Program.cs looks like:

    var host = new WebHostBuilder() 
            .UseKestrel()
            .UseConfiguration(config)
            .ConfigureServices(s => s.AddSingleton<IConfigurationRoot>(config))
            .ConfigureLogging(f => {
                f.AddConsole()
                .AddDebug();
            })
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();

        host.Run();

This is because our Startup constructor gets passed ILogger by the DI container (even when running via EF migrations and we haven't configured a logger).

I figured this out after reading: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup (under "Services Available in Startup).

Now... I have encountered so many frustrating issues like this with .NET core. I'm guessing it may be because it is still in it's infancy, and documentation of best practice etc is still improving. Because the majority of guides I have read have not done it this way.

I feel it's also partly because of the shift to "convention over configuration" in .NET core - which obviously comes with downsides. The beauty of working with a strongly typed language like C# is that you can have the compiler help you before Runtime to identify issues like this.

(Remembering that everything was working fine with ILoggerFactory, until you ran a migration using EF that doesn't have access to services added in Program.cs)

Keen to hear peoples thoughts on this. I'm sure other people must be experiencing the same frustrations. Don't get me wrong I love .NET core, but there have been so many times during the development of this application where I have been stuck for hours (sometimes longer) on the simplest stupid issues like this, which could have been spent just writing code.