简体   繁体   中英

How do you bind a new options object in .NET Core 3.1?

I'm trying to write an extension method in C# using ASP.NET Core 3.1 and option patterns that either binds any existing configuration provided in a provider (such as appsettings.json), or if no existing configuration is found binds a default object to the configuration instead.

My goal is to allow developers to either choose to include their own custom configuration in appsettings.json or to just use the default options.

The problem I'm having is that when I try to bind an object SomeOptions to the configuration object and then later try to retrieve the IOptions<SomeOptions> by calling Configuration.GetSection("SomeOptions").Get<SomeOptions>() it always returns a null object.

For example:

// Binding the creating options object to the configuration
var someOptions = new SomeOptions();
var section = Configuration.GetSection("SomeOptions")
section.Bind(someOptions);

// Retrieve the IOptions<SomeOptions> from the configuration
// This is the problem - it always returns null
var retrievedOptions = section.Get<SomeOptions>();

Why isn't the newly created object being bound to the configuration?

This is the function that I've written:

/// <summary>
/// Adds options to the <see cref="IServiceCollection"/> injected services.
/// If no options can be found within configuration then the default options will be used.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the option bindings to.</param>
/// <param name="configuration">The <see cref="IConfiguration"/> object used to retrieve the configuration sections from.</param>
/// <param name="sectionName">
/// The name of the configuration section to bind the options with.
/// If the configuration section cannot be found or it's blank the default configuration will be used instead.
/// </param>
/// <returns>The originally passed <see cref="IServiceCollection"/> with the added options.</returns>
public static IServiceCollection AddTransientOptionsWithDefault<TOptions>(this IServiceCollection services, IConfiguration configuration, string sectionName)
     where TOptions : class, new()
{
    if (configuration == null)
    {
        throw new ArgumentNullException(nameof(configuration));
    }

    // Retrieve any configured options
    var section = configuration.GetSection(sectionName);

    // If no configuration was found use the default options
    var options = section.Get<TOptions>();
    if (options == null)
    {
        options = new TOptions();

        // This section here seems to be the issue.
        // These are all of the different binding methods I've tried.
        section.Bind(options);
        //configuration.GetSection(sectionName).Bind(options);
        //services.Configure<TOptions>(configureOptions => new TOptions());
        //configuration.Bind(options);
    }

    // This returns null as it can't find the added TOptions
    var newSection = configuration.GetSection(sectionName).Get<TOptions>();

    // Another example - this returns an empty list
    var sectionChildren = section.GetChildren();

    services.Configure<TOptions>(section);

    return services;
}

Example options object:

public class CorsOptions
{
    public string[] Origins { get; set; } = Array.Empty<string>();
}

appsettings.json:

{
    "Cors": {
        "Origins": [ "www.google.com" ]
    }
}

Example using of all of the above in a real world...

Startup.cs snippet:

private IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
    AddCorsWithOrigins(services, Configuration);
}

private IServiceCollection AddCorsWithOrigins(IServiceCollection services, IConfiguration configuration)
{
    // The idea here is that any "Cors" configuration will be used if it exists,
    // otherwise it will just add the default IOptions<CorsOptions> instead
    services.AddTransientOptionsWithDefault<CorsOptions>(configuration, "Cors");

    // Retrieve the options that have been added to the configuration
    var corsOptions = configuration.GetSection("Cors");

    // Do something with the corsOptions object that has been added...
}

Make sure you import the package Microsoft.Extensions.Options.ConfigurationExtensions. Once you do that you can bind it to your custom Dto classes. I tried below:

serviceCollection.Configure(configuration.GetSection("Global")); where AppSettingDto is my custom class and Global is a section in my appsettings json file

public class AppSettingDto
{
    public bool UseSendGrid { get; set; }
    public string EmailFrom { get; set; }
}

"Global": {
"EmailFrom": "sunnylnct007@gmail.com",
"UseSendGrid": true
},

Below is the github link that helped me sort out the issue https://github.com/dotnet/AspNetCore.Docs/issues/18833

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM