如何在使用IHostedService的控制台应用程序中访问命令行参数?

5

我无法弄清如何在我的ConsoleHostedService实现类中访问命令行参数。我在源代码中看到CreateDefaultBuilder(args)以某种方式将其添加到配置中...命名为Args...

有了主程序:

internal sealed class Program
{
    private static async Task Main(string[] args)
    {
        await Host.CreateDefaultBuilder(args)
            .UseContentRoot(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
            .ConfigureServices((context, services) =>
            {
                services.AddHostedService<ConsoleHostedService>();
            })
            .RunConsoleAsync();
    }
}

以及托管服务:

internal sealed class ConsoleHostedService : IHostedService
{
    public ConsoleHostedService(
        IHostApplicationLifetime appLifetime,
        IServiceProvider serviceProvider)
    {
        //...
    }
}

9
System.Environment.GetCommandLineArguments() 是一个用于获取命令行参数的方法。 - Dai
这取决于您实际尝试从命令行中提取的内容。请提供一个使用案例的示例。 - Nkosi
@Nkosi:我有一种感觉,绝对不应该。我想要按照原样访问args数组。 - g.pickardou
@Dai 谢谢,这将是备选方案,尽管这可能不是 .NET 的本意方式,除非 CreateDefaultBuilder(args) 恰好在 mainargs 处调用。另外,Environment.CommandLine 是一个单块字符串,当然我可以使用空格拆分它,但如果我得到了原始的 OS 数组,它会 更少出错,并且更兼容。 - g.pickardou
如果您想保留args数组,那么可以创建一个带有string[]属性的模型,使用args初始化该模型,并将其添加到服务集合中,以便在需要时进行注入。@g.pickardou - Nkosi
1
传递给配置的是为了通过CommandLineConfigurationProvider将配置绑定到强类型上。详见https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-2.1#command-line-configuration-provider - Nkosi
2个回答

4

我不相信有一种内置的 DI 方法可以获取命令行参数 - 但可能处理命令行参数是您的主机应用程序的责任,应该通过 IConfigurationIOptions 等传递主机/环境信息。

无论如何,只需定义自己的可注入项:

public interface IEntrypointInfo
{
    String CommandLine { get; }

    IReadOnlyList<String> CommandLineArgs { get; }

    // Default interface implementation, requires C# 8.0 or later:
    Boolean HasFlag( String flagName )
    {
        return this.CommandLineArgs.Any( a => ( "-" + a ) == flagName || ( "/" + a ) == flagName );
    }
}

/// <summary>Implements <see cref="IEntrypointInfo"/> by exposing data provided by <see cref="System.Environment"/>.</summary>
public class SystemEnvironmentEntrypointInfo : IEntrypointInfo
{
    public String CommandLine => System.Environment.CommandLine;

    public IReadOnlyList<String> CommandLineArgs => System.Environment.GetCommandLineArgs();
}

/// <summary>Implements <see cref="IEntrypointInfo"/> by exposing provided data.</summary>
public class SimpleEntrypointInfo : IEntrypointInfo
{
    public SimpleEntrypointInfo( String commandLine, String[] commandLineArgs )
    {
        this.CommandLine = commandLine ?? throw new ArgumentNullException(nameof(commandLine));
        this.CommandLineArgs = commandLineArgs ?? throw new ArgumentNullException(nameof(commandLineArgs));
    }

    public String CommandLine { get; }

    public IReadOnlyList<String> CommandLineArgs { get; }
}

//

public static class Program
{
    public static async Task Main( String[] args )
    {
        await Host.CreateDefaultBuilder( args )
            .UseContentRoot(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
            .ConfigureServices((context, services) =>
            {
                services.AddHostedService<ConsoleHostedService>();
                services.AddSingleton<IEntrypointInfo,SystemEnvironmentEntrypointInfo>()
            })
            .RunConsoleAsync();
    }

对于自动化的单元测试和集成测试,请使用SimpleEntrypointInfo


3
< p > < code > CreateDefaultBuilder 添加了 < code > CommandLineConfigurationProvider 到其配置提供程序中,但通常您不会直接访问它。相反,将一个 < code > IConfiguration 参数添加到您的 < code > ConsoleHostedService 构造函数中,您将自动从多个设置源接收设置,包括命令行参数:

internal sealed class ConsoleHostedService : IHostedService
{
    public ConsoleHostedService(
        IHostApplicationLifetime appLifetime,
        IServiceProvider serviceProvider,
        IConfiguration configuration)
    {
        // Get the value as a string
        string argValueString = configuration["MyFirstArg"]

        // Or if it's an integer
        int argValueInt = configuration.GetValue<int>("MyFirstArg")
    }
}

这需要您的命令行参数遵循如定义的规定格式:

MyFirstArg=12345
/MyFirstArg 12345
--MyFirstArg 12345

然而...

如果您确实需要获取实际的命令行参数,并且不介意依赖于默认生成器的实现,可以进行以下操作:

创建一个自定义的CommandLineConfigurationProvider类并公开其DataArgs属性:

public class ExposedCommandLineConfigurationProvider : CommandLineConfigurationProvider
{
    public ExposedCommandLineConfigurationProvider(IEnumerable<string> args, IDictionary<string, string> switchMappings = null)
        :base(args, switchMappings)
    {
    }

    public new IDictionary<string, string> Data => base.Data;

    public new IEnumerable<string> Args => base.Args;
}

然后在您的主程序中,将其添加到现有配置提供程序列表中:
internal sealed class Program
{
    private static async Task Main(string[] args)
    {
        await Host.CreateDefaultBuilder(args)
            .UseContentRoot(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
            .ConfigureAppConfiguration(config => config
                .Add(new ExposedCommandLineConfigurationSource { Args = args }))
            .ConfigureServices((context, services) => services
                .AddHostedService<SchedulerWorker>())
            .RunConsoleAsync();
    }
}

最后,从提供给您的IConfiguration中挖掘出您的参数提供程序,并传递给ConsoleHostedService。
internal sealed class ConsoleHostedService : IHostedService
{
    public ConsoleHostedService(
        IHostApplicationLifetime appLifetime,
        IServiceProvider serviceProvider,
        IConfiguration configuration)
    {
        if (configuration is ConfigurationRoot configRoot)
        {
            var provider = configRoot.Providers.OfType<ExposedCommandLineConfigurationProvider>().Single();
            var rawArgs = provider.Args;
            var namedArgs = provider.Data;
        }
        else
        {
            // Handle this unlikely situation
        }
    }
}

但是对于一些本来可以很简单完成的事情,这似乎需要花费大量的工作量(并且可能会因为默认构建器实现的任何更改而导致破坏)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接