简体   繁体   中英

How do I get Type that Dependency is being injected into?

Im working with .net core 2.2 and using dependency injection. I have some custom infrastructure dependencies that manage logging, tracing, etc. that I inject into classes. I'd like the dependencies to know what class they are in. I can do that if I instantiate the class like new Logger<Type>(); but is that possible to manage with dependency injection?

Ex:

public class Logger<Type> : ILogger
{

}

public class Foo
{
    private Ilogger _logger;

    public Foo(ILogger logger)
    {
       _logger = logger;
    }

    public void DoStuff()
    {
        _logger.Log();  //<= knows it's in the Foo class.
    }
}

How do I inject ILogger into Foo, and have the Logger know what Type it was injected into?

You can specify the type like this

public class Foo
{
    private Ilogger<Foo> _logger;
}

Then in the Logger, you can know the type of T by

public class Logger<T> : ILogger<T>
{
    public Type WhatType() => typeof(T);
}

You will need to register the service like this

serviceCollection.AddSingleton(typeof(ILogger<>), typeof(Logger<>));

While more mature and feature rich DI Containers, such as Autofac and Simple Injector , do support context-based injection (which is the feature you are looking for), MS.DI does not support this feature.

It is for this very reason that all the Microsoft documentation describe the injection of a generic ILogger<T> where T equals the consuming type, as in:

public class C
{
    public C(ILogger<C> logger) { ... }
}

Letting a consumer depend on the generic ILogger<T> , instead of simply depending on a non-generic ILogger , however, is more verbose and error prone. It makes you code harder to test and harder to maintain. This is likely the reason you are trying to inject a context-aware ILogger instead, and I applaud you for that.

You can kinda 'hack' this feature into MS.DI by iterating through the ServiceCollection , but there are practically too many limitations to it to be workable in your production application. For fun and entertainment, however, this is what you can try:

// Run just before finalizing the IServiceCollection
for (int i = 0; i < services.Count; i++)
{
    ServiceDescriptor descriptor = services[i];

    if (descriptor.ImplementationType != null)
    {
        var loggerParameters =
            from ctor in descriptor.ImplementationType.GetConstructors()
            from param in ctor.GetParameters()
            where param.ParameterType == typeof(ILogger)
            select param;

        if (loggerParameters.Any())
        {
            // Replace registration
            services[i] =
                new ServiceDescriptor(
                    descriptor.ServiceType,
                    provider =>
                        ActivatorUtilities.CreateInstance(
                            provider,
                            descriptor.ImplementationType,
                            provider.GetRequiredService(
                                typeof(ILogger<>).MakeGenericType(
                                    descriptor.ImplementationType))),
                    descriptor.Lifetime);
        }
    }
}

What this code does is:

  • search for registrations of
    • (closed or non-generic) implementation type
    • that uses Auto-Wiring (so no lambda registration, or instance registration)
    • and whose constructor depends on ILogger
  • and replaces that registration with a lambda registration that Auto-Wires the type (using ActivatorUtilities ) while replacing the ILogger dependency with an ILogger<T> dependency.

Limitations:

  • Doesn't work on open-generic registrations
  • Doesn't work on delegate registrations
  • Might not work when replacing MS.DI with another DI Containers (eg might "blind" the container).
  • Behavior is undefined when working with multiple constructors

In practice, I would therefore not suggest using this approach but instead use a mature DI Container.

TIP: To get some inspiration, when using Simple Injector, such context-based registration can be made as follows:

container.RegisterConditional(
    typeof(ILogger),
    c => typeof(Logger<>).MakeGenericType(c.Consumer.ImplementationType),
    Lifestyle.Singleton,
    c => true);

When integrating with ASP.NET Core, Simple Injector even contains an extension method that simplifies this. Here 's the Simple Injector documentation on how to configure the container to inject ASP.NET Core's ILogger , and the basic description on how to apply context-based injection in Simple Injector can be found here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM