Search code examples
c#design-patternsdependency-injectioninversion-of-controlfactory-pattern

How can I inject named delegate function into a factory to resolve dependency in .NET 7


I am working on a .NET 7 c# solution. I have a class library that defines my base interface. For example.

public interface IFoo<T1, T2> 
        where T1 : Contract1 where T2 : Contract2
    {
        Task<ResponseContract> Function1(
            T1 T1Object, 
            T2 T2Object);
    }

In other class libraries, I have defined the implementations for this interface

public class Bar1Contract1 : Contract1{}
public class Bar1Contract2 : Contract2{}
public interface IBar1 : IFoo<Bar1Contract1, Bar1Contract2>{}
public class Bar1 : IBar1{
    public async Task<ResponseContract> Function1(
                Bar1Contract1 T1Object, 
                Bar1Contract2 T2Object){
        //Do something and return ResponseContract
    }
}

public class Bar2Contract1 : Contract1{}
public class Bar2Contract2 : Contract2{}
public interface IBar2 : IFoo<Bar2Contract1, Bar2Contract2>{}
public class Bar2 : IBar2{
    public async Task<ResponseContract> Function1(
                Bar2Contract1 T1Object, 
                Bar2Contract2 T2Object){
        //Do something and return ResponseContract
    }
}

Then I have a separate service that needs to call Bar1 implementation or Bar2 implementation based on user request. My approach to this is that I have created a factory method to create the IFoo object in my class libraries where my services are determined

public class FooFactory{
private readonly Func<string, IFoo<Contract1, Contract2>> _fooFactory;

        public FooFactory(Func<string, IFoo<Contract1, Contract2>> fooFactory)
        {
            this._fooFactory = fooFactory;
        }

        public IFoo<Contract1, Contract2> CreateFoo(string input)
        {
            return _fooFactory(input);
        }
}

public interface IFooService(){
Task<ServiceContract1> ServiceFunction1(string input, Contract1 contract1, Contract2 contract2);
}

public class FooService(){
        private FooFactory _fooFactory;
        public FooService(FooFactory fooFactory){
                this._fooFactory = fooFactory;
        }
        public async Task<ServiceContract1> ServiceFunction1(string input, Contract1 contract1, Contract2 contract2){
                //do something
                var foo = this._fooFactory.CreateFoo(input);
                var response = await foo.Function1(contract1, contract2);
                // do something else and return serviceContract1
        }
}

Finally I have a web application project where I'm registering my dependencies in my Project.cs

Builder.Services.AddScoped<IBar1, Bar1>();
Builder.Services.AddScoped<IBar2, Bar2>();
Builder.Services.AddSingleton<FooFactory>();
Builder.Services.AddScoped<IFooService, FooService>();

In my controller, I have

[Route("api/[controller]")]
[ApiController]
public class FooController : ControllerBase
{
    IFooService _fooService;
    public FooController(IFooService fooService) { 
        this._fooService = fooService;
    }
    public async Task<ActionResult> Route1(ViewModel model){
        var response = await this._fooService.ServiceFunction1(model.input, model.Contract1, model.Contract2);
        return Ok(response);
    }
}

However, I'm unable to create a delegate function that resolves IFoo based on an input string and inject it in my Factory. Is there any other approach that can be used to decide the implementation based on user provided string? I have also considered injecting a dictionary of inputs and Service implemntations and inject that in Factory instead of injecting a delegate method but not sure how I can do that in program.cs


Solution

  • public IFoo<Contract1, Contract2> CreateFoo(string input)
    However, I'm unable to create a delegate function ...

    The problem here is that neither Bar1 nor Bar2 are IFoo<Contract1, Contract2>. Imagine if you could do something like IFoo<Contract1, Contract2> foo = new Bar2(); and following implementation:

    public class Bar2Contract1 : Contract1
    {
        public int Bar2Contract1Specific { get; set; }
    }
    
    public class Bar2 : IBar2
    {
        public async Task<ResponseContract> Function1(
            Bar2Contract1 T1Object,
            Bar2Contract2 T2Object)
        {
            Console.WriteLine(T1Object.Bar2Contract1Specific);
            return default;
        }
    }
    

    You see the problem now? If not - foo allows to pass instance of Contract1 to Function1 while Bar2 requires Bar2Contract1, so this will blow up at runtime. Since C# is a statically typed language compiler prevents you from shooting yourself in the foot in such an easy way.

    Even if you make IFoo a contravariant interface it will not help, because it will be the other way around:

    public interface IFoo<in T1, in T2> ....
    
    IFoo<Bar1Contract1, Bar1Contract2> bar1ByInterface = new Bar1(); 
    IFoo<Contract1, Contract2> b = ...;
    bar1ByInterface = b; // compiles
    

    You need to rethink your approach, maybe inject specific factories like Func<Bar1>, Func<Bar2> or just the instances:

    public class FooService()
    {
        private readonly Bar1 _bar1;
        private readonly Bar2 _bar2;
    
        public FooService(Bar1 bar1, Bar2 bar2)
        {
            _bar1 = bar1;
            _bar2 = bar2;
        }
    
        public async Task<ServiceContract1> ServiceFunction1(string input, Contract1 contract1, Contract2 contract2)
        {
            //do something
            var result = await (input switch // example logic to determine bar to use
            {
                "bar1" => UseBar1(),
                "bar2" => UseBar2(),
                _ => throw new InvalidOperationException()
            });
            // do something else and return serviceContract1
            
            Task<ResponseContract> UseBar1()
            {
                Bar1Contract1 c1 = ... // convert contract1 to Bar1Contract1
                Bar1Contract2 c2 = ... // convert contract1 to Bar1Contract2
                return _bar1.Function1(c1, c2);
            }
            
            Task<ResponseContract> UseBar2()
            {
                Bar2Contract1 c1 = ... // convert contract1 to Bar2Contract1
                Bar2Contract2 c2 = ... // convert contract1 to Bar2Contract2
                return _bar2.Function1(c1, c2);
            }
        }
    }