A similar question has been answered here:

How can I pass a runtime parameter as part of the dependency resolution?

However, I was wondering how this can be done when registering a generic class?

Normally, I would register it as following:

services.AddScoped(typeof(ITest<>), typeof(Test<>));

But what if I want to pass a runtime parameter to constructor? Without using DI, it would be something like:

new Test<MyClass>(string mystring, int myInt)

In the linked answer it's suggests using a factory method but this is giving me an error if I don't pass it the exact type.

The alternative would be to get an instance without passing a runtime parameter in the constructor and instead using a setter method after getting exact instance. I would like to avoid this however because every time after getting instance you must remember to call setter method.

Is there some way around it? I guess I could use some factory class instead of registering it in startup class...

EDIT: After reading Steven's answer which was very useful, I updated question with more concrete example: Following example is inside some method:

//instance of repository are passed inside constructor of class
//calling some to update/insert
//IMPORTANT - calling external service I want save parameters to db no matter what
using(var ctx=new DbContext())
{
//create log object
ctx.logs.add(Obj)
ctx.save()
}
//some code after

Let's say I want to be consistent and call method of my loggingrepository and there add logging object and save everything to database However, every repository in constructor accepts DbContext, which is registered as scoped (durig one request). If it's inside transaction, saving depends about code after calling external service and it can throw exception and save nothing.

So yeah, I could create new dbContext and pass it in logging method or call some private logging function and save inside it, but point is that if I would ask for instance of loggingRepository I would want DI to pass this localy created dbContext variable to constructor and not one registered as scoped inside startup method, so that addind and saving log happens no matter what external service or code after calling it does.

My situation in something similar, but it's going for some data in db based on current user and I don't wanna pass same parameter to numerous method, but only inside class constructor.

1 Answers

2
Steven On

The general solution in injecting primitive configuration values into your application components, is to extract them into a Parameter Object. This gives those values a new, unambiguous type, which can be registered into your container:

// Parameter Object
public TestConfiguration
{
    public string Mystring;
    public int MyInt;
}

// (Generic) class using the Parameter Object
public class Test<T>
{
    public Test(TestConfiguration config) { ... }
}

// Registering both
services.AddScoped(typeof(ITest<>), typeof(Test<>));
services.AddSingleton(new TestConfiguration { Mystring = ..., Myint = ... });

Configuration values are not considered to be runtime data as their values are known at startup and constant for the duration of the application. That's why you can supply them to the constructors of your application components.

Real runtime data, however, should not be passed on to a component during construction. Runtime data are values that are not known at startup and typically passed along by the user through a web request, are retrieved from the database, session, or anything that can change during the lifetime of the application.

Instead of passing runtime data in through the constructor, you should either:

  1. Pass runtime data through method calls of the API or

  2. Retrieve runtime data from specific abstractions that allow resolving runtime data.

You can find more information about passing runtime data here.