【问题标题】:asp.net core service locator how to avoid in cosole applicationasp.net核心服务定位器如何避免在控制台应用程序中
【发布时间】:2016-04-29 01:00:20
【问题描述】:

我对如何在使用控制台应用程序时避免服务定位器感到有些困惑

计划

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>();
    }

命令行选项

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 注入新的 Task1()

任务1

public class Task1
{
    public Task1()
    {

    }

    private readonly IExampleRepository _exampleRepository;

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


    public void Process() {
      ....
    }

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

我相信,在 MVC 中,有一种神奇的方式恰好可以做到这一点。如果不使用服务定位器模式,我将如何进行注入?

【问题讨论】:

    标签: c# asp.net-core dependency-injection console-application


    【解决方案1】:

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

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

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

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

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

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

    然后注入你的依赖。

    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
        }
    }
    

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

    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);
        }
    }
    

    及用法

    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); 
        }
    }
    

    【讨论】:

    • 感谢您的详尽解释,要一个工厂实现的小例子会不会太麻烦?我倾向于学习更好的例子。我在看这个:stackoverflow.com/questions/557742/…
    • 分别添加了工厂方法和工厂类的两个示例
    • 感谢您的努力 :)
    • 实际上我正在努力解决如何调用 MyTaskApplication ... 我不确定我是否正确地解决了我的 DI 查看您的第二个示例它期待一个 ITaskFactory 但我认为 DI 应该能解决吗?
    • 您也可以向 DI 注册您的 MyTaskApplicationservices.AddSingleton&lt;MyTaskApplication&gt;(); 并在创建 IServiceProvider 之后(通过 IServiceProvider provider = services.BuildServiceProvider();,您可以使用 var application = provider.GetService&lt;MyTaskApplication&gt;() 解析您的主应用程序,最后使用 application.Run() 运行它。有关更完整的示例,请参阅 stackoverflow.com/a/36908537/455493
    【解决方案2】:

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

    我也不确定如何为 MyTaskApplication CreateNewTask(connectionString) 中的方法传入连接字符串

    是否需要作为属性传入,或者作为 MyTaskApplication 的构造函数的一部分或替代方法传入?

    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);
        }
    }
    

    【讨论】:

    • 我认为我需要将我的任务设置为 AddTransient
    猜你喜欢
    • 2019-02-02
    • 2018-05-14
    • 2018-06-28
    • 2023-03-21
    • 2015-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多