Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET core Pass Commandline Args to Startup.cs from Program.cs

Tags:

c#

.net-core

I'm trying to configure kestrel so that when it's in it's raw mode it runs on a specific port. However to do so it appears that the launchsettings.json needs to pass command line args to do so since there is no straight up option and it always runs on port 5000 which will obviously conflict if you have an api you need to run and a website.

So I added the CommandLine package to my site and you can indeed use builder.AddCommandLine() in the startup.cs file.

The problem is how to get the args from the program.cs to the Startup.cs or look them up other than a static variable.

Kind of makes the extension method pointless if you can't get at the args.

Any better ways of doing this?

like image 228
James Hancock Avatar asked Dec 22 '16 19:12

James Hancock


3 Answers

A simple solution is to access the command line arguments through the Environment.GetCommandLineArgs method.

You only need to make sure that you remove the first argument, which is the executable name:

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var args = Environment.GetCommandLineArgs().Skip(1).ToArray();
        var builder = new ConfigurationBuilder();
        builder.AddCommandLine(args);

        Configuration = builder.Build();
    }
}
like image 152
Samir Aguiar Avatar answered Oct 22 '22 00:10

Samir Aguiar


UPDATE

I actually found what seems more elegant solution:

  1. Parse command line arguments into IConfigurationRoot in Program (using CommandLineApplication, good article & examples here)
  2. Just pass this IConfigurationRoot to Startup via DI container.

Like so:

public static IWebHost BuildWebHost(string[] args)
{
    var configuration = LoadConfiguration(args);

    // Use Startup as always, register IConfigurationRoot to services
    return new WebHostBuilder()
        .UseKestrel()
        .UseConfiguration(configuration)
        .ConfigureServices(s => s.AddSingleton<IConfigurationRoot>(configuration))
        .UseStartup<Startup>()
        .Build();
}

public class Startup
{
    public Startup(IConfigurationRoot configuration)
    {
        // You get configuration in Startup constructor or wherever you need
    }
}

Example implementation of LoadConfiguration, which parses args and builds IConfigurationRoot (in this example configuration file name can be overridden in command line arguments):

private static IConfigurationRoot LoadConfiguration(string[] args)
{
    var configurationFileName = "configuration.json";

    var cla = new CommandLineApplication(throwOnUnexpectedArg: true);

    var configFileOption = cla.Option("--config <configuration_filename>", "File name of configuration", CommandOptionType.SingleValue);

    cla.OnExecute(() =>
    {
        if (configFileOption.HasValue())
            configurationFileName = configFileOption.Value();

        return 0;
    });

    cla.Execute(args);

    return new ConfigurationBuilder()
        .SetBasePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
        .AddJsonFile(configurationFileName, optional: false, reloadOnChange: true)
        .AddCommandLine(args)
        .Build();
}

OLD ANSWER

You can instantiate Startup class by yourself and pass it as instances to WebHostBuilder. It is somewhat not so elegant, but doable. From here.

public static IWebHost BuildWebHost(string[] args)
{
    // Load configuration and append command line args
    var config = new ConfigurationBuilder()
        .SetBasePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
        .AddJsonFile("configuration.json")
        .AddCommandLine(args)
        .Build();

    // pass config to Startup instance
    var startup = new Startup(config);

    // Instead of using UseStartup<Startup>()
    // Register startup to services
    return new WebHostBuilder()
        .UseKestrel()
        .UseSetting("applicationName", "Your.Assembly.Name")
        .UseConfiguration(config)
        .ConfigureServices(services => services.AddSingleton<IStartup>(startup))
        .Build();
}

Couple of caveats are:

  • by doing so, Startup should implement IStartup which is limited for Configure method in parameters to only Configure(IApplicationBuilder app) instead of full Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime lifetime)
  • For some reason, you need to specify applicationName parameter manually as in my example. I'm testing this on 2.0.0-preview1-final
like image 22
Algirdas Avatar answered Oct 22 '22 00:10

Algirdas


Kestrel can be configured to listen on a different port in several ways. None of these methods need to happen in the Startup class, but rather in the Main method of the Program class. Using the AddCommandLine extension method is one of them. To use that, modify your Program.cs file's Main method to look something like this:

public static void Main(string[] args)
{
    var config = new ConfigurationBuilder()
        .AddCommandLine(args)
        .Build();

    var host = new WebHostBuilder()
                .UseKestrel()
                .UseConfiguration(config)
                .UseStartup<Startup>()
                .Build();
    host.Run();
}

Then, run the application with dotnet run --server.urls http://*:<yourport>, replacing <yourport> with the actual port number that you want it to run on. The * makes it listen on all available IP addresses, if you want to listen on a specific address then you need to specify it there instead of the *.

Another option for changing the port is to use the .UseUrls method to hard-code the port and address. For example:

public static void Main(string[] args)
{
    var host = new WebHostBuilder()
                .UseKestrel()
                .UseUrls("http://*:8080")
                .UseStartup<Startup>()
                .Build();
    host.Run();
}

This example will make your application listen on port 8080 on all available IP addresses.

like image 22
Daniel Grim Avatar answered Oct 21 '22 23:10

Daniel Grim