Есть ли способ автоматической передачи (автоматической регистрации) одноэлементных сервисов в контейнер C # DI (Microsoft.Extensions.DependencyInjection) по аннотации?

Например. что-то вроде параметра ProvidedIn в аннотации @Injectable () в Angular, аннотация @injectable () в InversifyJS (Node.js), автоматическое подключение в Spring (Java) или автоматическое подключение в Symfony Framework (PHP) ?

См. Угловой пример ниже:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class UserService {
}

В настоящее время я должен вручную добавить каждый синглтон-сервис в ServiceCollection (и иногда забываю сделать это):

internal static ServiceProvider SetupDi()
{
    return new ServiceCollection() // Microsoft.Extensions.DependencyInjection.ServiceCollection.ServiceCollection()
        .AddDbContext<DbContext>()
        .AddSingleton<ServiceA>()
        .AddSingleton<ServiceB>();
}

Желаемое эквивалентное решение будет примерно таким:

internal static ServiceProvider SetupDi()
{
    return new ServiceCollection() // Microsoft.Extensions.DependencyInjection.ServiceCollection.ServiceCollection()
        .AddDbContext<DbContext>();
}

[Injectable()]
public class ServiceA
{
}

[Injectable()]
public class ServiceB
{
}
1
miloshavlicek 25 Апр 2020 в 13:50

2 ответа

Лучший ответ

Вы можете использовать Scrutor для добавления возможностей сканирования сборок в контейнер ASP.NET Core DI.

Для получения дополнительной информации вы можете проверить этот блог https://andrewlock.net/using-scrutor-to-automatics-register-your-services-with-the-asp-net-core-di-container/

1
Deepak Mishra 25 Апр 2020 в 11:11

Нет, и это на самом деле второстепенный.

Весь смысл DI в том, что неожиданностей в том, как настроена ваша программа, нет: все, что вы настраиваете в ConfigureServices (в вашем случае, SetupDi), это именно то, что вы получить во время выполнения. Используя атрибуты для настройки DI, это приведет к появлению «нелокальных эффектов», и будет гораздо сложнее отследить ошибки, вызванные неправильными или неправильно настроенными зависимостями, вызванными ошибочным атрибутом.

(В этом смысле я не согласен с дизайном Angular, но это не по теме).

(Я также чувствую, что система DI .NET Core также несовершенна - слишком много необходимых деталей скрыто за методами расширения DI-инъекций, для которых вам необходимо использовать ILSpy или Reflector, чтобы вглядываться).

В качестве обходного пути вы можете «протестировать» ваши службы DI при запуске приложения, чтобы убедиться, что все настроено, отражая каждый IService в вашем проекте и пытаясь создать экземпляр реализации.

Вот код, который я использую в своих проектах ASP.NET и ASP.NET Core для проверки правильности настройки DI:

internal static void TestAllServices( IServiceProvider sp )
{
    Assembly[] mySolutionAssemblies = new[]
    {
        typeof(FromAssemblyX.Foobar).Assemby,
        typeof(FromAssemblyY.Foobar).Assemby,
        typeof(FromAssemblyZ.Foobar).Assemby,
    };

    List<Type> allServiceInterfaceTypes = mySolutionAssemblies
        .SelectMany( ass => ass.GetTypes() )
        .Where( t => t.IsInterface && t.IsPublic )
        .Where( /* Filter out interfaces you don't want to verify here */ )
        .ToList();

    foreach( Type serviceInterfaceType in serviceInterfaceTypes )
    {
        try
        {
            Object implementation = sp.GetRequiredService( serviceInterfaceType );
            if( implementation is IDisposable disp ) disp.Dispose();
        }
        catch( Exception ex )
        {
            // Log an error or throw or set a breakpoint here
        }
    }
}

Примечания:

  • Не передавайте свой "настоящий" IServiceProvider в TestAllServices - вместо этого создайте отдельный экземпляр IServiceProvider, потому что этот метод будет располагать любой реализацией, которая реализует IDisposable, даже если они одинокие.
  • Я рекомендую поместить этот код в #if DEBUG, чтобы он не пошел в производство.
  • Основным недостатком системы DI по умолчанию в .NET Core является то, что невозможно статически отличить реализации «определенных» сервисов от одиночных и переходных сервисов - вы можете обойти это, добавив маркерные интерфейсы в реализации сервисов и обрабатывая их соответствующим образом.

Еще одна заметка:

Если вы действительно хотели, вы можете использовать ту же технику allServiceInterfaceTypes, описанную выше в вашем методе ConfigureServices, для перечисления по всему интерфейсу и обнаружения всех типов, реализующих каждый интерфейс, и для обнаружения любых пользовательских атрибуты этих типов и автоматически регистрируют их в контейнере DI - но я не рекомендую делать это, потому что отражение медленнее, чем использование DI по назначению - и настройка таких вещей, как фабричные методы, будет намного сложнее.

1
Dai 25 Апр 2020 в 11:08