Простой инжектор — внедрение службы на основе указанного универсального типа во время выполнения

Я был давним пользователем Autofac, который недавно переключился на Simple Injector для нужд контейнера DI. Когда я использовал Autofac, я смог сделать то, что я до сих пор не могу сделать с помощью Simple Injector, возможно, потому, что я еще не совсем понимаю API.

Допустим, у меня есть сервис IEntityRepository из TEntity и TDbContext. Его реализация выглядит так:

    public class EntityRepository<TEntity, TDbContext> : IEntityRepository<TEntity, TDbContext>
            where TDbContext : IEntityDbContext where TEntity : class
        {
            public EntityRepository(TDbContext dbContext)
            {
            }
        }

С помощью Autofac я смог зарегистрировать открытую универсальную реализацию EntityRepository в качестве открытого универсального интерфейса IEntityRepository, поэтому, когда я вводил, скажем, IEntityRepository из Product и IProductsDbContext, контейнер внедрения зависимостей автоматически догадывался, что я внедряю через конструктор экземпляр ProductsDbContext.

Возможно ли это с помощью Simple Injector? Я пытаюсь сделать это, но все равно не получается:

container.Register(typeof(IEntityRepository<,>), typeof(EntityRepository<,>).Assembly);
container.Register(typeof(IEntityRepository<,>), typeof(EntityRepository<,>));

Заранее спасибо за вашу помощь!

Обновлено: Итак, вот полный пример с Autofac по просьбе Стивена. Создайте новое консольное приложение .NET Core. Вам потребуется установить пакет NuGet Autofac.

Программа.cs:

internal class Program
    {
        private static void Main(string[] args)
        {
            var builder = new ContainerBuilder();

            builder.RegisterType<ProductsDbContext>().AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(EntityRepository<,>)).As(typeof(IEntityRepository<,>));

            var container = builder.Build();
            using (var scope = container.BeginLifetimeScope())
            {
                var productsRepository = scope.Resolve<IEntityRepository<Product, IProductsDbContext>>();
                Console.WriteLine($"Resolved IEntityRepository is of type: {productsRepository.GetType()}");
            }
        }
    }

ПродуктыDbContext.cs

public class ProductsDbContext : IProductsDbContext
    {
        public void Dispose()
        {
            // Demo, do nothing.
        }

        public int SaveChanges()
        {
            throw new System.NotImplementedException();
        }
    }

Продукт.cs

public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

EntityRepository.cs

public class EntityRepository<TEntity, TDbContext> : IEntityRepository<TEntity, TDbContext>
        where TDbContext : IEntityDbContext where TEntity : class
    {
        private readonly TDbContext _dbContext;

        public EntityRepository(TDbContext dbContext)
        {
            _dbContext = dbContext;
            Console.WriteLine($"Database context is of type {dbContext.GetType()}.");
        }

        public IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> whereClause)
        {
            throw new NotImplementedException();
        }
    }

IEntityDbContext.cs

public interface IEntityDbContext : IDisposable
    {
        int SaveChanges();
    }

IPProductsDbContext.cs

public interface IProductsDbContext : IEntityDbContext
    {

    }

IEntityRepository.cs

public interface IEntityRepository<TEntity, TDbContext> where TDbContext : IEntityDbContext where TEntity : class
    {
        IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> whereClause);
    }

Окончательный вывод консоли должен выглядеть так:

