Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does .NET Core DI container not inject ILogger?

I am trying to get logging up and running in my C# console app based on .NET Core 2.1.

I added the following code to my DI declaration:

var sc = new ServiceCollection();

sc.AddLogging(builder =>
{
    builder.AddFilter("Microsoft", LogLevel.Warning);
    builder.AddFilter("System", LogLevel.Warning);
    builder.AddFilter("Program", LogLevel.Warning);
    builder.AddConsole();
    builder.AddEventLog();
});

I am trying to inject the service by using the Interface Microsoft.Extensions.Logging.ILogger in the constructor of the services, but I am getting the following error:

Unhandled Exception: System.InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger' while attempting to activate 'MyService'.

    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
    at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateEnumerable(Type serviceType, CallSiteChain callSiteChain)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
    at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.CreateServiceAccessor(Type serviceType)
    at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
    at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
    at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
    at Program.Main(String[] args) in Program.cs:line 27

Did I misunderstand something here? Shouldn't the AddLogging method be enough to expose the Service in the DI Container?

like image 477
mscho Avatar asked Jun 02 '20 08:06

mscho


2 Answers

In order for MS.DI to be able to do a mapping from ILogger to ILogger<T> where the T becomes the consumer, it must support context-based injection. This means that when building an application using MS.DI, you will have to let your classes depend on ILogger<T>.

There are multiple reasons why MS.DI lacks this feature. I think the two most important reasons are:

  • MS.DI only implements features that are required for framework components itself. Remember: MS.DI was built and designed especially for the ASP.NET Core framework, its components, and its third-parties. Much less to be a fully fledged DI Container
  • MS.DI tries to be a lowest common denominator (LCD) where it tries to only support features that all other DI Containers support as well to allow you to replace this built-in container with a more mature, feature-rich DI Container.

I understand your annoyance. It would be great to see MS.DI support context-based injection, because it makes a lot of sense to depend on ILogger instead for your application components, because this makes your code simpler, easier to test, and less error prone.

Unfortunately, because of MS.DI's design and its LCD philosophy, it's unlikely that it ever gets such feature. Every time Microsoft adds behavior, a long and complex discussion is started with maintainers of most DI Containers to see how such feature can be supported in a way that is compatible with all other libraries (I've been parted of many of these discussions myself). This is a daunting task, and already proved to be impossible in some cases.

Instead, it makes sense to select a more mature and feature-rich DI Container that contains such feature, and many other features. For Simple Injector, for instance, we added integration for injecting ILogger. But there are other DI Containers that allow you to inject ILogger.

like image 77
Steven Avatar answered Nov 15 '22 18:11

Steven


The dependency injection system doesn't register ILogger. Instead it registers ILogger<T>. If you need an instance of a logger, you will need to accept ILogger<MyService>.

The reason behind this is that the generic argument is used to build the logger's category name--something which all loggers require. With a non-generic logger there's not necessarily a good default name. If you really want a non-generic ILogger, you can register one like this (change the name as you please):

services.AddSingleton(sp => sp.GetRequiredService<ILoggerFactory>().CreateLogger("DefaultLogger"));

Alternatively, you can accept an ILoggerFactory in your constructor and then create your own logger on the fly.

like image 41
pinkfloydx33 Avatar answered Nov 15 '22 18:11

pinkfloydx33