使用依赖注入来实例化运行时的类

huangapple go评论82阅读模式
英文:

Using Dependency Injection for classes instantiated at run time

问题

以下是您要翻译的内容:

I have two classes: ImportBase.cs (Parent) and ImportFleet.cs (Child) which will in the future import a CSV file. Each child of ImportBase will implement a different implementation of the actual import code.
The appropriate child class to use is determined in a Service class where the correct class is instantiated and the import method called. All is going well up until this point.

The problem is that I also want to Dependency Inject some repository classes into ImportBase and its inherited classes (as I have attempted in the code below):

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)
        {
        }
    }
}

InvestmentService.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);
            }
        }
    }
}

The problem is the following line:

importService = new ImportKuflink();

Because I only determine which child class to instantiate at run time, I cannot take advantage of DI here.

Under normal circumstances I would make the Import classes a DI based service so all dependencies are available, however I have to create the instance at run time so this I don't think is possible.

Is there a way to accomplish the above?

英文:

I have two classes: ImportBase.cs (Parent) and ImportFleet.cs (Child) which will in the future import a CSV file. Each child of ImportBase will implement a different implementation of the actual import code.
The approriate child class to use is determined in a Service class where the correct class is instantiated and the import method called. All is going well up until this point.

The problem is that I also want to Dependency Inject some repository classes into ImportBase and it's inherited classes (as I have attempted in the code below):

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&lt;InvestmentTransactionType&gt; 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)
        {
        }
    }
}

InvestmentService.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 &quot;fleet&quot;:
                    importService = new ImportFleet();   // problem is here
                    break;
            }

            if (importService != null)
            {
                importService.Import(file, UserId);
            }                          
        }
    }
}

The problem is the following line:

importService = new ImportKuflink();

Because I only determine which child class to instantiate at run time, I cannot take advantage of DI here.

Under normal circumstances I would make the Import classes a DI based service so all dependencies are available, however I have to create the instance at run time so this I don't think is possible.

Is there a way to accomplish the above?

答案1

得分: 1

是的,当然有一种方法可以实现这个。但我猜你正在使用的 DI 容器(比如来自微软的)可能不会在这方面帮助你。

我已经摆弄了类似这样的东西大约两年了,而且现在仍然忙于此。两年来一直在创建我的自己的 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);
英文:

Yes, of course there is a way to accomplish this. But I guess the DI container you are using (like from MS) won't help you here.

I've been fiddling with crap like this for like two years so far and still am busy with it. Two years of creating my own IoC framework.

Usual DI/IoC microkernels follow OCP and other really mandatory concepts and patterns. What I've done is leaving one single small door open. I won't bore you with details. The fundamental idea is that a class must be decorated with the appropriate attributes in code, and then is able to call the microkernel within its constructor (which has been called by a simple "var foo = new Barney();") to let an entity be modified like it had been created by the microkernel.

There is no(t yet a) way to hook into the plain new() code. Some cheer this, some don't. I'm with the cheerleaders here. Why? Side-effects.

Imagine this:

public class SomeNumber
{
    public int SomeValue { get; private set; }

    public SomeNumber()
    {
        SomeValue = 19;
    }
}

Okay? Let's assume you'd modified the new() process by whatever, then another user of your code goes:

Assert.AreEqual(19, someNumberEntity.SomeNumber);

and this code throws an exception, because for whatever reason your modifying code set the number to 7.

Now look at this code (from a unit test):

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: &quot;I am the doctor!&quot;)]
		public string Name { get; set; } = &quot;&quot;;

		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);
		}
	}
}

Clean code isn't always easily readable, but the aspects/attributes will raise coder's awareness.

PS: I posted the unit test code on purpose to show you what's happening.

Short version:

Microkernel.Modify(this);

答案2

得分: 1

你可以注入一个工厂,该工厂已经注入了相关服务。

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);
    }
}

然后在你的服务类中将该工厂作为依赖项进行注入。
英文:

You can inject a factory which has the services injected into it.

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);
    }
}

Then inject the factory as a dependency in your Service class.

答案3

得分: 1

以下是您代码的简化版本,演示了如何从DI服务容器中填充对象的实例。

在您的 InvestmentService 中:

  1. 注入 IServiceProvider
  2. 使用不太知名的工具 ActivatorUtilities 获取完全由DI注入的对象实例。
  3. 如果实现了 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&lt;ImportFleet&gt;(_serviceProvider);
        if (importFleet is IDisposable)
            disposable = importFleet as IDisposable;

        importService = importFleet as IImport;
        // 做你想做的任何操作

        disposable?.Dispose();
    }

    public async ValueTask ImportAsync()
    {
        IImport? importService = null;
        IDisposable? disposable = null;
        IAsyncDisposable? asyncDisposable = null;

        var importFleet = ActivatorUtilities.CreateInstance&lt;ImportFleet&gt;(_serviceProvider);

        if (importFleet is IDisposable)
            disposable = importFleet as IDisposable;

        if (importFleet is IAsyncDisposable)
            asyncDisposable = importFleet as IAsyncDisposable;

        importService = importFleet as IImport;
        // 做你想做的任何操作

        disposable?.Dispose();

        if (asyncDisposable is not null)
            await asyncDisposable.DisposeAsync();
    }
}
英文:

Here's a simplified version of your code that demonstrates how you can populate an instance of an object from a DI service container.

In your InvestmentService:

  1. Inject the IServiceProvider.
  2. Use the little known utility ActivatorUtilities to get a fully DI'd instance of your object.
  3. Make sure you dispose it properly if it implemenents IDisposable. I've included an async version if you use anything that needs a 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&lt;ImportFleet&gt;(_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&lt;ImportFleet&gt;(_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();
    }
}

huangapple
  • 本文由 发表于 2023年1月8日 23:54:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/75049196.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定