skip to Main Content

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<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?

3

Answers


  1. 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: "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);
            }
        }
    }
    

    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);
    
    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
  3. 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<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();
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search