Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 404
n0rd Avatar asked Jan 05 '23 02:01

n0rd


1 Answers

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>();
like image 138
n0rd Avatar answered Jan 13 '23 13:01

n0rd