6

Is it possible to configure ASP.NET Core DI to resolve all classes? Something similar to Autofac AnyConcreteTypeNotAlreadyRegisteredSource functionality.

I'm able to use Autofac in ASP.NET Core, but I do not want to add additional library to my application, because ASP.NET Core already has DI.

So basically instead of:

services.AddTransient<MyService>();
services.AddTransient<MyAnotherService>();

I'd prefer to do something like:

services.ResolveAll();
Maroun
  • 94,125
  • 30
  • 188
  • 241
Vadim Sentiaev
  • 741
  • 4
  • 18
  • 2
    I suppose it could be done but there isn't already a convenient call to do it. I'm curious about what your particular use case is for this. What are you trying to accomplish and why do you think this is the best way to do it? – Erik Noren Mar 24 '17 at 06:33
  • @ErikNoren it's how I usually use DI. I prefer do not create silly interfaces. So if I need only one implementation I will create a class instead of interface and class (to make test frameworks or DI frameworks happy). And almost all projects do not need several implementations for some functionality. So, if I want to inject some `Foo` class it's only `Foo` class I have. And my goal is to not configure DI at all for this. Actually I use Autofac for this `builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource());` – Vadim Sentiaev Jul 22 '17 at 23:31

4 Answers4

11

it seems we can start down this general automatic injection concept with a little reflection

i've filtered on "Provider" namespace and look for class names spelled the same as interfaces without "I", but of course season to your own taste

(this code works under an asp.net core 2.0 context)

  var allProviderTypes = System.Reflection.Assembly.GetExecutingAssembly()
    .GetTypes().Where(t=>t.Namespace != null && t.Namespace.Contains("Providers"));

  foreach(var intfc in allProviderTypes.Where(t=>t.IsInterface)) {
    var impl = allProviderTypes.FirstOrDefault(c=>c.IsClass && intfc.Name.Substring(1) == c.Name);
    if (impl != null) services.AddScoped(intfc, impl);
  }
Youp Bernoulli
  • 5,303
  • 5
  • 39
  • 59
Beej
  • 794
  • 8
  • 15
  • thanks for sharing this, but question was about some build in method. I do not check if it's possible in 2.0, but looks like it's not. – Vadim Sentiaev Dec 09 '17 at 23:59
  • 2
    Perhaps something like: var impl = allProviderTypes.FirstOrDefault(c=>c.IsClass && intfc.IsAssignableFrom(c) && !c.IsAbstract); would be a lot less brittle than assuming a naming convention. – computrius Mar 27 '20 at 18:43
6

Perhaps Scrutor (Scanning feature) is close to what you`re looking for. https://github.com/khellang/Scrutor

4

ASP.NET Core DI doesn't support auto-discovery and auto-registration and there is no plan to add those features (at least in near feature).

The main reason is that the .NET Core team tries to keep the built-in container as simple as possible and saves an easy/straitforward way for adding other DI containers.

Related SO question: Property Injection in Asp.Net Core

Community
  • 1
  • 1
Set
  • 47,577
  • 22
  • 132
  • 150
  • That's not strictly true. You can scan and register dependencies found in assemblies using Scrutor. Check out IServiceCollection Scan. – Keith Hill Jul 16 '20 at 19:31
0

Though using library like Scrutor or SimpleInjector is better option, due to constraints I had to hand-roll auto registeration with reflection. I've tried to filter types by their namespace like @Beej's answer, but I have since learned that it is more effective to explicitly mark classes with attributes after editing Startup.cs 3 times in a row in order to tweak lifetime.

Here is example to collect classes marked with attributes:

[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class TransientAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class ScopedAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class SingletonAttribute : Attribute {}

/*
Mark your classes with [Transient], [Scoped] or [Singleton] above then register like:
services.AddServicesByAttributes(typeof(Startup).Assembly);
*/

public static class AddByAttributeServiceCollectionExtensions
{
    public static IServiceCollection AddServicesByAttributes(this IServiceCollection services, Assembly assembly)
    {
        foreach (ServiceDescriptor d in assembly.GetTypes().SelectMany(MakeDescriptors).Distinct())
        {
            services.Add(d);
        }
        return services;
    }

    private static ServiceLifetime? FindLifetime(Type type)
    {
        return (
            type.IsDefined(typeof(TransientAttribute), false),
            type.IsDefined(typeof(ScopedAttribute), false),
            type.IsDefined(typeof(SingletonAttribute), false)
        ) switch
        {
            (false, false, false) => null,
            (true, false, false) => ServiceLifetime.Transient,
            (false, true, false) => ServiceLifetime.Scoped,
            (false, false, true) => ServiceLifetime.Singleton,
            _ => throw new ArgumentException($"Lifetime attribute specified more than once for {type}", nameof(type)),
        };
    }

    private static IEnumerable<ServiceDescriptor> MakeDescriptors(Type type)
    {
        if (FindLifetime(type) is not ServiceLifetime lifetime) { yield break; }

        if (type.IsGenericType)
        {
            // Handle Generic interfaces. Nested types not supported
            Type impl = type.GetGenericTypeDefinition();

            foreach (Type ifType in type.GetInterfaces())
            {
                // Strip type arguments
                Type service = ifType.IsGenericType ? ifType.GetGenericTypeDefinition() : ifType;

                yield return ServiceDescriptor.Describe(service, impl, lifetime);
            }
        }
        else
        {
            Type impl = type;

            yield return ServiceDescriptor.Describe(impl, impl, lifetime);

            // Alias for interfaces
            // NB. Dispose() may be called more than once for shared instances
            Func<IServiceProvider, object> factory = (sp) => sp.GetRequiredService(impl);
            foreach (Type service in type.GetInterfaces())
            {
                yield return ServiceDescriptor.Describe(service, factory, lifetime);
            }
        }
    }
}

I recommend this book by @ploeh and @Steven that shows how to cope with ASP.NET Dependency injection.

snipsnipsnip
  • 2,268
  • 2
  • 33
  • 34