简体   繁体   中英

Built-in dependency injection with conventions

How to inject services without registering them? I mean in the past some DI frameworks automatically registered Service for IService .

I'm in a situation where I have a dozen of services and basically registering every single one is a pain in the ass. so is this supported in asp.net core default DI framework?

I guess you like the way it works with Autofac:

var assembly = typeof(MyModule).Assembly;
builder.RegisterAssemblyTypes(assembly)
    .Where(t => t.Name.EndsWith("Service"))
    .AsImplementedInterfaces()
    .InstancePerLifetimeScope();

But you don't want to switch to Autofac for some reasons (for example you want use extensions from external libraries to register their dependencies). So my suggestion is to create some extensions that use reflection like these ones:

public static IServiceCollection AddSingletonsByConvention(this IServiceCollection services, Assembly assembly, Func<Type, bool> interfacePredicate, Func<Type, bool> implementationPredicate)
{
    var interfaces = assembly.ExportedTypes
        .Where(x => x.IsInterface && interfacePredicate(x))
        .ToList();
    var implementations = assembly.ExportedTypes
        .Where(x => !x.IsInterface && !x.IsAbstract && implementationPredicate(x))
        .ToList();
    foreach (var @interface in interfaces)
    {
        var implementation = implementations.FirstOrDefault(x => @interface.IsAssignableFrom(x));
        if (implementation == null) continue;
        services.AddSingleton(@interface, implementation);
    }
    return services;
}

public static IServiceCollection AddSingletonsByConvention(this IServiceCollection services, Assembly assembly, Func<Type, bool> predicate)
    => services.AddSingletonsByConvention(assembly, predicate, predicate);

Now you can register all your services by simple code like this:

var assembly = typeof(MyType).Assembly;
services.AddSingletonsByConvention(assembly, x => x.Name.EndsWith("Service"));

Feel free to customize these extensions to match your needs. For example you can fire an exception if you don't find implementations for some services if that will make you feel a bit safer.

The out-of-the-box DI doesn't support it and do not intend to do so. The built-in IoC Container is kept simple by design, to allow basic dependency injection which works for most cases.

If you want advanced features like registering by convention, assembly scanning or decorator support, you have to use 3rd party IoC container like Autofac, SimpleInjector, Castle Windsor.

I would like to mention the Scrutor nuget which let's you use the ASP.NET Core Dependency Injection but adds a fluent interface on top of it to allow for convention-based configuration.

Read this article for more information.

Adding to @cherepets' answer, if you would like to:

  • register transient services by convention
  • search in different assemblies for interface and implementation
  • only look at those that begin with a particular namespace (eg, company name or app name)
  • only look at implementation names names that "match" the interface name (eg, IMyEmail and MyEmail)
  • throw an exception upon startup if any implementations are not found

You can use this function:

public void AddTransientsByConvention(IServiceCollection services, Assembly[] assemblies, Func<Type, bool> myPredicate)
{
    List<Type> interfaces = new List<Type>();
    List<Type> implementations = new List<Type>();

    foreach (var assembly in assemblies)
    {
        interfaces.AddRange(assembly.ExportedTypes.Where(x => x.IsInterface && myPredicate(x)));
        implementations.AddRange(assembly.ExportedTypes.Where(x => !x.IsInterface && !x.IsAbstract && myPredicate(x)));
    }

    foreach (var @interface in interfaces)
    {
        var implementation = implementations
            .FirstOrDefault(x => @interface.IsAssignableFrom(x) &&
                $"I{x.Name}" == @interface.Name );

        if (implementation == null)
            throw new Exception($"Couldn't find implementation for interface {@interface}");

        services.AddTransient(@interface, implementation);
    }
}

And then call it like this in your ConfigureServicescs:

var assemblyOne = Assembly.GetAssembly(typeof(IMyRepository));
var assemblyTwo = Assembly.GetAssembly(typeof(IMyBusinessLogic));
var assemblyThree = Assembly.GetExecutingAssembly();

AddTransientsByConvention(services,
    new [] { assemblyOne, assemblyTwo , assemblyThree },
    x => x.Namespace.StartsWith("CompanyName"));

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