"AddOptions<T>.Configure()" doesn't work when "services.AddTransient<IConfigureOptions<T>, ConfigureTOptions>()" does?
Asked Answered
D

2

11

I am trying to configure IdentityServer4 Authentication using a strongly typed configuration object.

The Options pattern documentation by Microsoft says:

You can access other services from dependency injection while configuring options in two ways:

  • Pass a configuration delegate to Configure on OptionsBuilder. OptionsBuilder provides overloads of Configure that allow you to use up to five services to configure options:

    services.AddOptions<MyOptions>("optionalName")
        .Configure<Service1, Service2, Service3, Service4, Service5>(
            (o, s, s2, s3, s4, s5) => 
                o.Property = DoSomethingWith(s, s2, s3, s4, s5));
    
  • Create your own type that implements IConfigureOptions or IConfigureNamedOptions and register the type as a service.

We recommend passing a configuration delegate to Configure, since creating a service is more complex. Creating your own type is equivalent to what the framework does for you when you use Configure. Calling Configure registers a transient generic IConfigureNamedOptions, which has a constructor that accepts the generic service types specified.

I've looked at the source code for the OptionsBuilder and it looks like the documentation is accurate in that these two methods are functionally equivalent, but the configuration delegate method isn't working for IdentityServer4 so I'm asking this question as more of a curiosity.

This doesn't work when I add it to Startup.cs:

services
    .AddOptions<IdentityServerAuthenticationOptions>()
    .Configure<IdentityServerConfiguration>((options, configuration)) =>
    {
        options.Audience = configuration.Audience
        // etc.
    });

services
    .AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
    .AddIdentityServerAuthentication();

However, creating a class like below:

public sealed class ConfigureOptions : IConfigureNamedOptions<IdentityServerAuthenticationOptions>
{
    private readonly IdentityServerConfiguration _configuration;

    public ConfigureOptions(IdentityServerConfiguration configuration)
    {
        _configuration = configuration;
    }

    public void Configure(string name, IdentityServerAuthenticationOptions options)
    {
        options.ApiName = _configuration.Audience;
        // etc.
    }

    public void Configure(IdentityServerAuthenticationOptions options)
    {
        options.ApiName = _configuration.Audience;
        // etc.
    }
}

And adding it to Startup.cs like:

services
    .AddTransient<IConfigureOptions<IdentityServerAuthenticationOptions>, ConfigureOptions>();

services
    .AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
    .AddIdentityServerAuthentication();

Makes it work perfectly.

Does anyone know why it would behave like that?

Digestif answered 3/3, 2020 at 6:53 Comment(1)
Did you ever figure this out or just settle on the class implementation? I'm running into the same issue.Cassatt
I
0

I had a similar problem. I was getting null when injected in other service as value of my ModelConfigOptions. My code was

public class ModelConfigOptions
{
    public IEnumerable<ModelConfig> ModelConfigs { get; set; }
}

public class ModelConfig
{
    public int Id { get; set; }
    public int ModelId { get; set; }
    public string Name { get; set; }
    public IModelParameter Parameters { get; set; }
}

public interface IModelLoader
{
    ModelConfigOptions LoadModelConfig(params int[] modelIds);
}

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
       Host.CreateDefaultBuilder(args)
            .ConfigureLogging((hostContext, logging) =>
            {
                logging.ClearProviders();
                logging.AddNLog(hostContext.Configuration, new NLogProviderOptions() { LoggingConfigurationSectionName = "NLog" });
            })                
            .ConfigureServices((hostContext, services) =>
            {
                ...
                services.AddOptions<ModelConfigOptions>().Configure<IModelLoader>((o, s) =>
                {
                    o = s.LoadModelConfig(4, 5);
                });
                ...                    
            });
}

Then I changed IModelLoader and Program in the following way to make it work:

public interface IModelLoader
{
      IEnumerable<ModelConfig> LoadModelConfig(params int[] modelIds);
}

end

services.AddOptions<ModelConfigOptions>().Configure<IModelLoader>((o, s) =>
{
     o.ModelConfigs = s.LoadModelConfig(4, 5);
});
            
Ignaz answered 25/11, 2021 at 14:55 Comment(0)
A
0

This is almost definitely because of named options. AddOptions() without a name parameter applies the delegate to the default name (string.Empty). Your implementation of IConfigureNamedOptions runs for all options names.

Your solutions works fine. Or figure out which name IS4 is using when it creates the options and pass that to AddOptions().

Adios answered 9/9, 2023 at 3:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.