Is there a good way to inject IConfiguration into a Option object that inherits from the Option class via DI? I was providing a default value from an Environment.GetVariable but may need to get default values from the appsettings.json.
I am not sure of the best way to do this, but here is how I am doing it. I added a Startup.cs to make it work in a similar pattern to MVC.
public class Startup
{
private readonly IConfigurationRoot _configuration;
public Startup()
{
var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIROMENT");
if (string.IsNullOrWhiteSpace(environmentName))
{
聽 聽 environmentName = "Development";
}
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.{environmentName}.json", true, true)
.AddEnvironmentVariables();
_configuration = builder.Build();
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
if (services == null) throw new ArgumentNullException(nameof(services));
services.AddOptions();
services.Configure<MyOptions>(_configuration.GetSection(nameof(MyOptions)));
services.AddTransient<ISomeOtherObject, SomeOtherObject>();
return services.BuildServiceProvider();
}
}
In you Program.cs:
var serviceProvider = new Startup().ConfigureServices(new ServiceCollection());
If you need something, you need to get it in Program.cs or pass down the IServiceProvider:
serviceProvider.GetService<ISomeOtherObject>();
or
serviceProvider.GetService<IOptions<MyOptions>>();
Also, ensure that the appsettings.json is in the output directory. Update your csproj like so:
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserverNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
There's work underway to provide support for environment variables as defaults for option and argument values. Settings might be a similar case. So far, the approach we're taking avoids DI and inheritance, but does require some discussion about convention-based mapping between option names and environment variable names.
@bartr
Related: #708
@jeredm I'm trying to understand how the code example you provided ties into the Options type in System.CommandLine when you create a new Command and use the AddOption function.
@jonsequitur Thanks for the update! I was imagining that instead of instantiating an instance of Option<T> ourselves, we can just add the typeof(Option) and then some DI can be used to pass in any service we like into an Option, similar to how the binding work is being done with the ICommandHandler example you have.
I'm wondering if this approach introduces a circular dependency. It's most common (arguably) that DI wireup (typed instances with behavior specified via primitives such as connection strings) depends on configuration settings (strings in files, environment variables, command line input). If the parser is needed to gather your app settings but depends on DI to get to those settings, that could be circular. On the other hand, if the command line default values can pull configuration directly from your configuration rather than through DI, then you don't need DI to create your parser.
I'm not suggesting this describes your code or that this is a universal pattern, but it was a consideration in the parser design. At app startup you should be able to set up your parser and start parsing. Setting up the parser mostly requires program invariants: option names, types to bind to. DI is more useful later, when your handler is invoked. Default values are the one exception.
I don't know if that's helpful. I'm just explaining the way I think about it.
The way I have worked around the circular dependency problem is to leverage the default value callback (Argument.SetDefaultValueFactory) on the option's argument. Because this is evaluated on invocation rather than construction of the parser it makes for a nice place to implement "fall-back" values. Back to the point of the issue, this doesn't solve the question of how to get the IConfiguration into the callback.
@pjmagee I think your question may have been a little deeper than I was thinking. What I was thinking is that you needed to provide a default to the Option and that default needed to come from appsettings.json via the DI container. If this is not helpful, let me know and I can clean out my comments to keep the case clear.. Here is what I was thinking in Program.cs:
var serviceProvider = new Startup().ConfigureServices(new ServiceCollection());
var addCommand = new Command("add-item", "Removes a grocery item to the list") {
new Option<int>(
new string[] { "--quantity", "-q" },
() => serviceProvider.Get<IOptions<MyOptions>>().Value.DefaultQuantity,
"The number of items to add to the list"),
new Argument<string>("name")
{
Description = "The name of the grocery item"
}
};