我目前正在尝试实施一些工人服务。如果我可以告诉它基于命令行添加服务,那就太好了。
假设我使用命令“Background --service1 --service2”。 我希望命令处理程序将服务添加到主机。假设我在没有任何参数的情况下运行它,它会立即运行并停止。
我当前的实现无法工作,因为服务对象在添加到托管服务时始终为空,因为命令行尚未解析。
我能想象这种工作的唯一方法是分离命令行和主机构建器。然后从命令行获取结果并使用它来构建构建器。但我希望有更好的方法来做到这一点,因为有一个用于 System.CommandLine 的主机包。
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Hosting;
using System.CommandLine.Parsing;
class Program
{
public static async Task<int> Main(string[] args)
{
IServiceCollection commandServices = new ServiceCollection();
var parser = BuildCommandLine(ref commandServices)
.UseHost(_ => Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
ConfigureServices(context, services, commandServices);
AddLogging(context, services);
}),
hostBuilder =>
{
hostBuilder.UseCommandHandler<BackgroundCommand, BackgroundCommand.BackgroundCommandHandler>();
})
.UseDefaults() // Ensure defaults are added last in the chain
.Build();
return await parser.InvokeAsync(args);
static CommandLineBuilder BuildCommandLine(ref IServiceCollection services)
{
// Create root command with description
var rootCommand = new RootCommand("This is your command-line app's root command.");
var backgroundCommand = new BackgroundCommand();
backgroundCommand.Handler = new BackgroundCommand.BackgroundCommandHandler(ref services);
rootCommand.AddCommand(backgroundCommand);
return new CommandLineBuilder(rootCommand);
}
}
}
public class BackgroundCommand : Command
{
public BackgroundCommand()
: base("Background", "Runs background workers based on options")
{
var service1Option= new Option<bool>(aliases: ["--service1"], description: "", getDefaultValue: () => false);
var service2Option= new Option<bool>(aliases: ["--service2"], description: "", getDefaultValue: () => false);
AddOption(service1Option);
AddOption(service2Option);
}
public class BackgroundCommandHandler : ICommandHandler
{
private readonly IServiceCollection _services;
public bool Service1 { get; set; } = false;
public bool Service2 { get; set; } = false;
public BackgroundCommandHandler(ref IServiceCollection services)
{
_services = services;
}
public int Invoke(InvocationContext context)
{
throw new NotImplementedException();
}
public async Task<int> InvokeAsync(InvocationContext context)
{
if (Service1)
{
_services.AddHostedService<Service1Worker>();
}
if(Service2)
{
_services.AddHostedService<Service2Worker>();
}
return 0;
}
}
}
所以我遇到的问题是这样的。 托管的工作方式是添加服务,然后运行命令行。因此,一旦解析了命令行,您就无法添加更多服务。
有一个简单的解决方法,那就是仅使用标准主机,并使用 CommandLineBuilder()。
Progam.cs
var builder = Host.CreateApplicationBuilder(args);
ConfigureWorkerServices(builder.Services, args);
void ConfigureWorkerServices(IServiceCollection services, string[] args)
{
var commandLineBuilder = new CommandLineBuilder();
commandLineBuilder.Command.AddCommand(new WorkerCommand(services));
var parser = commandLineBuilder.Build();
parser.InvokeAsync(args);
}
基本命令
public abstract class BaseCommand<T> : Command where T : ICommandModel
{
public IServiceCollection Services { get; init; }
public BaseCommand(string name, IServiceCollection services, string? description = null) : base(name, description)
{
Services = services;
Handler = CommandHandler.Create<T>(ParseOptions);
}
/// <summary>
/// This fills the Settings.
/// </summary>
/// <param name="model">Moddel to fill.</param>
public abstract void ParseOptions(T model);
}
后台命令
public abstract class BackgroundCommand : BaseCommand<BackgroundCommandModel>
{
public Option<bool> Background = new Option<bool>(aliases: ["--Background", "--background", "-b", "-B"], description: "Run the process as a background task.", getDefaultValue: () => false);
public BackgroundCommand(string name, IServiceCollection services, string? description = null) : base(name, services, description)
{
AddOption(Background);
}
public override void ParseOptions(BackgroundCommandModel model)
{
ArgumentNullException.ThrowIfNull(model);
Services.AddSingleton<Processing>();
if (model.Background)
{
Services.AddHostedService<ProcessingWorker>();
}
else
{
Services.AddHostedService<OneTimeRunHostedService<Processing>>();
}
}}
后台命令模式
public class BackgroundCommandModel : ICommandModel
{
public bool Background { get; set; }
}
因此,在构建主机之前,您需要单独运行 CommandLineBuiler。这样您就可以注入您可能需要的所有服务。