asp.net 核心服务定位器如何在控制台应用程序中避免
asp.net core service locator how to avoid in cosole application
我对如何在使用控制台应用程序时避免服务定位器感到有点困惑
计划
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 中有魔法恰好可以实现这一点。我将如何在不使用服务定位器模式的情况下进行注入?
基本上你不想将 IServiceProvider
传递给任何 class 除了引导程序 (Startup
) 或工厂 methods/classes 因为这与你的 classes 到特定的 IoC 容器。
你可以做的是将依赖项添加到你的 CommandLineApplication
class 并在 Main
方法中解决它,然后你可以从这里开始你的依赖注入链。只要您 need/want 立即解决所有依赖项,这就会起作用。
当你遇到只需要加载它的一个子集的情况(即在传递某个参数时使用不同的服务或程序逻辑),你将需要一种工厂(工厂是一个瘦包装器,在传递对象之前创建和配置对象,在 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
}
}
如果您需要更复杂的配置或使用运行时参数,创建工厂可能更有意义 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);
}
}
以及用法
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(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);
}
}
我对如何在使用控制台应用程序时避免服务定位器感到有点困惑
计划
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 中有魔法恰好可以实现这一点。我将如何在不使用服务定位器模式的情况下进行注入?
基本上你不想将 IServiceProvider
传递给任何 class 除了引导程序 (Startup
) 或工厂 methods/classes 因为这与你的 classes 到特定的 IoC 容器。
你可以做的是将依赖项添加到你的 CommandLineApplication
class 并在 Main
方法中解决它,然后你可以从这里开始你的依赖注入链。只要您 need/want 立即解决所有依赖项,这就会起作用。
当你遇到只需要加载它的一个子集的情况(即在传递某个参数时使用不同的服务或程序逻辑),你将需要一种工厂(工厂是一个瘦包装器,在传递对象之前创建和配置对象,在 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
}
}
如果您需要更复杂的配置或使用运行时参数,创建工厂可能更有意义 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);
}
}
以及用法
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(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);
}
}