0

I'm trying to understand how to create an instance of a service (for example, ITransport) with some input config at runtime using the Factory Pattern and Dependency Injection (Microsoft.Extensions.DependencyInjection). I'm pretty sure that using Ninject would make the task easier, but still, I'd like to stick with Microsoft's DI.

For example, here are our services:

public interface ITransport
{
    string Type { get; }
    void Move();
}
public class CarConfig { }
public class Car(CarConfig config) : ITransport
{
    public string Type => nameof(Car);
    public void Move() { /* logic */ }
}
public class BoatConfig { }
public class Boat(BoatConfig config) : ITransport
{
    public string Type => nameof(Boat);
    public void Move() { /* logic */ }
}

Or perhaps it's better like this:

public class Car(IConfig config) : ITransport {...}
public class Boat(IConfig config) : ITransport {...}

Each of them has some input configuration in the constructor. Also, each of them will have different dependencies, so they need to be created only through DI.

Here's the factory for getting the necessary service (transport):

public interface ITransportFactory
{
    ITransport GetTransport(string type, object config);
    // or
    ITransport GetTransport(string type, IConfig config);
}

I see a dozen ways to approach implementing this, but I'm not sure if any of them will work. Please help.

1
  • There's an ActivatorUtilities.CreateInstance methods that you can use inside your ITransportFactory implementation. It allows you to supply the runtime data and it will compose the desired instance based on known dependencies and runtime data. Commented May 2, 2024 at 13:41

2 Answers 2

0

You can inject your factory into a class and then use/call it with your parameters. Similar to what you can see here.

Your class could then look like this:

public class YourClass
{
   private readonly ITransportFactory _transportFactory;

   public YourClass(ITransportFactory transportFactory)
   {
       _transportFactory = transportFactory;
   }

   public void YourTransportMethod(string type, object config)
   {
        var transport = _transportFactory.GetTransport(type, config);
   }
Sign up to request clarification or add additional context in comments.

Comments

0

I would recommend to look into using AtivatorUtilities.CreateInstance:

Instantiates a type with constructor arguments provided directly or from an IServiceProvider.

Which allows to pass argument not registered in the DI. You need to somehow map from the passed parameters to the actual type you will need. In the provided example I would consider using type of the config as discriminator for target type (but dependent on the actual type hierarchy you might need some other approach) and leveraging some generics:

public interface ITransportFactory
{
    ITransport GetTransport<T>(T config) where T : IConfig;
}

public interface IConfig;

public class CarConfig : IConfig;
public class Car(CarConfig config) : ITransport
{
    // ...
}

public class BoatConfig : IConfig;
public class Boat(BoatConfig config, ISomeDepFromDi dep) : ITransport
{
    // ...
}

public interface ISomeDepFromDi;
public class SomeDepFromDi : ISomeDepFromDi;

With following implementation for the factory:

public class TransportFactory(IServiceProvider serviceProvider) : ITransportFactory
{
    private readonly IServiceProvider _serviceProvider = serviceProvider;

    private static Dictionary<Type, Type> TypeMap = new()
    {
        { typeof(BoatConfig), typeof(Boat) },
        { typeof(CarConfig), typeof(Car) },
    };

    public ITransport GetTransport<T>(T config) where T : IConfig
    {
        if (TypeMap.TryGetValue(typeof(T), out var transportType))
        {
            return ActivatorUtilities.CreateInstance(serviceProvider, transportType, config) as ITransport;
        }

        throw new InvalidOperationException($"Unknown config type {typeof(T)}");
    }
}

And usage:

var services = new ServiceCollection();
services.AddTransient<ITransportFactory, TransportFactory>();
services.AddTransient<ISomeDepFromDi, SomeDepFromDi>();
var serviceProvider = services.BuildServiceProvider();
var transportFactory = serviceProvider.GetRequiredService<ITransportFactory>();

Assert.IsInstanceOf<Car>(transportFactory.GetTransport(new CarConfig()));
Assert.IsInstanceOf<Boat>(transportFactory.GetTransport(new BoatConfig()));

Again - without seeing an actual domain it is hard to recommend the best course of action here. You might need the string type discriminator and change the TypeMap to be Dictionary<string, Type>. Another thing which could be done is to build the TypeMap via reflection or by analyzing the service collection (something similar to what is done here).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.