n0rd
n0rd

Reputation: 12610

Factory for generic interfaces with Autofac

I have an ILoggerFactory implementation registered in Autofac container. I want to be able to register the following class with Autofac and be able to instantiate it:

public class Test
{
    public Test(ILogger<Test> logger)
    {
        logger.LogInformation("Works!");
    }
}

For that I need to be able to 'teach' Autofac to produce instances implementing the ILogger<T> for any class T. There are an ILoggerFactory extension methods CreateLogger<T>() and CreateLogger(Type) that are able to produce instances of required type, but I can't wrap my head around how to make Autofac to call them.

If I wanted non-generic ILogger, I'd write

containerBuilder.Register(c => c.Resolve<ILoggerFactory>().CreateLogger("default"))

but I'm not sure what to do for the generic one. Any pointers?

Update: After reading some more documentation, I came up with this:

public class LoggingModule: Autofac.Module
{
    private readonly MethodInfo mi;

    public LoggingModule()
    {
        this.mi = typeof(LoggerFactoryExtensions)
            .GetMethod(nameof(LoggerFactoryExtensions.CreateLogger), new[] { typeof(ILoggerFactory) });
    }

    protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
    {
        registration.Preparing += OnComponentPreparing;
    }

    private void OnComponentPreparing(object sender, PreparingEventArgs e)
    {
        e.Parameters = e.Parameters.Union(
            new[]
            {
                new ResolvedParameter(
                    (p, i) => IsGenericLoggerParameter(p.ParameterType),
                    (p, i) => CreateLogger(this.mi, p.ParameterType, i.Resolve<ILoggerFactory>())
                    )
            });
    }

    private static bool IsGenericLoggerParameter(Type parameterType)
    {
        return parameterType.IsGenericType && parameterType.GetGenericTypeDefinition() == typeof(ILogger<>);
    }

    private static object CreateLogger(MethodInfo mi, Type typeArg, ILoggerFactory loggerFactory)
    {
        Type genericArg = typeArg.GetGenericArguments().First();
        MethodInfo generic = mi.MakeGenericMethod(new[] { genericArg });
        return generic.Invoke(null, new[] { loggerFactory });
    }
}

And then:

        containerBuilder.RegisterType<Test>().AsSelf();
        containerBuilder.RegisterModule<LoggingModule>();
        var container = containerBuilder.Build();
        var test = container.Resolve<Test>();

works. Issue is I don't fully understand how it works (and, more importantly, how it might break), so I'm still looking for more elegant and/or understandable solutions.

Upvotes: 4

Views: 3084

Answers (4)

Avius
Avius

Reputation: 470

I'm answering after a year to provide clean solutions for AutoFac v6

If you want to add Microsoft.Extensions.Logging ...

This cleanest way to add ILogger<T> (From Microsoft.Extensions.Logging) with AutoFac is to register Microsoft.Extensions.Logging.ILoggerFactory implementation, and Microsoft.Extensions.Logging.Logger<T> generic class.

The Logger<T> class is decorator class, inside Microsoft.Extensions.Logging.Abstractions library, which wraps the actual ILogger implementation, but doesn't actually implement concrete logger (console logger, EventLog logger, etc.). The Logger<T> has constructor with parameter of type ILoggerFactory, which is used to create ILogger concrete implementation.

Solution

static ContainerBuilder CreateBuilder()
{
    var builder = new ContainerBuilder();

    builder.RegisterInstance(LoggerFactory.Create(config => config
        .AddConsole()
        .SetMinimumLevel(LogLevel.Trace)
    ));

    builder.RegisterGeneric(typeof(Logger<>)).As(typeof(ILogger<>));

    return builder;
}

If you want to register generic service with complicated constructor logic...

As for the literal answer to the question: I would recommend to use RegisterGeneric method.

Example

builder.RegisterGeneric((context, types) =>
{
    var factory = context.Resolve<ILoggerFactory>();
    return factory.CreateLogger(types[0].FullName);
}).As(typeof(ILogger<>));

Upvotes: 2

snerting
snerting

Reputation: 380

When using Autofac and need to inject Microsoft ILogger< T > into objects, you must add the following to the ContainerBuilder

ContainerBuilder builder = new ContainerBuilder();
var loggerFactory = new LoggerFactory();
//loggerFactory.AddProvider(LogProvider) //If you need another provider than Microsoft logger
builder.RegisterInstance(loggerFactory).As<ILoggerFactory>().SingleInstance();
builder.RegisterGeneric(typeof(Logger<>)).As(typeof(ILogger<>)).SingleInstance();

Object who need to inject ILogger< T > will now get the correct ILogger

Upvotes: 2

maxfridbe
maxfridbe

Reputation: 5970

YAY so glad you posted this: With your example I got it working for ILogger and ILogger

  public class LoggingDependencyRegistrationModule : Autofac.Module
{
    private readonly ILoggerFactory _lf;
    private readonly MethodInfo _mi;
    private readonly Type _t;

    public LoggingDependencyRegistrationModule(ILoggerFactory lf)
    {
        _lf = lf;
        _mi = typeof(LoggerFactoryExtensions).GetMethod(nameof(LoggerFactoryExtensions.CreateLogger), new[] { typeof(ILoggerFactory) });
        _t = typeof(ILogger);
    }
    private void OnComponentPreparing(object sender, PreparingEventArgs e)
    {
        var t = e.Component.Activator.LimitType;
        e.Parameters = e.Parameters.Union(new[]
        {
            new ResolvedParameter(
                (p, i) =>
                            _t.IsAssignableFrom(p.ParameterType),
               (p,i)=>{

                    if(p.ParameterType == _t)
                       return _lf.CreateLogger(t.ToString());

                    return  CreateLogger(_mi, p.ParameterType, _lf);
                   }
               )
        });
    }
    private static object CreateLogger(MethodInfo mi, Type typeArg, ILoggerFactory loggerFactory)
    {
        Type genericArg = typeArg.GetGenericArguments().First();
        MethodInfo generic = mi.MakeGenericMethod(new[] { genericArg });
        return generic.Invoke(null, new[] { loggerFactory });
    }


    protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
    {
        registration.Preparing += OnComponentPreparing;
    }
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterInstance(_lf).SingleInstance();
        base.Load(builder);
    }
}

Upvotes: 0

n0rd
n0rd

Reputation: 12610

I found out a simple answer, but only for the ILogger<T> from the Microsoft.Extensions.Logging package: using the ServiceCollection from the Microsoft.Extensions.DependencyInjection package:

        IServiceCollection services = new ServiceCollection();
        services.AddLogging(); // all the magic happens here
        // set up other services 
        var builder = new ContainerBuilder();
        builder.Populate(services); // from the Autofac.Extensions.DependencyInjection package
        IContainer container = builder.Build();
        Test t = container.Resolve<Test>();

Upvotes: 6

Related Questions