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

I'm a little confused about how to avoid service locator when using a console application

Program

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

CommandLineOptions

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

I need to inject the IExampleRepository into the new Task1()

Task1

public class Task1
{
    public Task1()
    {

    }

    private readonly IExampleRepository _exampleRepository;

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


    public void Process() {
      ....
    }

So basically my understanding is that I register my dependencies, then I should be able to inject them throughout my classes. I'm not sure if I need to pass my serviceProvider down?

I believe that in MVC there is magic that happens to accomplish this. How would I go about injecting without using the service locator pattern?


Basically you don't want to have to pass IServiceProvider to any class except the bootstrapper ( Startup ) or factory methods/classes as this ties your classes to the specific IoC container.

What you can do is add dependencies to your CommandLineApplication class and resolve it in the Main method and from here you can start your dependency injection chain. This will work as long as you need/want to resolve all of your dependencies at once.

When you get in an situation where you only need to load a subset of it (ie using a different service or program logic when a certain parameter is passed), you'll need a kind of factory (a factory is a thin wrapper that creates and configures an object before passing it, in case of IoC it also resolves the dependencies).

In the factory implementation it's okay to reference the container, if necessary (you need scoped dependencies or transient resolving per object creation). You'll also need a factory if you need more than one instance of Task1 .

There are two ways. For very simple factories you can use a factory method, that can be directly used while doing your IServiceCollection registrations.

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

then inject into your dependency.

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

If you need more complex configuration or with runtime parameter, it may make more sense to make a factory class.

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

And the usage

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

This was my attempt at using your code in a test app, but I'm unsure if I'm doing this correctly.

I'm also unsure about how to pass in the connection string for the method in MyTaskApplication CreateNewTask(connectionString)

Will it need to be passed in as a property, or part of the constructor for MyTaskApplication or an alternative method?

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);
    }
}
链接地址: http://www.djcxy.com/p/77762.html

上一篇: 工厂方法模式带来的好处

下一篇: asp.net核心服务定位器如何避免在cosole应用程序中