Consider this Worker class:
using Job.Interfaces;
using Job.Tasks;
namespace Job.WorkerService;
public sealed class Worker(IServiceProvider serviceProvider, ILogger<Worker> logger) : BackgroundService
{
// ...
private ITask CreateTask(string taskName)
{
var taskType = // ...
return (ActivatorUtilities.CreateInstance(serviceProvider, taskType) as ITask)!;
}
}
This code is working fine, but it doesn't feel right. In particular, I have to inject serviceProvider to Worker because I need it for creating task objects with ActivatorUtilities.CreateInstance. By doing this, my DI became wierd. Passing the whole IServiceProvider kind of defeats the reason for using DI in the first place, cause I am passing the whole container to Worker, not just what it needs to create itself.
Can anyone give suggestions on how to fix this? I've thought of 3 approaches. I don't like them very much. Looking for better approaches:
- put the CreateTask method inside a factory, then inject serviceProvider in there, and register to the DI container. This will fix my issue with the Worker class, but creating another class TaskFactory and perhaps another interface ITaskFactory just to incapsulate one very small method seems unwieldy to me.
- Inject a lambda function to the Worker class where it will call ActivatorUtilities.CreateInstance. I will not need to inject serviceProvider since the lambda function will have it scoped in Program.cs. I also don't want this approach since the lambda function has no other reason to be there.
- register all my Task objects to the DI container, then inject all of them. This is bad because I will be dynamically creating the tasks based on a config from a database, I won't be using all of them.
Are my concerns valid regarding my 3 approaches? Maybe I am missing something. Is there a better approach?
Consider this Worker class:
using Job.Interfaces;
using Job.Tasks;
namespace Job.WorkerService;
public sealed class Worker(IServiceProvider serviceProvider, ILogger<Worker> logger) : BackgroundService
{
// ...
private ITask CreateTask(string taskName)
{
var taskType = // ...
return (ActivatorUtilities.CreateInstance(serviceProvider, taskType) as ITask)!;
}
}
This code is working fine, but it doesn't feel right. In particular, I have to inject serviceProvider to Worker because I need it for creating task objects with ActivatorUtilities.CreateInstance. By doing this, my DI became wierd. Passing the whole IServiceProvider kind of defeats the reason for using DI in the first place, cause I am passing the whole container to Worker, not just what it needs to create itself.
Can anyone give suggestions on how to fix this? I've thought of 3 approaches. I don't like them very much. Looking for better approaches:
- put the CreateTask method inside a factory, then inject serviceProvider in there, and register to the DI container. This will fix my issue with the Worker class, but creating another class TaskFactory and perhaps another interface ITaskFactory just to incapsulate one very small method seems unwieldy to me.
- Inject a lambda function to the Worker class where it will call ActivatorUtilities.CreateInstance. I will not need to inject serviceProvider since the lambda function will have it scoped in Program.cs. I also don't want this approach since the lambda function has no other reason to be there.
- register all my Task objects to the DI container, then inject all of them. This is bad because I will be dynamically creating the tasks based on a config from a database, I won't be using all of them.
Are my concerns valid regarding my 3 approaches? Maybe I am missing something. Is there a better approach?
Share Improve this question edited Mar 13 at 1:36 Zhi Lv 22k1 gold badge27 silver badges37 bronze badges asked Mar 11 at 8:33 lightning_missilelightning_missile 3,0845 gold badges38 silver badges67 bronze badges 4 |1 Answer
Reset to default 2I would personally go with something like option 2, register a Func<Type, ITask>
and inject that
It makes your code clean and typesafe.
public sealed class Worker(Func<Type, ITask> taskFactory, ILogger<Worker> logger)
: BackgroundService
{
// ...
private ITask CreateTask(string taskName)
{
var taskType = // ...
return taskFactory(taskType);
}
}
And the registration is super easy.
services.AddSingleton<Func<Type, ITask>>(
sp => t => ActivatorUtilities.CreateInstance(sp, t) as ITask)
ActivatorUtilities.CreateInstance
can construct types that aren't in DI. I would instead try to register each type directly so you canserviceProvider.GetService<T>()
. Perhaps creating a service scope first, so you can use and dispose scoped services in one unit of work. You'll have to manage all of that somewhere, which makes it difficult to remove all knowledge of the service provider. – Jeremy Lakeman Commented Mar 13 at 2:42Worker
is part of your Composition Root and you're fine. – Steven Commented Mar 13 at 10:16