关于依赖注入:asp.net核心服务定位器如何在控制台应用程序中避免

asp.net core service locator how to avoid in cosole application

我有点困惑,在使用控制台应用程序时如何避免使用服务定位器。

程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static int Main(string[] args)
{        
    // Configuration
        var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").AddEnvironmentVariables().Build();

        // DI container
        var services = new ServiceCollection();
        ConfigureServices(services, configuration);
        var serviceProvider = services.BuildServiceProvider();

        // Do I pass along the serviceProvider?
        // Can resolve using locator pattern do I just use this in my classes?
        // var exampleRepository = _serviceProvider.GetService<IExampleRepository>();

          // Execute the correct command based on args
        return CommandLineOptions.Execute(args);

}

 private static void ConfigureServices(IServiceCollection services, IConfiguration configuration)
    {
        services.AddScoped<ApplicationDbContext>((s) => new ApplicationDbContext(configuration.GetSection("Data:DefaultConnection:ConnectionString").Value));
        services.AddScoped<IExampleRepository, ExampleRepository>();
    }

命令行选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public static class CommandLineOptions
{        
    public static int Execute(string[] args, IServiceProvider serviceProvider)
    {
        try
        {
            var app = new CommandLineApplication
            {
                Name ="dnx abc",
                FullName ="Abc Commands",
                Description ="ABC",

            };

            app.VersionOption("--version", PlatformServices.Default.Application.ApplicationVersion);
            app.HelpOption("-?|-h|--help");

            app.OnExecute(() =>
                {
                    //ShowLogo();
                    app.ShowHelp();
                    return 2;
                });

            app.Command(
           "task",
            task=>
            {
                task.Name ="Task1";
                task.FullName ="Task1";
                task.Description ="Tasks";                    
                task.HelpOption("-?|-h|--help");
                task.OnExecute(() => { task.ShowHelp(); return 0; });

                task.Command(
                   "task1",
                    data =>
                    {
                        data.FullName ="Task1 command";
                        data.Description ="Task1";

                        data.OnExecute(() =>
                        {
                            // Need to inject
                            var p = new Task1();
                            p.Process()  

                            return 0;
                        });

我需要将IExampleRepository注入新的任务1()。

任务1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Task1
{
    public Task1()
    {

    }

    private readonly IExampleRepository _exampleRepository;

    public Task1(IExampleRepository exampleRepository)
    {
        _exampleRepository = exampleRepository;
    }


    public void Process() {
      ....
    }

所以基本上我的理解是注册依赖项,然后我应该能够在我的类中注入它们。我不确定是否需要传递我的服务提供商?

我相信在MVC中,实现这一点是有魔力的。在不使用服务定位器模式的情况下如何进行注入?


基本上,您不需要将IServiceProvider传递给除引导程序(Startup或工厂方法/类之外的任何类,因为这将您的类与特定的ioc容器相关联。

您所能做的就是将依赖项添加到您的CommandLineApplication类中,并在Main方法中解析它,然后从这里开始您的依赖项注入链。只要您需要/希望一次解决所有依赖项,这将起作用。

当您遇到只需要加载它的一个子集的情况(即,当传递某个参数时使用不同的服务或程序逻辑),您将需要一种工厂(工厂是一种薄包装器,它在传递对象之前创建和配置对象,如果是IOC,它还解析依赖项)。

在工厂实现中,如果需要,可以引用容器(您需要范围依赖性或每个对象创建的临时解析)。如果需要多个Task1实例,还需要一个工厂。

有两种方法。对于非常简单的工厂,您可以使用工厂方法,这可以在进行IServiceCollection注册时直接使用。

1
2
3
4
services.AddTransient<Task1>();
services.AddTransient<Func<Task1>>( (serviceProvider) => {
    return () => serviceProvider.GetService<Task1>();
});

然后注入你的依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyTaskApplication
{
    private readonly Func<Task> taskFactory;
    public MyApplicationService(Func<Task> taskFactory)
    {
         this.taskFactory = taskFactory;
    }

    public void Run()
    {
        var task1 = taskFactory(); // one instance
        var task2 = taskFactory(); // another instance, because its registered as Transient
    }
}

如果您需要更复杂的配置或带有运行时参数,那么创建工厂类可能更有意义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class TaskFactory : ITaskFactory
{
    private readonly IServiceProvider services;

    public TaskFactory(IServiceProvider services)
    {
         this.services = services;
    }

    public Task1 CreateNewTask()
    {
        // get default task service, which is transient as before
        // so you get a new instance per call
        return services.GetService<Task1>();
    }

    public Task1 CreateNewTask(string connectionString)
    {
         // i.e. when having multiple tenants and you want to
         // to the task on a database which is only determined at
         // runtime. connectionString is not know at compile time because
         // the user may choose which one he wants to process

         var dbContext = MyDbContext(connectionString);
         var repository = new ExampleRepository(dbContext);

         return new Task1(repository);
    }
}

及用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyTaskApplication
{
    private readonly ITaskFactory taskFactory;
    public MyApplicationService(ITaskFactory taskFactory)
    {
         this.taskFactory = taskFactory;
    }

    public void Run()
    {
        // Default instance with default connectionString from appsettings.json
        var task1 = taskFactory.CreateNewTask();

        // Tenant configuration you pass in as string
        var task2 = taskFactory.CreateNewTask(tenantConnectionString);
    }
}


这是我尝试在测试应用程序中使用您的代码,但我不确定是否正确执行了此操作。

我也不确定如何传入mytaskapplication createnewtask(connection string)中方法的连接字符串。

它是否需要作为MyTaskApplication的属性或构造函数的一部分或其他方法传入?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
public class Program
{
    public static void Main(string[] args)
    {
        var services = new ServiceCollection();
        services.AddScoped<Task1>();
        services.AddScoped<MyTaskApplication>();
        services.AddTransient<ITaskFactory, TaskFactory>();
        var serviceProvider = services.BuildServiceProvider();

        var m = serviceProvider.GetService<MyTaskApplication>();
        m.Run();

    }
}

public class TaskFactory : ITaskFactory
{
    private readonly IServiceProvider services;

    public TaskFactory(IServiceProvider services)
    {
        this.services = services;
    }

    public Task1 CreateNewTask()
    {            
        // get default task service, which is transient as before
        // so you get a new instance per call
        return services.GetService<Task1>();
    }

    public Task1 CreateNewTask(string connectionString)
    {
        // i.e. when having multiple tenants and you want to
        // to the task on a database which is only determined at
        // runtime. connectionString is not know at compile time because
        // the user may choose which one he wants to process

        //var dbContext = MyDbContext(connectionString);
        //var repository = new ExampleRepository(dbContext);


        return new Task1(connectionString);
    }
}

public interface ITaskFactory
{
    Task1 CreateNewTask();

    Task1 CreateNewTask(string connectionString);
}

public class MyTaskApplication
{
    private readonly ITaskFactory taskFactory;
    private string tenantConnectionString;

    public MyTaskApplication(ITaskFactory taskFactory)
    {
        this.taskFactory = taskFactory;
    }

    public void Run()
    {
        // Default instance with default connectionString from appsettings.json
        var task1 = taskFactory.CreateNewTask();
        task1.Process();

        // Tenant configuration you pass in as string
        var task2 = taskFactory.CreateNewTask(tenantConnectionString);
        task2.Process();

        Console.WriteLine("Running");
    }
}

public class Task1
{
    private string _repositoryText;

    public Task1()
    {
        _repositoryText = String.Empty;  
    }

    public Task1(string repositoryText)
    {
        _repositoryText = repositoryText;
    }

    public void Process()
    {
        Console.WriteLine("process:" + _repositoryText);
    }
}