Database context is of type GenericTypeDiTester.DbContexts.ProductsDbContext. Resolved IEntityRepository is of type: GenericTypeDiTester.Repositories.EntityRepository`2[GenericTypeDiTester.Models.Product,GenericTypeDiTester.Interfaces.DbContexts.IProductsDbContext]

Вы можете скачать полный пример здесь: https://drive.google.com/file/d/1UkIYxLsY6YGwo5jOB5TyyncXc6yho8X5/view?usp=sharing

Обновлено еще раз: Проблема была не в библиотеке Simple Injector в конце. Кажется, что смешивание использования Microsoft.DependencyInjection и SimpleInjector не очень хорошо. Как предложил Стивен, вы должны использовать исключительно SI для регистрации большинства ваших сервисов и, в редких случаях, MS.DI (например, для использования AddDbContext).

Что касается меня, то у меня в проекте MediatR есть библиотека, реализующая паттерн Mediator. Эта библиотека предлагает пакет NuGet с методом расширения AddMediatR для IServiceCollection MS.DI, который должен правильно регистрировать все обработчики, но для меня это не так. В итоге я зарегистрировал модуль самостоятельно, используя SI.

В итоге все заработало отлично. Вам действительно нужно вызвать эти строки в конце процесса регистрации: EnableSimpleInjectorCrossWiring и UseSimpleInjectorAspNetRequestScoping. Ничего больше не должно быть зарегистрировано с использованием IServiceCollection впоследствии. Таким образом, перекрестное соединение обеих сред DI в конечном итоге будет работать прекрасно.

Я почти уверен, что Autofac не догадывается об этом и что ваша конфигурация Autofac имеет дополнительное сопоставление (от IProductsDbContext до ProductsDbContext), которое в настоящее время отсутствует в вашей конфигурации Simple Injector. Может быть полезно поделиться MCVE, который показывает рабочий пример вашей конфигурации Autofac.

Steven 03.03.2019 21:53

@Steven Да, извините, конечно, мне нужно зарегистрировать ProductsDbContext в контейнере, чтобы его можно было внедрить. Через мгновение я предоставлю полный MCVE, а затем отредактирую свой вопрос.

David 03.03.2019 23:04

@Steven, в Autofac есть концепция «AsImplementedInterfaces», которую я не могу «реплицировать» в SimpleInjector, но я думаю, это просто потому, что процесс регистрации действительно отличается. Однако полный пример должен позволить вам понять, что я хочу сделать в SimpleInjector. Заранее спасибо за вашу помощь!

David 03.03.2019 23:58
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
240
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Способ зарегистрировать это в Simple Injector:

container.Register(typeof(IEntityRepository<,>), typeof(EntityRepository<,>));
container.Register<IProductsDbContext, ProductsDbContext>();

В Simple Injector нет эквивалента AsImplementedInterfaces, хотя есть несколько способов добиться того же. В случае, если ProductsDbContext имеет несколько интерфейсов, которые необходимо зарегистрировать, наиболее очевидным способом является явная регистрация каждого интерфейса:

container.Register<IProductsDbContext, ProductsDbContext>();
container.Register<IUsersDbContext, ProductsDbContext>();
container.Register<ICustomersDbContext, ProductsDbContext>();

Хорошо, я могу понять, почему это не работает на моей стороне. Возможно ли, что мне нужно использовать исключительно SimpleInjector вместо использования Microsoft.Extensions.DependencyInjection и SimpleInjector? На протяжении всего процесса регистрации я использую SimpleInjector сначала для «сложных» регистраций, а затем библиотеку Microsoft для остальных. Контекст моей базы данных зарегистрирован в MS, потому что у них есть удобный метод RegisterDbContext<TService, TImplementation>() или что-то подобное.

David 04.03.2019 14:42

Это зависит от ваших потребностей, но обычно я советую использовать Simple Injector для всех компонентов вашего приложения и использовать MS.DI для изменения фреймворка и сторонних материалов. Однако есть исключения, и DbContext может быть одним из них, так как его можно тесно интегрировать с фреймворком. Это особенно актуально, когда вы начинаете использовать DbContexts в пуле. В этом случае вы позволяете Simple Injector подключить регистрацию из MS.DI.

Steven 04.03.2019 16:54

Хорошо, значит ли это, что мне нужно вызывать EnableSimpleInjectorCrossWiring и UseSimpleInjectorAspNetRequestScoping как можно дальше при запуске приложения? В этот момент я сначала регистрирую свой открытый общий IEntityRepository в SI, а затем в MS DI, после чего использую RegisterDbContext. Еще одна вещь, которая приходит мне на ум, — это использование расширения AddMediatR, предоставленного командой библиотеки, которое вызывается полностью в конце процесса регистрации. Еще раз спасибо за помощь, зная, что я попробую что-то другое и, возможно, впоследствии обновлю свой вопрос, чтобы поделиться своими открытиями.

David 04.03.2019 17:04

Звучит правильно, хотя, когда дело доходит до MediatR, я бы сделал регистрацию tge частью Simple Injector из-за ограничений MS.DI и потому, что эти абстракции MediatR на самом деле являются частью компонентов вашего приложения, а не частью фреймворка.

Steven 04.03.2019 17:22

А поскольку MediatR — это не более чем набор абстракций, рассмотрите возможность самостоятельного определения этих абстракций, а не зависимости от MediatR. Это также позволяет вам определять абстракции наиболее удобным для вас способом. Например, вас могут не интересовать асинхронные абстракции. И вы можете на самом деле найти использование декораторов более удобным, чем использование этих пре- и пост-обработчиков. Я считаю, что пре- и пост-дизайн был выбран Джимми, чтобы MediatR работал с большим количеством DI-контейнеров. Однако, как разработчик приложения, вы должны выбрать дизайн, который лучше всего подходит для вас и вашего приложения.

Steven 04.03.2019 20:32

Действительно интересный подход к части "самого проектирования абстракции". Я использую MediatR просто потому, что он существует, и мне не нужно самому реализовывать шаблон Mediat. Я не использую процессоры «до и после», предлагаемые библиотекой, поскольку, как вы сказали, расширенные возможности, вероятно, будут разработаны мной. С другой стороны, я следовал вашим рекомендациям, и теперь мой проект отлично внедряет мои зависимости! Большое спасибо! Вы оказали большую помощь!

David 05.03.2019 05:01

Другие вопросы по теме