У меня есть два класса: ImportBase.cs (родительский) и ImportFleet.cs (дочерний), которые в будущем будут импортировать файл CSV. Каждый дочерний элемент ImportBase будет реализовывать другую реализацию фактического кода импорта. Соответствующий дочерний класс для использования определяется в классе службы, где создается экземпляр правильного класса и вызывается метод импорта. Все идет хорошо до этого момента.
Проблема в том, что я также хочу внедрить некоторые классы репозитория Dependency Inject в ImportBase и его унаследованные классы (как я пытался в приведенном ниже коде):
ImportBase.cs
namespace WebApi.Services.Import.Investments
{
interface IImport
{
public void Import(IFormFile file, int UserId);
}
public abstract class ImportBase : IImport
{
public abstract void Import(IFormFile file, int UserId);
protected List<InvestmentTransactionType> transactionTypes = new();
protected IInvestmentEntityRepository _investmentEntityRepository;
public ImportBase(IInvestmentEntityRepository investmentEntityRepository)
{
_investmentEntityRepository = investmentEntityRepository;
}
}
}
ImportFleet.cs
namespace WebApi.Services.Import.Investments
{
public class ImportFleet : ImportBase
{
public ImportFleet(IInvestmentEntityRepository investmentEntityRepository) : base(investmentEntityRepository)
{
}
public override void Import(IFormFile file, int UserId)
{
}
}
}
ИнвестСервис.cs
namespace WebApi.Services
{
public interface IInvestmentService
{
public void Import(IFormFile file, int UserId, int InvestmentEntityId);
}
public class InvestmentService: IInvestmentService
{
public void Import(IFormFile file, int UserId, int InvestmentEntityId)
{
IImport importService = null;
string investmentEntity = ImportBase.determineInvestmentEntityFromCsv(file);
switch(investmentEntity)
{
case "fleet":
importService = new ImportFleet(); // problem is here
break;
}
if (importService != null)
{
importService.Import(file, UserId);
}
}
}
}
Проблема заключается в следующей строке:
importService = новый ImportKuflink();
Поскольку я определяю, экземпляр какого дочернего класса нужно создать, только во время выполнения, я не могу воспользоваться преимуществами DI здесь.
При нормальных обстоятельствах я бы сделал классы импорта службой на основе DI, чтобы все зависимости были доступны, однако мне нужно создать экземпляр во время выполнения, поэтому я не думаю, что это возможно.
Есть ли способ выполнить вышеизложенное?
@thewallrus при использовании класса Factory у меня все еще возникает та же проблема при попытке создать экземпляр дочернего класса. Ожидается, что службы, которые я хочу ввести в качестве параметров при вызове new()
Внедрение сервисов в качестве параметров (через конструктор) является поведением по умолчанию в системе .NET DI. И услуги могут зависеть друг от друга. Вы должны зарегистрировать службы с правильной областью действия при запуске. Нам нужно будет увидеть ваш запуск или program.cs, чтобы увидеть, как регистрируются службы.
Да, конечно, есть способ сделать это. Но я думаю, что контейнер DI, который вы используете (например, из MS), вам здесь не поможет.
Я возился с такой ерундой уже около двух лет и до сих пор занят ею. Два года создания собственного фреймворка IoC.
Обычные микроядра DI/IoC следуют OCP и другим действительно обязательным концепциям и шаблонам. Что я сделал, так это оставил одну маленькую дверь открытой. Не буду утомлять вас подробностями. Фундаментальная идея состоит в том, что класс должен быть снабжен соответствующими атрибутами в коде, а затем он может вызывать микроядро в своем конструкторе (который вызывается простым «var foo = new Barney();»), чтобы позволить сущность может быть изменена так, как будто она была создана микроядром.
Нет (пока) способа подключиться к простому коду new(). Некоторые приветствуют это, некоторые нет. Я здесь с чирлидершами. Почему? Побочные эффекты.
Представьте себе это:
public class SomeNumber
{
public int SomeValue { get; private set; }
public SomeNumber()
{
SomeValue = 19;
}
}
Хорошо? Предположим, вы каким-то образом модифицировали процесс new(), тогда другой пользователь вашего кода переходит:
Assert.AreEqual(19, someNumberEntity.SomeNumber);
и этот код выдает исключение, потому что по какой-то причине ваш модифицирующий код установил число 7.
Теперь посмотрите на этот код (из модульного теста):
using System.Reflection;
using Kis.Core.Attributes;
namespace UnitTests_Kis.Core
{
[KisAware]
public class KisAwareSimpleClass
{
[Property(value: 123)]
public int ValueToCheck { get; set; } = 0;
[Property(value: "I am the doctor!")]
public string Name { get; set; } = "";
public KisAwareSimpleClass()
{
var t = this.GetType();
var fqtn = t.FullName;
var ec = new Kis.Core.EntityCreator(Assembly.GetAssembly(t));
ec.ModifyExistingEntity(fullyQualifiedTypeName: fqtn, existingEntity: this);
}
}
}
Чистый код не всегда легко читается, но аспекты/атрибуты повысят осведомленность программиста.
PS: я специально разместил код модульного теста, чтобы показать вам, что происходит.
Укороченная версия:
Microkernel.Modify(this);
Вы можете внедрить фабрику, в которую внедрены службы.
public interface IImportFactory
{
ImportFleet CreateFleetImporter();
}
public class MyImportFactory : IImportFactory
{
private readonly IMyDependency1 _dependency1;
private readonly IMyDependency2 _dependency2;
public MyImportFactory(IMyDependency1 dependency1, IMyDependency2 dependency2)
{
_dependency1 = dependency1;
_dependency2 = dependency2;
}
public ImportFleet CreateFleetImporter()
{
return new ImportFleet(_dependency1, _dependency2);
}
}
Затем добавьте фабрику в качестве зависимости в свой класс обслуживания.
Вот упрощенная версия вашего кода, которая демонстрирует, как вы можете заполнить экземпляр объекта из контейнера службы DI.
В вашем InvestmentService
:
IServiceProvider
.ActivatorUtilities
, чтобы получить экземпляр вашего объекта с полным внедрением зависимостей.IDisposable
. Я включил асинхронную версию, если вы используете что-то, что требует IAsyncDisposable
.public class InvestmentService : IInvestmentService
{
private IServiceProvider _serviceProvider;
public InvestmentService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
//...
}
public Import()
{
IImport? importService = null;
IDisposable? disposable = null;
var importFleet = ActivatorUtilities.CreateInstance<ImportFleet>(_serviceProvider);
if (importFleet is IDisposable)
disposable = importFleet as IDisposable;
importService = importFleet as IImport;
// Do whatever you want to do with it
disposable?.Dispose();
}
public async ValueTask ImportAsync()
{
IImport? importService = null;
IDisposable? disposable = null;
IAsyncDisposable? asyncDisposable = null;
var importFleet = ActivatorUtilities.CreateInstance<ImportFleet>(_serviceProvider);
if (importFleet is IDisposable)
disposable = importFleet as IDisposable;
if (importFleet is IAsyncDisposable)
asyncDisposable = importFleet as IAsyncDisposable;
importService = importFleet as IImport;
// Do whatever you want to do with it
disposable?.Dispose();
if (asyncDisposable is not null)
await asyncDisposable.DisposeAsync();
}
}
это прекрасно работает, правильный класс может быть создан вместе с любыми DI и использоваться в общем контексте. Вы предоставили чрезвычайно полезный фрагмент кода. Насколько важна одноразовость? как я вижу, код был бы намного чище без него.
Важный. Если вы не удаляете объекты, которые реализуют IDisposable
, вы получите утечку памяти в вашем приложении. Однако вам нужно реализовать его только в том случае, если ваши классы реализуют его.
Спасибо. Любопытно, почему для удаления importFleet требуется отдельный одноразовый объект. Нет ли в importFleet метода, который мог бы избавиться от него (учитывая, что он реализует IDisposable)?
Вы не удаляете importFleet, вы удаляете importService
, который является экземпляром IImport
. Это не реализуется IDisposable
. Таким образом, вы захватываете любой созданный вами объект как disposable
, если он нуждается в удалении, а затем просто вызываете Dispose
для него в конце (если он не нулевой — disposable?
).
Я не вижу проблем с созданием нового экземпляра во время выполнения, но похоже, что использование фабричного шаблона принесет пользу. Возможно,
ImportServiceFactory
вернет новый экземпляр. В эту фабрику можно внедрить зависимость.