最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - Injecting IServiceProvider due to ActivatorUtilities.CreateInstance - Stack Overflow

programmeradmin4浏览0评论

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
  • I would suggest reconsidering registering all task types in the container, because you can easily have thousands of registrations in the container without any performance penalty, while you benefit from the validation the container does, which, most importantly means it will check up front whether all the task's dependencies can be fulfilled. As all types must exist in your application, you could even register all of them all the time, and only allow resolving them based on their existence in the database config. – Steven Commented Mar 12 at 17:27
  • @Steven can you provide an example? The only way I can think of to remove it at runtime is to pass hte service provider, which I think is not good for DI. – lightning_missile Commented Mar 12 at 22:58
  • ActivatorUtilities.CreateInstance can construct types that aren't in DI. I would instead try to register each type directly so you can serviceProvider.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:42
  • For application code it is a bad idea to depend on the IServiceProvider, because it leads to the [Service Locator anti-pattern](mng.bz/WaQw ). For infrastructural code that is part of the Composition Root, however, it is completely fine to depend on IServiceProvider. Make sure that Worker is part of your Composition Root and you're fine. – Steven Commented Mar 13 at 10:16
Add a comment  | 

1 Answer 1

Reset to default 2

I 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)
发布评论

评论列表(0)

  1. 暂无评论