1

Title. I have a non-generic logging abstraction and some implementations might be generic, e.g. Microsoft logging.

public interface ICustomLogger
{
  void Log();
}

public class NLogLogger : ICustomLogger
{
  private NLog.ILogger _logger;

  void Log() => _logger.Log();
}

public class MicrosoftLogger<T> : ICustomLogger
{
  private Microsoft.ILogger<T> _logger;

  void Log() => _logger.Log();
}

How to setup MS.DI container so that it could resolve generic implementation where an interface is needed? Example of usage:

public class LoggerConsumer
{
  private readonly ICustomLogger _logger;

  public LoggerConsumer(ICustomLogger thereShouldBeMicrosoftGenericLogger)
  {
    _logger = thereShouldBeMicrosoftGenericLogger;
  }
}

I'd like not to change my ICustomLogger to ICustomLogger<T> signature, because only Microsoft logging seems to be generic.

  • Should this be `public class MicrosoftLogger`? If so, you would need to provide the type argument at registration e.g. `AddSingleton>()` or create a factory and inject that. – Johnathan Barclay Feb 22 '22 at 09:33
  • Well, yeah. It should be `MicrosoftLogger`. In that case, I don't know how to resolve class type, because `ILogger` is used like `ILogger`. And I need to somehow resolve `ILogger` to `MicrosoftLogger : ILogger`. Imagine I have more than one class that requires logging (in case I can't centralize that activity). Is there any alternative to not registering **every** class? You suggest a factory that would create ILogger instance in constructor? Isn't it some kind of ServiceLocator? Because it's an anti-pattern then. – Nikita Kalimov Feb 22 '22 at 09:40
  • So should `MicrosoftLogger` also implement `ILogger` rather than just `ILogger` as in your example? If so, you can register it as an "_open generic_" e.g. `AddSingleton(typeof(ILogger<>), typeof(MicrosoftLogger<>))`. Otherwise a factory is your only option. – Johnathan Barclay Feb 22 '22 at 09:45
  • I've clarified the question. There is no such thing as `ILogger` from **my** perspective. There is only `ICustomLogger` (I've changed the signatures). The question is about generic implementations to non-generic interfaces. I know for sure that MS.DI container won't allow multiple interface implementations, so there might be a way to resolve generic implementations without resorting to ServiceLocator, factories or delegates like `p => new MyClass(p.GetRequiredService>())` If there is none, then my design decision is a bad one. And that's what I want to find out. – Nikita Kalimov Feb 22 '22 at 10:04
  • So you effectively want to register an open generic type, with a single generic parameter, as the implementation of a non-generic interface, and have that generic argument inferred as the type that it is being injected into. No container will support that without explicit configuration, such as your delegate example above. Why not introduce a generic `ICustomLogger`? – Johnathan Barclay Feb 22 '22 at 10:48
  • In that case generic would be introduced only because of the Microsoft implementation and the interface is designed by the business needs, not developers'. Or maybe I'm wrong and its develops' needs actually. I need to clarify that. Anyway, thank you for your input on the topic. Looks like I'll have to introduce generic version of the interface but also find a compelling reason to justify it. – Nikita Kalimov Feb 23 '22 at 10:10

1 Answers1

0

It is possible using Autofac.

If you use only the MS logging then you can create an Autofac module that will resolve the appropriate MS logger type for you.

using Autofac;
using Autofac.Core;
using Autofac.Core.Registration;
using Autofac.Core.Resolving.Pipeline;

using Microsoft.Extensions.Logging;

using System;
using System.Linq;

public class DependencyModule : Module
{
    protected override void AttachToComponentRegistration(IComponentRegistryBuilder componentRegistry, IComponentRegistration registration)
    {
        registration.PipelineBuilding +=
            (sender, e) =>
            {
                e.Use(
                    "ICustomLogger instance injection middleware",
                    PipelinePhase.ParameterSelection,
                    MiddlewareInsertionMode.StartOfPhase,
                    (context, next) =>
                    {
                        var parameters = context.Parameters.ToList();

                        parameters.Add(
                            new ResolvedParameter(
                                (parameter, context) => typeof(ICustomLogger) == parameter.ParameterType,
                                (parameter, context) =>
                                {
                                    // This is the type the ctor of which contains the ICustomLogger parameter.
                                    var type = parameter.Member.DeclaringType;
                                    
                                    // This is the Microsoft.Extensions.Logging.ILogger<T> instance.
                                    var msLogger = context.Resolve<ILoggerFactory>().CreateLogger(type);
                                    
                                    // Here we create an instance of ICustomLogger.
                                    return Activator.CreateInstance(typeof(MicrosoftLogger<>).MakeGenericType(type), msLogger)
                                }
                            )
                        );

                        context.ChangeParameters(parameters);

                        next.Invoke(context);
                    }
                );
            };
    }
}

Then you need to register this module into Autofac container, e.g. when you create the hostbuilder for your app.

using Autofac;
using Autofac.Extensions.DependencyInjection;

Host.CreateDefaultBuilder(args)
    .UseServiceProviderFactory(new AutofacServiceProviderFactory())
    .ConfigureContainer<ContainerBuilder>(
        builder => builder.RegisterModule<DependencyModule>()
    )
    // rest is ommitted for clarity
    ;

Then your consumer class will get the correct instance of your MicrosoftLogger<T> type.

Gabor
  • 3,021
  • 1
  • 11
  • 20