Injecting ILogger like all the cool ASP.NET Core kids

Cross posted from https://dev.givetheiron.se/2019/10/26/injecting-ilogger-like-all-the-cool-asp-net-core-kids/

Just about every service class used in our ASP.NET Classic sites need a logger. The Nlog way to do that is to put this in your class

private static readonly NLog.Logger Logger =
    NLog.LogManager.GetCurrentClassLogger();

But after setting up logging in ASP.NET Core I really wanted some ILogger Dependency Injecting magic. With Dependency Injecting being a fundamental part of ASP.NET Core, setting up an ILogger is done by Injecting the logger into the constructor

public class WorkingClass
{
    private readonly ILogger _logger;
    public WorkingClass(ILogger<WorkingClass> logger)
    {
        _logger = logger;
    }

Injecting the logger not only makes the code cleaner, it’s also far easier to test. So how do I get me a piece of that action?

StructureMap to the rescue

Note that we use StructureMap as our Dependency Injection Framework, but I’m sure the following has similar ways in your favorite Dependency Injecting framework. Anyhoo, to setup the Injection we use the StructureMap Registry

public class DomainRegistry : Registry
{
    public DomainRegistry()
    {
        Scan(
            scan => {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
            });
				
        For<ILogger>().AlwaysUnique().Use(c => GetLogger(c));
    }
    private ILogger GetLogger(IContext context)
    {
        return NLog.LogManager.GetLogger(
            context.ParentType?.FullName ?? "Datema.EasyShop");
    }
}

So lets break it down. First line of interest

For<ILogger>().AlwaysUnique().Use(c => GetLogger(c));

Since the Logger is unique per class AlwaysUnique() is added after the For<ILogger>(), which will make sure the call to Use(…) gets called for each object that want’s a logger. The last part is Use(c => GetLogger(c)); which is required since it’s not allowed to have a null propagating operator in a lambda expression. Why use one of those? Lets find out!

return NLog.LogManager.GetLogger(
    context.ParentType?.FullName ?? "Datema.EasyShop");

The interesting bit here is what’s passed into the GetLogger call. The StructureMap IContext we get from the Use(c => GetLogger(c)); call knows which type it’s creating the ILogger for, and it’s type is stored in context.ParentType. That means we can use the ParentType.FullName to create a new Logger with the full name of the class being created. That’s exactly what we want to achieve, so why the whole ”?.” or null propagating operator? The DomainRegistry will eventually be inserted into a StructureMap Container, and on startup of the application, there’s a call to container.AssertConfigurationIsValid(); to make sure it has everything it needs. The call to AssertConfigurationIsValid() works by attempting to create every object the IContainer is tracking. This means it will attempt to create a ILogger directly, without a value set in context.ParentType since there is none. With more complex DI scenarios it’s easy to miss something, and failing on startup is better than later on when some specific part of the system is accessed.

Lämna en kommentar