Я был давним пользователем 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 в конечном итоге будет работать прекрасно.
@Steven Да, извините, конечно, мне нужно зарегистрировать ProductsDbContext в контейнере, чтобы его можно было внедрить. Через мгновение я предоставлю полный MCVE, а затем отредактирую свой вопрос.
@Steven, в Autofac есть концепция «AsImplementedInterfaces», которую я не могу «реплицировать» в SimpleInjector, но я думаю, это просто потому, что процесс регистрации действительно отличается. Однако полный пример должен позволить вам понять, что я хочу сделать в SimpleInjector. Заранее спасибо за вашу помощь!





Способ зарегистрировать это в 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>() или что-то подобное.
Это зависит от ваших потребностей, но обычно я советую использовать Simple Injector для всех компонентов вашего приложения и использовать MS.DI для изменения фреймворка и сторонних материалов. Однако есть исключения, и DbContext может быть одним из них, так как его можно тесно интегрировать с фреймворком. Это особенно актуально, когда вы начинаете использовать DbContexts в пуле. В этом случае вы позволяете Simple Injector подключить регистрацию из MS.DI.
Хорошо, значит ли это, что мне нужно вызывать EnableSimpleInjectorCrossWiring и UseSimpleInjectorAspNetRequestScoping как можно дальше при запуске приложения? В этот момент я сначала регистрирую свой открытый общий IEntityRepository в SI, а затем в MS DI, после чего использую RegisterDbContext. Еще одна вещь, которая приходит мне на ум, — это использование расширения AddMediatR, предоставленного командой библиотеки, которое вызывается полностью в конце процесса регистрации. Еще раз спасибо за помощь, зная, что я попробую что-то другое и, возможно, впоследствии обновлю свой вопрос, чтобы поделиться своими открытиями.
Звучит правильно, хотя, когда дело доходит до MediatR, я бы сделал регистрацию tge частью Simple Injector из-за ограничений MS.DI и потому, что эти абстракции MediatR на самом деле являются частью компонентов вашего приложения, а не частью фреймворка.
А поскольку MediatR — это не более чем набор абстракций, рассмотрите возможность самостоятельного определения этих абстракций, а не зависимости от MediatR. Это также позволяет вам определять абстракции наиболее удобным для вас способом. Например, вас могут не интересовать асинхронные абстракции. И вы можете на самом деле найти использование декораторов более удобным, чем использование этих пре- и пост-обработчиков. Я считаю, что пре- и пост-дизайн был выбран Джимми, чтобы MediatR работал с большим количеством DI-контейнеров. Однако, как разработчик приложения, вы должны выбрать дизайн, который лучше всего подходит для вас и вашего приложения.
Действительно интересный подход к части "самого проектирования абстракции". Я использую MediatR просто потому, что он существует, и мне не нужно самому реализовывать шаблон Mediat. Я не использую процессоры «до и после», предлагаемые библиотекой, поскольку, как вы сказали, расширенные возможности, вероятно, будут разработаны мной. С другой стороны, я следовал вашим рекомендациям, и теперь мой проект отлично внедряет мои зависимости! Большое спасибо! Вы оказали большую помощь!
Я почти уверен, что Autofac не догадывается об этом и что ваша конфигурация Autofac имеет дополнительное сопоставление (от
IProductsDbContextдоProductsDbContext), которое в настоящее время отсутствует в вашей конфигурации Simple Injector. Может быть полезно поделиться MCVE, который показывает рабочий пример вашей конфигурации Autofac.