Reputation: 12610
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
Reputation: 470
I'm answering after a year to provide clean solutions for AutoFac v6
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;
}
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
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
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
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