Dmitry Nogin
Dmitry Nogin

Reputation: 3750

.NET Core DI looking into IConfiguration

What would be the simplest way to support attribute-based setting reference resolving in ASP.NET Core DI? I would like to make something like this to read the config value:

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    public WeatherForecastController(
        [Setting("Logging:LogLevel:Microsoft")] string logLevel)
    {
        …

When appsettings.json looks like:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      …

Any ideas how to approach it? I would like it to work for services as well.

Upvotes: 1

Views: 226

Answers (1)

Steven
Steven

Reputation: 172696

Technically, it is possible to achieve this with MS.DI, but it's pretty hard to do so and, as Dmitry mentioned, you will have more luck with mature DI Containers, such as Autofac or Simple Injector.

If you really want to keep using MS.DI, here's some code that would get you started:

var settingDescriptors = (
    from descriptor in services
    let type = descriptor.ImplementationType
    where type != null && !type.IsGenericTypeDefinition
    where type.GetConstructors().Single().GetParameters()
        .Any(p => p.GetCustomAttribute<SettingAttribute>() != null)
    select descriptor)
    .ToArray();

foreach (var descriptor in settingDescriptors)
{
    services.Remove(descriptor);

    object[] settings = // TODO: Determine settings from parameters here

    services.Add(new ServiceDescriptor(
        descriptor.ServiceType,
        c => ActivatorUtilities.CreateInstance(
            c,
            descriptor.ImplementationType,
            settings),
        descriptor.Lifetime));
}

What this code does is iterate over the service collection and replace all registrations for an implementation that has a constructor argument which is marked with the SettingAttribute. That registration is replaced with a registration that uses a factory delegate which asks the DI Container to create an instance, while supplying one or multiple objects (the settings) to it. In other words, it enables Auto-Wiring while manually supplying objects.

What this means is that, to some degree, this is possible with MS.DI, but... there are some serious limitations you should take into consideration:

  • This code does not work for open-generic registrations. As a matter of fact, it is a limitation of MS.DI that prevents you to effectively apply this method on open-generic types.
  • Once applied, you can't apply other extensions that work the same way, because once you replaced a descriptor for an implementation type with one that uses a factory delegate, the DI system becomes blind. For instance, if you apply decorators using Scrutor (which works on top of MS.DI), you might see some weird behavior depending in which order you apply them.
  • The use of ActivatorUtilities.CreateInstance blinds the DI system. Where MS.DI contains checks to prevent circular dependencies, the use of ActivatorUtilities.CreateInstance will lead to hard to debug stack overflow exceptions in case of a circular dependency.
  • This particular implementation is naive and might screw things up because it changes the ordering of registrations. This could break in case you use the attribute on classes that are part of a collection, or when you have a type that replaces another type (which typically works by appending a registration to the list). To fix this, you will have to insert the new descriptor in the same location in the list as where you removed the old.
  • There are likely more caveats, but these are the ones that I can recall from the top of my head.

Upvotes: 1

Related Questions