问题
I'm making a .NET Core 2.0 app and I need to configure it. I'm looking at this documentation and it seems that in .NET Core 1.0 you could do:
var appConfig = new AppSettings();
config.GetSection("App").Bind(appConfig);
And in .NET Core 1.1 you could do:
var appConfig = config.GetSection("App").Get<AppSettings>();
But neither Bind nor Get exist in .NET Core 2.0. What's the new way to achieve this?
Thanks,
Josh
回答1:
You can still do both of these. Since you are in a console application, and as such likely not using the ASP.NET Core metapackage, you need to make sure to have the correct dependencies.
In order to bind the configuration to an object, you need the Microsoft.Extensions.Configuration.Binder
package. Then, both solutions should work just fine.
Btw. even if you are in a console application, you could still make use of the dependency injection container that comes with ASP.NET Core. I’ve personally found it very simple to set up, so if you can still modify your application to use it, it might be worth it. The setup would just look like this:
var configuration = new ConfigurationBuilder()
.AddJsonFile("config.json", optional: false)
.Build();
var services = new ServiceCollection();
services.AddOptions();
// add your services here
services.AddTransient<MyService>();
services.AddTransient<Program>();
// configure options
services.Configure<AppSettings>(configuration.GetSection("App"));
// build service provider
var serviceProvider = services.BuildServiceProvider();
// retrieve main application instance and run the program
var program = serviceProvider.GetService<Program>();
program.Run();
Then, all your registered services can take dependencies just like they would do in ASP.NET Core. And to consume your configuration, you could then inject the IOptions<AppSettings>
type like usually.
回答2:
I was still having issues with this until I finally figured it out today.
The code was running without issues, but all the properties were still null, even after binding. I was doing this:
public class AppSettings
{
public string MyProperty
}
and it turns out you have to do this:
public class AppSettings
{
public string MyProperty { get; set; }
}
It only works if your class has Properties, not Fields. This wasn't clear to me.
回答3:
If you want to register the config during Startup
add this to Startup.cs
:
services.Configure<AppSettings>(Configuration.GetSection("App"));
which you can then access by injecting an instance of IOptions<>
:
private readonly AppSettings _appSettings;
public MyClass(IOptions<AppSettings> appSettings) {
_appSettings = appSettings.Value;
}
回答4:
Just for an easier configuration I created a helper class that scans the configuration object for nested configurations, then tries to find a corresponding class in the loaded assemblies and initialize it with the given configuration.
appsettings.json:
{
"MyState": {
"SomeSimpleValue": "Hello World",
"MyTimeSpan": "00:15:00"
}
}
MyStateOptions.cs
// Class has same name as in appsettings.json with Options suffix.
public class MyStateOptions
{
// Properties must be deserializable from string
// or a class with a default constructor that has
// only properties that are deserializable from string.
public string SomeSimpleValue { get; set; }
public DateTime MyTimeSpan { get; set; }
}
Startup.cs
public class Startup
{
public IConfigurationRoot Configuration { get; }
public Startup(IHostingEnvironment env)
{
// Create configuration as you need it...
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile(...)
.AddEnvironmentVariables();
// Save configuration in property to access it later.
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
// Register all your desired services...
services.AddMvc(options => ...);
// Call our helper method
services.RegisterOptions(Configuration);
}
}
HelperClass.cs
public static class IServiceCollectionExtensions
{
public static void RegisterOptions(
this IServiceCollection services,
IConfiguration configuration)
{
// Create all options from the given configuration.
var options = OptionsHelper.CreateOptions(configuration);
foreach (var option in options)
{
// We get back Options<MyOptionsType> : IOptions<MyOptionsType>
var interfaces = option.GetType().GetInterfaces();
foreach (var type in interfaces)
{
// Register options IServiceCollection
services.AddSingleton(type, option);
}
}
}
}
OptionsHelper.cs
public static class OptionsHelper
{
public static IEnumerable<object> CreateOptions(IConfiguration configuration)
{
// Get all sections that are objects:
var sections = configuration.GetChildren()
.Where(section => section.GetChildren().Any());
foreach (var section in sections)
{
// Add "Options" suffix if not done.
var name = section.Key.EndsWith("Options")
? section.Key
: section.Key + "Options";
// Scan AppDomain for a matching type.
var type = FirstOrDefaultMatchingType(name);
if (type != null)
{
// Use ConfigurationBinder to create an instance with the given data.
var settings = section.Get(type);
// Encapsulate instance in "Options<T>"
var options = CreateOptionsFor(settings);
}
}
}
private static Type FirstOrDefaultMatchingType(string typeName)
{
// Find matching type that has a default constructor
return AppDomain.CurrentDomain.GetAssemblies()
.Where(assembly => !assembly.IsDynamic)
.SelectMany(assembly => assembly.GetTypes())
.Where(type => type.Name == typeName)
.Where(type => !type.IsAbstract)
.Where(type => type.GetMatchingConstructor(Type.EmptyTypes) != null)
.FirstOrDefault();
}
private static object CreateOptionsFor(object settings)
{
// Call generic method Options.Create<TOptions>(TOptions options)
var openGeneric = typeof(Options).GetMethod(nameof(Options.Create));
var method = openGeneric.MakeGenericMethod(settings.GetType());
return method.Invoke(null, new[] { settings });
}
}
After doing all that stuff you can have a service within your service collection that demands in its constructor an IOptions<MyStateOptions>
and you'll get it without explicitly configure each and every option you have. Just create a new project with the desired service and options instance. Add the project to your main project and add the desired configuration to your appsettings.json.
ExampleService.cs
public class MyExampleService
{
private readonly MyStateOptions _options;
public MyExampleService(IOptions<MyStateOptions> options)
{
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
}
回答5:
This is how I bind my setting objects and add them as singleton in .Net Core 3.0
public void ConfigureServices(IServiceCollection services)
{
var jwtSettings = new JwtSettings();
Configuration.Bind(jwtSettings);
services.AddSingleton(jwtSettings);
var databaseSettings = new DatabaseSettings();
Configuration.Bind(databaseSettings);
services.AddSingleton(databaseSettings);
services.AddControllersWithViews();
}
My setting objects looks like this:
public class DatabaseSettings
{
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
}
public class JwtSettings
{
public string Secret { get; set; }
public string Lifetime { get; set; }
}
My appsettings.json file looks like below:
{
"DatabaseSettings": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "TestDb"
},
"JwtSettings": {
"Secret": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"Lifetime": "170"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
来源:https://stackoverflow.com/questions/47863078/binding-a-configuration-to-an-object-graph-in-net-core-2-0