2

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?

AndyNZ
  • 2,131
  • 4
  • 24
  • 27

1 Answers1

1

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://learn.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.

AndyNZ
  • 2,131
  • 4
  • 24
  • 27
  • Yes, you'll see ASP.NET Core 2.0 move toward this model, so that logging is available in Startup. Also, see here: https://ardalis.com/logging-and-using-services-in-startup-in-aspnet-core-apps – ssmith Jul 17 '17 at 12:30