0
votes

I'm trying to use DI to bind a different implementation of my networking class. I've been able to do this successfully using a none generic version of the class. My implementation is as follows:

class MainClass
{
    public static void Main(string[] args)
    {
        IKernel kernel;

        // Hardcode here but will be managed by build system.
        bool runningInProd = false;

        if (runningInProd)
        {
            kernel = new StandardKernel(new RealNetworkModule());
        }
        else
        {
            kernel = new StandardKernel(new FakeNetworkModule());
        }

        Session session = kernel.Get<Session>();

        session.Authenticate();
    }

    public class RealNetworkModule : NinjectModule
    {
        public override void Load()
        {
            Bind(typeof(IRequestSender)).To(typeof(RealRequestSender));
        }
    }

    public class FakeNetworkModule : NinjectModule
    {
        public override void Load()
        {
            Bind(typeof(IRequestSender)).To(typeof(FakeRequestSender));
        }
    }
}

Class that uses my IRequestSender:

public class Session
{
    IRequestSender requestSender;

    [Inject]
    public Session(IRequestSender requestSender)
    {
        this.requestSender = requestSender;
    }

    public void Authenticate()
    {
        Console.WriteLine(requestSender.Send("Hello There"));
    }
}

The IRequestSender interface:

public interface IRequestSender
{
    string Send(string request);
}

And the two different implementations:

public class RealRequestSender: IRequestSender
{
    public string Send(string request)
    {
        return "RealRequestSender right back at you: " + request;
    }
}

public class FakeRequestSender: IRequestSender
{
    public string Send(string request)
    {
        return "FakeRequestSender right back at you: " + request;
    }
}

This is very straightforward and it works; however, what I need is for my IRequestSender to use Generic types rather than string for input output:

public interface IRequestSender<RequestT, ResponseT> where RequestT: class where ResponseT: class
{
    RequestT Send(RequestT request);
}

And the impl's:

public class FakeRequestSender<RequestT, ResponseT> : IRequestSender<RequestT, ResponseT> where RequestT : class where ResponseT : class
{
    public RequestT Send(RequestT request)
    {
        throw new NotImplementedException();
    }
}

public class RealRequestSender<RequestT, ResponseT> : IRequestSender<RequestT, ResponseT> where RequestT : class where ResponseT : class
{
    public RequestT Send(RequestT request)
    {
        throw new NotImplementedException();
    }
}

I've come across several examples that address this issue and I've tried to base my implementation on them but I have failed. Here are the two problems that I'm running into:

1) Binding: this is the main problem. Here is what my binding looks like based on solutions I have seen online:

public class RealNetworkModule : NinjectModule
{
    public override void Load()
    {
        Bind(typeof(IRequestSender<>)).To(typeof(RealRequestSender<>));
    }
}

VSCode gives me the error:

Program.cs(29,29): Error CS0305: Using the generic type 'IRequestSender<RequestT, ResponseT>' requires 2 type arguments (CS0305) (DI)

Based on this error and what I have read online it is still not clear to me what I need to do here.

2) Accessing IRequestSender: the solution to this might be clear once I know how to fix binding. In the original implementation I used [Inject] to get access to the IRequestSender I need in my Sessions class. However now in the generic version I imagine I will not be able to do this. If I were to use RequestSender without DI it would look like:

RequestSender <AuthRequest, AuthResponse> requestSender = new RequestSender<AuthRequest, AuthResponse>();

or

RequestSender <UserRequest, UserResponse> requestSender = new RequestSender< UserRequest, UserResponse >();

for any number of different types.

So I'm not sure how to go about accessing the RequestSender in this scenario.

1
I'm not sure if this is your entire problem, but the message Using the generic type 'IRequestSender<RequestT, ResponseT>' requires 2 type arguments is significant... try changing your binding to Bind(typeof(IRequestSender<,>)).To(typeof(RealRequestSender<,>)); (note the commas). - xander
Ya that was it. Dumb mistake. Ok that fixes problem 1. Great ty! - Przemek Lach

1 Answers

1
votes

Given your current interface, you'll have to specify the generic type arguments when injecting. Assuming your request and response are both strings, your constructor would look like:

public Session(IRequestSender<string, string> requestSender)
{
    this.requestSender = requestSender;
}

If you don't want to specify the arguments at creation/injection time, you'll have to change the design a bit. I can't say for certain with the sample code you provided, but it might be possible to remove the generic type args from your interface and place them on the method instead:

public interface IRequestSender
{
    RequestT Send<RequestT, ResponseT>(RequestT request)
        where RequestT: class
        where ResponseT: class;
}

With that definition, you'd inject IRequestSender, and then specify the generic type parameters when calling. For example,

string myResponse = requestSender.Send<string, string>("my string");