Я сделал простейшую утилиту для регистрации в контейнере по атрибутам на интерфейсе\реализации. Добавил тесты а так же поддержку IServiceCollection.

Зачем?

Убрать весь бойлерплейт регистраций в контейнере.

На больших проектах с регистрациями в контейнере иногда можно ошибиться даже при merge.

Если в проектах есть какие-нибудь IChainProcessor-ы в количестве более 5-10 штук - удобнее регистрировать их все через атрибут.

Ситуации с тем, что написал сервис(ы), но забыл зарегистрировать в контейнере теперь исключены.

[TypeRegistration(LifetimeManagementType.PerThread)]
internal interface I1
{    
  public void Action1();    
  public void Action2();
}

Теперь вы можете добавлять реализации, не добавляя их в контейнер вручную.

Для регистрации Mock в Debug конфигурациях можно (и нужно) использовать директивы препроцессора #if.

Так же вы можете переопределить LifetimeManagement для любых реализаций.

[DerivedTypeRegistration(LifetimeManagementType.Singletone)]
internal class C1 : I1
{    
  public void Action1()  
  {    
    var a = 1 + 2;  
  }    
  public void Action2()  
  {    
    var b = 3 + 4;    
  }
}

Самое смешное, что 2 атрибута обеспечивают все возможные типы регистрации в контейнере.

Как пользоваться?

Устанавливаем атрибуты регистрации

Добавить TypeRegistrationAttribute на интерфейс, реализации которого хочется регистрировать в контейнере.

Добавить DerivedTypeRegistrationAttribute на реализацию чтобы переопределить LifetimeManagement.

Запускаем регистрацию

Вызвать 

servicesCollection.RegisterByAttributes();

Чтобы вытянуть регистрации из сборки с данными атрибутами достаточно всего-лишь вызвать 

servicesCollection.RegisterByAttributes(myExternalAssembly);

Теперь все реализации зарегистрированы в контейнере.

А это протестированно?

Да, написано 14 тестов.

Тесты:

1 к 1

1 к N

1 к 1 переопределение Lifetime

1 к N переопределение Lifetime

Все это для:

1) простых интерфейсов с простыми реализациями

2) Generic интерфейсов с простыми реализациями

3) и Generic интерфейсов с Generic реализациями

4) Так же есть тесты на смешанные кейсы

Заключение

Теперь регистрацию в контейнере не нужно переписывать по 10 раз, достаточно указывать атрибуты.

Для импорта из сборок без атрибутов по прежнему придется использовать сторонние утилиты.

Исходники лежать здесь.

Upd

Добавил Nuget пакет.

Поправил регистрацию если интерфейсы в другой сборке.

https://www.nuget.org/packages/RegistrationByAttributes

Комментарии (6)


  1. alex1t
    08.08.2025 12:12

    А что будет если есть два класса, реализующий интерфейс. При обычной регистрации будет использоваться тот, что зарегистрирован последним. Или надо использовать named регистрации. А здесь какой порядок будет?

    Такая декларативная регистрация, разбросанная по коду на мой взгляд не очень удобна, поскольку требует времени на поиск и идентификацию. Я предпочитаю иметь один класс/метод, в котором все регистрации контейнеров и производятся из корня композиции (обычно Startup.ConfigureServices)


    1. ValeriyPus Автор
      08.08.2025 12:12

      При обычной регистрации будет использоваться тот, что зарегистрирован последним. Или надо использовать named регистрации. А здесь какой порядок будет?

      Тот класс, который зарегистрирован последним. Специально для Вас вышла версия 1.0.0.4. С поддержкой Keyed регистрации. Надо установить name в атрибуте для реализации.

      Но на мой взгляд это плохая практика, у нас еще появляются списки имен, которые надо тащить в сервисы.

      Такая декларативная регистрация, разбросанная по коду на мой взгляд не очень удобна, поскольку требует времени на поиск и идентификацию

      Скорее, наоборот. Ctrl+T, Открыл интерфейс/класс и смотри.

      Регистрации в контейнерах тоже не удобно - по 700 строк бойлерплейта, тяжело мержить, добавил интерфейс\реализацию - иди пиши еще 50 строк, не пропусти ничего и т.д.


      1. withkittens
        08.08.2025 12:12

        по 700 строк бойлерплейта,

        Так они у вас никуда не делись, только теперь размазаны по всему солюшену.


        1. ValeriyPus Автор
          08.08.2025 12:12

          нет, читайте внимательнее.

          98% регистраций - атрибут НА ИНТЕРФЕЙСЕ.

          Т.е. строк точно меньше, чем при в регистрации в контейнере. Менее 1 строки на реализацию интерфейса.

          Все не "размазано по солюшену", а берется из метаданных к интерфейсу\классу.

          С тем же успехом можно сказать, что из-за модификаторов доступа теперь все размазано по солюшену, без единой таблицы импорта\экспорта :).

          Размазано по солюшену - это именованные (Keyed) регистрации, и их получение из контейнера :). Вот где место скопления ошибок, не видимых при компиляции.


  1. pankraty
    08.08.2025 12:12

    А как быть, если класс реализует 2 интерфейса, и его нужно зарегистрировать как синглтон таким образом, чтобы при резолве любого из интерфейсов возвращался один и тот же экземпляр?


    1. ValeriyPus Автор
      08.08.2025 12:12

      Атрибуты на оба интерфейса

      [TypeRegistration(LifetimeManagementType.Default)]

      на класс

      [DerivedTypeRegistration(LifetimeManagementType.Singletone)]