ASP.NET Core의 옵션 패턴

작성자: Kirk LarkinRick Anderson.

옵션 패턴은 클래스를 사용하여 관련 설정 그룹에 대한 강력한 형식의 액세스를 제공합니다. 구성 설정이 시나리오에 따라 별도 클래스로 격리된 경우 앱은 두 가지 중요한 소프트웨어 엔지니어링 원칙을 따릅니다.

옵션은 구성 데이터의 유효성을 검사하는 메커니즘도 제공합니다. 자세한 내용은 옵션 유효성 검사 섹션을 참조하세요.

이 항목에서는 ASP.NET Core의 옵션 패턴 정보를 제공합니다. 콘솔 앱에서 옵션 패턴을 사용하는 방법에 관한 자세한 내용은 .NET의 옵션 패턴을 참조하세요.

예제 코드 살펴보기 및 다운로드 (다운로드 방법)

계층적 구성 바인딩

관련 구성 값을 읽는 기본 방법은 옵션 패턴를 사용하는 것입니다. 예를 들어 다음 구성 값을 읽으려면:

  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

다음 PositionOptions 클래스를 만듭니다.

public class PositionOptions
{
    public const string Position = "Position";

    public string Title { get; set; }
    public string Name { get; set; }
}

옵션 클래스:

  • 매개 변수가 없는 public 생성자를 사용하는 비추상이어야 합니다.
  • 형식의 모든 공용 읽기-쓰기 속성이 바인딩됩니다.
  • 필드가 바인딩되지 않았습니다. 위 코드에서 Position은 바운딩되지 않습니다. Position 속성을 사용하므로 클래스를 구성 공급자에 바인딩할 때 문자열 "Position"을 앱에서 하드 코딩하지 않아도 됩니다.

코드는 다음과 같습니다.

  • ConfigurationBinder.Bind를 호출하여 PositionOptions 클래스를 Position 섹션에 바인딩합니다.
  • Position 구성 데이터를 표시합니다.
public class Test22Model : PageModel
{
    private readonly IConfiguration Configuration;

    public Test22Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var positionOptions = new PositionOptions();
        Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

위 코드에서는 기본적으로 앱을 시작한 후의 JSON 구성 파일의 변경 사항을 읽습니다.

ConfigurationBinder.Get<T>는 지정된 형식을 바인딩하고 반환합니다. ConfigurationBinder.Get<T>ConfigurationBinder.Bind를 사용하는 것보다 편리할 수 있습니다. 다음 코드에서는 ConfigurationBinder.Get<T>PositionOptions 클래스를 함께 사용하는 방법을 보여 줍니다.

public class Test21Model : PageModel
{
    private readonly IConfiguration Configuration;
    public PositionOptions positionOptions { get; private set; }

    public Test21Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {            
        positionOptions = Configuration.GetSection(PositionOptions.Position)
                                                     .Get<PositionOptions>();

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

위 코드에서는 기본적으로 앱을 시작한 후의 JSON 구성 파일의 변경 사항을 읽습니다.

옵션 패턴 을 사용하는 경우 대체 방법은 Position 섹션을 바인딩하고 종속성 주입 서비스 컨테이너에 추가하는 것입니다. 다음 코드에서 PositionOptions는 <xref:Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure_>를 통해 서비스 컨테이너에 추가되고 구성에 바인딩됩니다.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(Configuration.GetSection(
                                        PositionOptions.Position));
    services.AddRazorPages();
}

위의 코드를 사용하여 다음 코드는 위치 옵션을 읽습니다.

public class Test2Model : PageModel
{
    private readonly PositionOptions _options;

    public Test2Model(IOptions<PositionOptions> options)
    {
        _options = options.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Title: {_options.Title} \n" +
                       $"Name: {_options.Name}");
    }
}

위 코드에서는 앱을 시작한 후의 JSON 구성 파일의 변경 사항을 읽지 않습니다. 앱을 시작한 후의 변경 사항을 읽으려면 IOptionsSnapshot을 사용합니다.

옵션 인터페이스

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

사후 구성 시나리오에서는 모든 IConfigureOptions<TOptions> 구성이 발생한 후에 옵션을 설정하거나 변경할 수 있습니다.

IOptionsFactory<TOptions>는 새로운 옵션 인스턴스를 만듭니다. 단일 Create 메서드가 있습니다. 기본 구현에서는 등록된 모든 IConfigureOptions<TOptions>IPostConfigureOptions<TOptions>를 사용하며 먼저 구성을 모두 실행한 다음, 사후 구성을 수행합니다. IConfigureNamedOptions<TOptions>IConfigureOptions<TOptions>를 구별하며 적절한 인터페이스만 호출합니다.

IOptionsMonitorCache<TOptions>IOptionsMonitor<TOptions>에서 TOptions 인스턴스를 캐시하는 데 사용됩니다. IOptionsMonitorCache<TOptions>는 모니터의 옵션 인스턴스를 무효화하므로 값이 다시 계산됩니다(TryRemove). TryAdd를 사용하여 값을 수동으로 도입할 수 있습니다. 모든 명명된 인스턴스를 필요에 따라 다시 만들어야 하는 경우 Clear 메서드가 사용됩니다.

IOptionsSnapshot을 사용하여 업데이트된 데이터 읽기

IOptionsSnapshot<TOptions>을 사용하면, 옵션은 엑세스될 때 요청 당 한 번씩 계산되고 요청의 수명 동안 캐시됩니다. 업데이트된 구성 값 읽기를 지원하는 구성 공급자를 사용하는 경우 앱을 시작한 후 구성 변경 내용을 읽습니다.

IOptionsMonitorIOptionsSnapshot의 차이점은 다음과 같습니다.

  • IOptionsMonitor는 언제든지 현재 옵션 값을 검색하는 싱글톤 서비스로, 싱글톤 종속성에서 특히 유용합니다.
  • IOptionsSnapshot범위가 지정된 서비스이며 IOptionsSnapshot<T> 개체가 생성될 때 옵션의 스냅샷을 제공합니다. 옵션 스냅숏은 임시 및 범위가 지정된 종속성과 함께 사용하도록 설계되었습니다.

다음 코드에서는 IOptionsSnapshot<TOptions> 메서드를 사용합니다.

public class TestSnapModel : PageModel
{
    private readonly MyOptions _snapshotOptions;

    public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
    {
        _snapshotOptions = snapshotOptionsAccessor.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_snapshotOptions.Option1} \n" +
                       $"Option2: {_snapshotOptions.Option2}");
    }
}

다음 코드에서는 MyOptions가 바인딩되는 구성 인스턴스를 등록합니다.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));

    services.AddRazorPages();
}

위 코드에서는 앱을 시작한 후 JSON 구성 파일 변경 사항을 읽습니다.

IOptionsMonitor

다음 코드는 MyOptions가 바인딩되는 구성 인스턴스를 등록합니다.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));

    services.AddRazorPages();
}

다음 예제에서는 IOptionsMonitor<TOptions>를 사용합니다.

public class TestMonitorModel : PageModel
{
    private readonly IOptionsMonitor<MyOptions> _optionsDelegate;

    public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
    {
        _optionsDelegate = optionsDelegate;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_optionsDelegate.CurrentValue.Option1} \n" +
                       $"Option2: {_optionsDelegate.CurrentValue.Option2}");
    }
}

위 코드에서는 기본적으로 앱을 시작한 후의 JSON 구성 파일의 변경 사항을 읽습니다.

명명된 옵션은 IConfigureNamedOptions 사용을 지원

명명된 옵션:

  • 여러 구성 섹션이 동일한 속성에 바인딩되는 경우에 유용합니다.
  • 대/소문자를 구분합니다.

다음 appsettings.json 파일을 살펴보세요.

{
  "TopItem": {
    "Month": {
      "Name": "Green Widget",
      "Model": "GW46"
    },
    "Year": {
      "Name": "Orange Gadget",
      "Model": "OG35"
    }
  }
}

TopItem:MonthTopItem:Year를 바인딩하는 두 개의 클래스를 만드는 대신 각 섹션에 다음 클래스가 사용됩니다.

public class TopItemSettings
{
    public const string Month = "Month";
    public const string Year = "Year";

    public string Name { get; set; }
    public string Model { get; set; }
}

다음 코드는 명명된 옵션을 구성합니다.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<TopItemSettings>(TopItemSettings.Month,
                                       Configuration.GetSection("TopItem:Month"));
    services.Configure<TopItemSettings>(TopItemSettings.Year,
                                        Configuration.GetSection("TopItem:Year"));

    services.AddRazorPages();
}

다음 코드는 명명된 옵션을 표시합니다.

public class TestNOModel : PageModel
{
    private readonly TopItemSettings _monthTopItem;
    private readonly TopItemSettings _yearTopItem;

    public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
    {
        _monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
        _yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
    }

    public ContentResult OnGet()
    {
        return Content($"Month:Name {_monthTopItem.Name} \n" +
                       $"Month:Model {_monthTopItem.Model} \n\n" +
                       $"Year:Name {_yearTopItem.Name} \n" +
                       $"Year:Model {_yearTopItem.Model} \n"   );
    }
}

모든 옵션은 명명된 인스턴스입니다. IConfigureOptions<TOptions> 인스턴스는 string.EmptyOptions.DefaultName 인스턴스를 대상으로 지정한 것처럼 처리됩니다. IConfigureNamedOptions<TOptions>IConfigureOptions<TOptions>도 구현합니다. IOptionsFactory<TOptions>의 기본 구현에는 각 옵션을 적절하게 사용하기 위한 논리가 있습니다. null 명명된 옵션은 특정 명명된 인스턴스 대신 모든 명명된 인스턴스를 대상으로 지정하는 데 사용됩니다. ConfigureAllPostConfigureAll에서 이 규칙을 사용합니다.

OptionsBuilder API

OptionsBuilder<TOptions>TOptions 인스턴스를 구성하는 데 사용됩니다. OptionsBuilderAddOptions<TOptions>(string optionsName) 호출에 대한 단일 매개 변수이므로 모든 후속 호출에 나타나는 대신 명명된 옵션 생성을 간소화합니다. 옵션 유효성 검사 및 서비스 종속성을 허용하는 ConfigureOptions 오버로드는 OptionsBuilder를 통해서만 사용할 수 있습니다.

OptionsBuilder옵션 유효성 검사 섹션에서 사용됩니다.

사용자 지정 리포지토리를 추가하는 방법에 대한 내용은 AddOptions를 사용하여 사용자 지정 리포지토리 구성을 참조하세요.

DI 서비스를 사용하여 옵션 구성

다음 두 가지 방법으로 옵션을 구성하는 동안 종속성 주입에서 서비스에 액세스할 수 있습니다.

  • 구성 대리자를 OptionsBuilder<TOptions>Configure에 전달합니다. OptionsBuilder<TOptions>는 최대 5개의 서비스를 사용하여 옵션을 구성할 수 있는 Configure의 오버로드를 제공합니다.

    services.AddOptions<MyOptions>("optionalName")
        .Configure<Service1, Service2, Service3, Service4, Service5>(
            (o, s, s2, s3, s4, s5) => 
                o.Property = DoSomethingWith(s, s2, s3, s4, s5));
    
  • IConfigureOptions<TOptions> 또는 IConfigureNamedOptions<TOptions>를 구현하는 형식을 만들고 해당 형식을 서비스로 등록합니다.

서비스를 만드는 것이 더 복잡하기 때문에 구성 대리자를 Configure에 전달하는 것이 좋습니다. 형식을 만드는 작업은 Configure를 호출할 때 프레임워크에서 수행하는 작업에 해당합니다. Configure을 호출하면 지정된 일반 서비스 유형을 허용하는 생성자가 있는 일시적인 일반 IConfigureNamedOptions<TOptions>가 등록됩니다.

옵션 유효성 검사

옵션 유효성 검사를 통해 옵션 값의 유효성을 검사할 수 있습니다.

다음 appsettings.json 파일을 살펴보세요.

{
  "MyConfig": {
    "Key1": "My Key One",
    "Key2": 10,
    "Key3": 32
  }
}

다음 클래스는 "MyConfig" 구성 섹션에 바인딩되고 몇 가지 DataAnnotations 규칙을 적용합니다.

public class MyConfigOptions
{
    public const string MyConfig = "MyConfig";

    [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
    public string Key1 { get; set; }
    [Range(0, 1000,
        ErrorMessage = "Value for {0} must be between {1} and {2}.")]
    public int Key2 { get; set; }
    public int Key3 { get; set; }
}

코드는 다음과 같습니다.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOptions<MyConfigOptions>()
            .Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations();

        services.AddControllersWithViews();
    }

ValidateDataAnnotations 확장 메서드는 Microsoft.Extensions.Options.DataAnnotations NuGet 패키지에서 정의됩니다. Microsoft.NET.Sdk.Web SDK를 사용하는 웹앱의 경우 이 패키지는 공유 프레임워크에서 암시적으로 참조됩니다.

다음 코드는 구성 값 또는 유효성 검사 오류를 표시합니다.

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IOptions<MyConfigOptions> _config;

    public HomeController(IOptions<MyConfigOptions> config,
                          ILogger<HomeController> logger)
    {
        _config = config;
        _logger = logger;

        try
        {
            var configValue = _config.Value;
           
        }
        catch (OptionsValidationException ex)
        {
            foreach (var failure in ex.Failures)
            {
                _logger.LogError(failure);
            }
        }
    }

    public ContentResult Index()
    {
        string msg;
        try
        {
             msg = $"Key1: {_config.Value.Key1} \n" +
                   $"Key2: {_config.Value.Key2} \n" +
                   $"Key3: {_config.Value.Key3}";
        }
        catch (OptionsValidationException optValEx)
        {
            return Content(optValEx.Message);
        }
        return Content(msg);
    }

다음 코드는 대리자를 사용하여 보다 복잡한 유효성 검사 규칙을 적용합니다.

public void ConfigureServices(IServiceCollection services)
{
    services.AddOptions<MyConfigOptions>()
        .Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
        .ValidateDataAnnotations()
        .Validate(config =>
        {
            if (config.Key2 != 0)
            {
                return config.Key3 > config.Key2;
            }

            return true;
        }, "Key3 must be > than Key2.");   // Failure message.

    services.AddControllersWithViews();
}

복잡한 유효성 검사를 위한 IValidateOptions

다음 클래스는 IValidateOptions<TOptions>를 구현합니다.

public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
    public MyConfigOptions _config { get; private set; }

    public  MyConfigValidation(IConfiguration config)
    {
        _config = config.GetSection(MyConfigOptions.MyConfig)
            .Get<MyConfigOptions>();
    }

    public ValidateOptionsResult Validate(string name, MyConfigOptions options)
    {
        string vor=null;
        var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
        var match = rx.Match(options.Key1);

        if (string.IsNullOrEmpty(match.Value))
        {
            vor = $"{options.Key1} doesn't match RegEx \n";
        }

        if ( options.Key2 < 0 || options.Key2 > 1000)
        {
            vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
        }

        if (_config.Key2 != default)
        {
            if(_config.Key3 <= _config.Key2)
            {
                vor +=  "Key3 must be > than Key2.";
            }
        }

        if (vor != null)
        {
            return ValidateOptionsResult.Fail(vor);
        }

        return ValidateOptionsResult.Success;
    }
}

IValidateOptions를 사용하면 유효성 검사 코드를 StartUp에서 클래스로 이동할 수 있습니다.

위의 코드에서는 다음 코드를 사용하여 Startup.ConfigureServices에서 유효성 검사를 사용하도록 설정합니다.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyConfigOptions>(Configuration.GetSection(
                                        MyConfigOptions.MyConfig));
    services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
                              <MyConfigOptions>, MyConfigValidation>());
    services.AddControllersWithViews();
}

옵션 사후 구성

IPostConfigureOptions<TOptions>를 사용하여 사후 구성을 설정합니다. 사후 구성은 모든 IConfigureOptions<TOptions> 구성이 수행된 후에 실행됩니다.

services.PostConfigure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

PostConfigure를 사용하여 명명된 옵션을 사후 구성할 수 있습니다.

services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

모든 구성 인스턴스를 사후 구성하려면 PostConfigureAll을 사용합니다.

services.PostConfigureAll<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

시작하는 동안 옵션 액세스

Configure 메서드가 실행되기 전에 서비스가 만들어지므로 Startup.Configure에서 IOptions<TOptions>IOptionsMonitor<TOptions>를 사용할 수 있습니다.

public void Configure(IApplicationBuilder app, 
    IOptionsMonitor<MyOptions> optionsAccessor)
{
    var option1 = optionsAccessor.CurrentValue.Option1;
}

Startup.ConfigureServices에서는 IOptions<TOptions> 또는 IOptionsMonitor<TOptions>를 사용하지 마세요. 서비스 등록의 순서 지정으로 인해 일관성 없는 옵션 상태가 존재할 수 있습니다.

Options.ConfigurationExtensions NuGet 패키지

Microsoft.Extensions.Options.ConfigurationExtensions 패키지는 ASP.NET Core 앱에서 암시적으로 참조됩니다.

옵션 패턴은 클래스를 사용하여 관련 설정 그룹을 나타냅니다. 구성 설정이 시나리오에 따라 별도 클래스로 격리된 경우 앱은 두 가지 중요한 소프트웨어 엔지니어링 원칙을 따릅니다.

옵션은 구성 데이터의 유효성을 검사하는 메커니즘도 제공합니다. 자세한 내용은 옵션 유효성 검사 섹션을 참조하세요.

예제 코드 살펴보기 및 다운로드 (다운로드 방법)

사전 요구 사항

Microsoft.AspNetCore.App 메타패키지를 참조하거나 Microsoft.Extensions.Options.ConfigurationExtensions 패키지에 대한 패키지 참조를 추가합니다.

옵션 인터페이스

IOptionsMonitor<TOptions>는 옵션을 검색하고 TOptions 인스턴스에 대한 옵션 알림을 관리하는 데 사용됩니다. IOptionsMonitor<TOptions>은 다음 시나리오를 지원합니다.

사후 구성 시나리오에서는 모든 IConfigureOptions<TOptions> 구성이 수행된 후 옵션을 설정하거나 변경할 수 있습니다.

IOptionsFactory<TOptions>는 새로운 옵션 인스턴스를 만듭니다. 단일 Create 메서드가 있습니다. 기본 구현에서는 등록된 모든 IConfigureOptions<TOptions>IPostConfigureOptions<TOptions>를 사용하며 먼저 구성을 모두 실행한 다음, 사후 구성을 수행합니다. IConfigureNamedOptions<TOptions>IConfigureOptions<TOptions>를 구별하며 적절한 인터페이스만 호출합니다.

IOptionsMonitorCache<TOptions>IOptionsMonitor<TOptions>에서 TOptions 인스턴스를 캐시하는 데 사용됩니다. IOptionsMonitorCache<TOptions>는 모니터의 옵션 인스턴스를 무효화하므로 값이 다시 계산됩니다(TryRemove). TryAdd를 사용하여 값을 수동으로 도입할 수 있습니다. 모든 명명된 인스턴스를 필요에 따라 다시 만들어야 하는 경우 Clear 메서드가 사용됩니다.

IOptionsSnapshot<TOptions>은 모든 요청에서 옵션을 다시 계산해야 하는 시나리오에서 유용합니다. 자세한 내용은 IOptionsSnapshot을 사용하여 구성 데이터 다시 로드를 참조하세요.

IOptions<TOptions>를 사용하여 옵션을 지원할 수 있습니다. 그러나 IOptions<TOptions>는 앞에서 설명한 IOptionsMonitor<TOptions>의 시나리오를 지원하지 않습니다. IOptions<TOptions> 인터페이스를 이미 사용하고 있으며 IOptionsMonitor<TOptions>에서 제공하는 시나리오가 필요하지 않은 기존 프레임워크 및 라이브러리에서는 IOptions<TOptions>를 계속 사용할 수 있습니다.

일반 옵션 구성

일반 옵션 구성은 샘플 앱에 예제 1로 설명되어 있습니다.

옵션 클래스는 매개 변수가 없는 public 생성자를 사용하는 비추상이어야 합니다. 다음 클래스 MyOptions에는 Option1Option2의 두 가지 속성이 있습니다. 기본 값 설정은 선택 사항이지만, 다음 예제의 클래스 생성자는 Option1의 기본값을 설정합니다. Option2에는 직접 속성을 초기화하여 설정된 기본값이 있습니다(Models/MyOptions.cs).

public class MyOptions
{
    public MyOptions()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

MyOptions 클래스는 Configure를 통해 서비스 컨테이너에 추가되고 구성에 바인딩됩니다.

// Example #1: General configuration
// Register the Configuration instance which MyOptions binds against.
services.Configure<MyOptions>(Configuration);

다음 페이지 모델은 IOptionsMonitor<TOptions>를 통해 생성자 종속성 주입을 사용하여 설정에 액세스합니다(Pages/Index.cshtml.cs).

private readonly MyOptions _options;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #1: Simple options
var option1 = _options.Option1;
var option2 = _options.Option2;
SimpleOptions = $"option1 = {option1}, option2 = {option2}";

샘플의 appsettings.json 파일은 option1option2의 값을 지정합니다.

{
  "option1": "value1_from_json",
  "option2": -1,
  "subsection": {
    "suboption1": "subvalue1_from_json",
    "suboption2": 200
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

앱을 실행할 때 페이지 모델의 OnGet 메서드는 옵션 클래스 값을 표시하는 문자열을 반환합니다.

option1 = value1_from_json, option2 = -1

참고

사용자 지정 ConfigurationBuilder를 사용하여 설정 파일에서 옵션 구성을 로드하는 경우 기본 경로가 올바르게 설정되었는지 확인합니다.

var configBuilder = new ConfigurationBuilder()
   .SetBasePath(Directory.GetCurrentDirectory())
   .AddJsonFile("appsettings.json", optional: true);
var config = configBuilder.Build();

services.Configure<MyOptions>(config);

CreateDefaultBuilder를 통해 설정 파일에서 옵션 구성을 로드하는 경우에는 명시적으로 기본 경로를 설정하지 않아도 됩니다.

대리자로 간단한 옵션 구성

대리자를 사용한 간단한 옵션 구성은 샘플 앱에 예제 2로 설명되어 있습니다.

대리자를 사용하여 옵션 값을 설정합니다. 샘플 앱에서는 MyOptionsWithDelegateConfig 클래스(Models/MyOptionsWithDelegateConfig.cs)를 사용합니다.

public class MyOptionsWithDelegateConfig
{
    public MyOptionsWithDelegateConfig()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

다음 코드에서 두 번째 IConfigureOptions<TOptions> 서비스가 서비스 컨테이너에 추가됩니다. MyOptionsWithDelegateConfig를 통해 바인딩을 구성하는 데 대리자를 사용합니다.

// Example #2: Options bound and configured by a delegate
services.Configure<MyOptionsWithDelegateConfig>(myOptions =>
{
    myOptions.Option1 = "value1_configured_by_delegate";
    myOptions.Option2 = 500;
});

Index.cshtml.cs:

private readonly MyOptionsWithDelegateConfig _optionsWithDelegateConfig;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #2: Options configured by delegate
var delegate_config_option1 = _optionsWithDelegateConfig.Option1;
var delegate_config_option2 = _optionsWithDelegateConfig.Option2;
SimpleOptionsWithDelegateConfig = 
    $"delegate_option1 = {delegate_config_option1}, " +
    $"delegate_option2 = {delegate_config_option2}";

여러 구성 공급자를 추가할 수 있습니다. 구성 공급자는 NuGet 패키지에서 사용할 수 있으며 등록된 순서대로 적용됩니다. 자세한 내용은 ASP.NET Core의 구성를 참조하세요.

Configure를 호출할 때마다 IConfigureOptions<TOptions> 서비스가 서비스 컨테이너에 추가됩니다. 위의 예제에서는 Option1Option2의 값은 모두 appsettings.json 에서 지정되지만 Option1Option2의 값은 구성된 대리자에 의해 재정의됩니다.

둘 이상의 구성 서비스를 활성화한 경우 마지막 지정된 구성 소스가 승리 하고 구성 값을 설정합니다. 앱을 실행할 때 페이지 모델의 OnGet 메서드는 옵션 클래스 값을 표시하는 문자열을 반환합니다.

delegate_option1 = value1_configured_by_delegate, delegate_option2 = 500

하위 옵션 구성

하위 옵션 구성은 샘플 앱에 예제 3으로 설명되어 있습니다.

앱은 앱의 특정 시나리오 그룹(클래스)에 적합한 옵션 클래스를 만들어야 합니다. 구성 값을 필요로 하는 앱의 부분은 사용하는 구성 값에만 액세스할 수 있어야 합니다.

옵션을 구성에 바인딩하는 경우 옵션 형식의 각 속성은 property[:sub-property:] 양식의 구성 키에 바인딩됩니다. 예를 들어 MyOptions.Option1 속성은 키 Option1에 바인딩되어 appsettings.jsonoption1 속성에서 읽힙니다.

다음 코드에서 세 번째 IConfigureOptions<TOptions> 서비스가 서비스 컨테이너에 추가됩니다. 이 서비스는 MySubOptionsappsettings.json 파일의 섹션 subsection에 바인딩합니다.

// Example #3: Suboptions
// Bind options using a sub-section of the appsettings.json file.
services.Configure<MySubOptions>(Configuration.GetSection("subsection"));

GetSection 메서드를 사용하려면 Microsoft.Extensions.Configuration 네임스페이스가 필요합니다.

샘플의 appsettings.json 파일은 suboption1suboption2에 대한 키로 subsection 멤버를 정의합니다.

{
  "option1": "value1_from_json",
  "option2": -1,
  "subsection": {
    "suboption1": "subvalue1_from_json",
    "suboption2": 200
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

MySubOptions 클래스는 SubOption1SubOption2 속성을 정의하여 옵션 값을 유지합니다(Models/MySubOptions.cs).

public class MySubOptions
{
    public MySubOptions()
    {
        // Set default values.
        SubOption1 = "value1_from_ctor";
        SubOption2 = 5;
    }
    
    public string SubOption1 { get; set; }
    public int SubOption2 { get; set; }
}

페이지 모델의 OnGet 메서드는 옵션 값이 포함된 문자열을 반환합니다(Pages/Index.cshtml.cs).

private readonly MySubOptions _subOptions;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #3: Suboptions
var subOption1 = _subOptions.SubOption1;
var subOption2 = _subOptions.SubOption2;
SubOptions = $"subOption1 = {subOption1}, subOption2 = {subOption2}";

앱을 실행할 때 OnGet 메서드는 하위 옵션 클래스 값을 표시하는 문자열을 반환합니다.

subOption1 = subvalue1_from_json, subOption2 = 200

옵션 삽입

옵션 삽입은 샘플 앱에 예제 4로 설명되어 있습니다.

다음에 IOptionsMonitor<TOptions>를 삽입합니다.

  • @inject Razor 지시문이 포함된 Razor 페이지 또는 MVC 뷰.
  • 페이지 또는 뷰 모델입니다.

샘플 앱의 다음 예제는 페이지 모델(Pages/Index.cshtml.cs)에 IOptionsMonitor<TOptions>를 삽입합니다.

private readonly MyOptions _options;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #4: Bind options directly to the page
MyOptions = _options;

샘플 앱은 @inject 지시문을 사용하여 IOptionsMonitor<MyOptions>를 주입하는 방법을 보여 줍니다.

@page
@model IndexModel
@using Microsoft.Extensions.Options
@inject IOptionsMonitor<MyOptions> OptionsAccessor
@{
    ViewData["Title"] = "Options Sample";
}

<h1>@ViewData["Title"]</h1>

앱을 실행하면 옵션 값이 렌더링된 페이지에 표시됩니다.

옵션 값 Option1: value1_from_json 및 Option2: -1은 모델에서 로드되며 보기에 주입됩니다.

IOptionsSnapshot을 사용하여 구성 데이터 다시 로드

IOptionsSnapshot<TOptions>을 사용하여 구성 데이터를 다시 로드하는 방법은 샘플 앱에 예제 5로 설명되어 있습니다.

IOptionsSnapshot<TOptions>을 사용하면, 옵션은 엑세스될 때 요청 당 한 번씩 계산되고 요청의 수명 동안 캐시됩니다.

IOptionsMonitorIOptionsSnapshot의 차이점은 다음과 같습니다.

  • IOptionsMonitor는 언제든지 현재 옵션 값을 검색하는 싱글톤 서비스로, 싱글톤 종속성에서 특히 유용합니다.
  • IOptionsSnapshot범위가 지정된 서비스이며 IOptionsSnapshot<T> 개체가 생성될 때 옵션의 스냅샷을 제공합니다. 옵션 스냅숏은 임시 및 범위가 지정된 종속성과 함께 사용하도록 설계되었습니다.

다음 예제에는 appsettings.json 이 변경된 후 새 IOptionsSnapshot<TOptions>을 만드는 방법이 설명되어 있습니다(Pages/Index.cshtml.cs). 서버에 대한 여러 요청은 파일이 변경되고 구성이 다시 로드될 때까지 appsettings.json 파일에서 제공하는 상수 값을 반환합니다.

private readonly MyOptions _snapshotOptions;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #5: Snapshot options
var snapshotOption1 = _snapshotOptions.Option1;
var snapshotOption2 = _snapshotOptions.Option2;
SnapshotOptions = 
    $"snapshot option1 = {snapshotOption1}, " +
    $"snapshot option2 = {snapshotOption2}";

다음 이미지는 appsettings.json 파일에서 로드된 초기 option1option2 값을 보여 줍니다.

snapshot option1 = value1_from_json, snapshot option2 = -1

appsettings.json 파일의 값을 value1_from_json UPDATED200으로 변경합니다. appsettings.json 파일을 저장합니다. 옵션 값이 변경되었음을 확인하려면 브라우저를 새로 고칩니다.

snapshot option1 = value1_from_json UPDATED, snapshot option2 = 200

IConfigureNamedOptions로 명명된 옵션 지원

IConfigureNamedOptions<TOptions>로 명명된 옵션 지원은 샘플 앱에 예제 6으로 설명되어 있습니다.

앱은 명명된 옵션 지원을 통해 명명된 옵션 구성들을 구분할 수 있습니다. 샘플 앱에서 명명된 옵션은 ConfigureNamedOptions<TOptions>.Configure 확장 메서드를 호출하는 OptionsServiceCollectionExtensions.Configure를 사용하여 선언됩니다. 명명된 옵션은 대/소문자를 구분합니다.

// Example #6: Named options (named_options_1)
// Register the ConfigurationBuilder instance which MyOptions binds against.
// Specify that the options loaded from configuration are named
// "named_options_1".
services.Configure<MyOptions>("named_options_1", Configuration);

// Example #6: Named options (named_options_2)
// Specify that the options loaded from the MyOptions class are named
// "named_options_2".
// Use a delegate to configure option values.
services.Configure<MyOptions>("named_options_2", myOptions =>
{
    myOptions.Option1 = "named_options_2_value1_from_action";
});

샘플 앱은 Get을 사용하여 명명된 옵션에 액세스합니다(Pages/Index.cshtml.cs).

private readonly MyOptions _named_options_1;
private readonly MyOptions _named_options_2;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #6: Named options
var named_options_1 = 
    $"named_options_1: option1 = {_named_options_1.Option1}, " +
    $"option2 = {_named_options_1.Option2}";
var named_options_2 = 
    $"named_options_2: option1 = {_named_options_2.Option1}, " +
    $"option2 = {_named_options_2.Option2}";
NamedOptions = $"{named_options_1} {named_options_2}";

샘플 앱을 실행하면 명명된 옵션이 반환됩니다.

named_options_1: option1 = value1_from_json, option2 = -1
named_options_2: option1 = named_options_2_value1_from_action, option2 = 5

named_options_1 값은 appsettings.json 파일에서 로드되는 구성에서 제공됩니다. named_options_2 값은 다음에서 제공됩니다.

  • Option1에 대한 ConfigureServicesnamed_options_2 대리자.
  • MyOptions 클래스에서 제공되는 Option2에 대한 기본값.

ConfigureAll 메서드를 사용하여 모든 옵션 구성

ConfigureAll 메서드를 사용하여 모든 옵션 인스턴스를 구성합니다. 다음 코드는 모든 구성 인스턴스에 대해 Option1을 공통 값으로 구성합니다. 다음 코드를 Startup.ConfigureServices 메서드에 직접 추가합니다.

services.ConfigureAll<MyOptions>(myOptions => 
{
    myOptions.Option1 = "ConfigureAll replacement value";
});

코드를 추가한 후 샘플 앱을 실행하면 다음과 같은 결과가 생성됩니다.

named_options_1: option1 = ConfigureAll replacement value, option2 = -1
named_options_2: option1 = ConfigureAll replacement value, option2 = 5

참고

모든 옵션은 명명된 인스턴스입니다. 기존 IConfigureOptions<TOptions> 인스턴스는 string.EmptyOptions.DefaultName 인스턴스를 대상으로 지정한 것처럼 처리됩니다. IConfigureNamedOptions<TOptions>IConfigureOptions<TOptions>도 구현합니다. IOptionsFactory<TOptions>의 기본 구현에는 각 옵션을 적절하게 사용하기 위한 논리가 있습니다. null 명명된 옵션은 특정 명명된 인스턴스 대신 모든 명명된 인스턴스를 대상으로 지정하는 데 사용됩니다(ConfigureAllPostConfigureAll에서 이 규칙을 사용함).

OptionsBuilder API

OptionsBuilder<TOptions>TOptions 인스턴스를 구성하는 데 사용됩니다. OptionsBuilderAddOptions<TOptions>(string optionsName) 호출에 대한 단일 매개 변수이므로 모든 후속 호출에 나타나는 대신 명명된 옵션 생성을 간소화합니다. 옵션 유효성 검사 및 서비스 종속성을 허용하는 ConfigureOptions 오버로드는 OptionsBuilder를 통해서만 사용할 수 있습니다.

// Options.DefaultName = "" is used.
services.AddOptions<MyOptions>().Configure(o => o.Property = "default");

services.AddOptions<MyOptions>("optionalName")
    .Configure(o => o.Property = "named");

DI 서비스를 사용하여 옵션 구성

다음과 같은 두 가지 방법으로 옵션을 구성하는 동안 종속성 주입을 통해 다른 서비스에 액세스할 수 있습니다.

서비스를 만드는 것이 더 복잡하기 때문에 구성 대리자를 Configure에 전달하는 것이 좋습니다. 고유의 형식을 만드는 것은 Configure를 사용할 때 프레임워크가 수행하는 것과 동일합니다. Configure을 호출하면 지정된 일반 서비스 유형을 허용하는 생성자가 있는 일시적인 일반 IConfigureNamedOptions<TOptions>가 등록됩니다.

옵션 유효성 검사

옵션 유효성 검사를 사용하면 옵션이 구성될 때 옵션의 유효성을 검사할 수 있습니다. 옵션이 유효하면 true를 반환하고 옵션이 유효하지 않으면 false를 반환하는 유효성 검사 메서드로 Validate를 호출합니다.

// Registration
services.AddOptions<MyOptions>("optionalOptionsName")
    .Configure(o => { }) // Configure the options
    .Validate(o => YourValidationShouldReturnTrueIfValid(o), 
        "custom error");

// Consumption
var monitor = services.BuildServiceProvider()
    .GetService<IOptionsMonitor<MyOptions>>();

try
{
    var options = monitor.Get("optionalOptionsName");
}
catch (OptionsValidationException e) 
{
   // e.OptionsName returns "optionalOptionsName"
   // e.OptionsType returns typeof(MyOptions)
   // e.Failures returns a list of errors, which would contain 
   //     "custom error"
}

위의 예제에서는 명명된 옵션 인스턴스를 optionalOptionsName으로 설정합니다. 기본 옵션 인스턴스는 Options.DefaultName입니다.

유효성 검사는 옵션 인스턴스가 만들어지면 실행됩니다. 옵션 인스턴스는 처음 액세스되면 유효성 검사를 통과하도록 보장됩니다.

중요

옵션 유효성 검사는 옵션 인스턴스가 생성된 후 옵션 수정에 대해 보호하지 않습니다. 예를 들어 옵션이 처음 액세스될 때 요청마다 한 번씩 IOptionsSnapshot 옵션이 생성되고 유효성이 검사됩니다. IOptionsSnapshot 옵션은 동일한 요청에 대한 다음 액세스 시도에서 다시 검사되지 않습니다.

Validate 메서드는 Func<TOptions, bool>을 허용합니다. 유효성 검사를 완전히 사용자 지정하려면 다음을 허용하는 IValidateOptions<TOptions>를 구현합니다.

  • 여러 옵션 형식의 유효성 검사: class ValidateTwo : IValidateOptions<Option1>, IValidationOptions<Option2>
  • 다른 옵션 형식에 의존하는 유효성 검사: public DependsOnAnotherOptionValidator(IOptionsMonitor<AnotherOption> options)

IValidateOptions는 다음의 유효성을 검사합니다.

  • 특정 명명된 옵션 인스턴스.
  • namenull인 경우 모든 옵션.

인터페이스의 구현에서 ValidateOptionsResult를 반환합니다.

public interface IValidateOptions<TOptions> where TOptions : class
{
    ValidateOptionsResult Validate(string name, TOptions options);
}

데이터 주석 기반 유효성 검사는 OptionsBuilder<TOptions>ValidateDataAnnotations 메서드를 호출하여 Microsoft.Extensions.Options.DataAnnotations 패키지에서 사용할 수 있습니다. Microsoft.Extensions.Options.DataAnnotationsMicrosoft.AspNetCore.App 메타패키지에 포함되어 있습니다.

using Microsoft.Extensions.DependencyInjection;

private class AnnotatedOptions
{
    [Required]
    public string Required { get; set; }

    [StringLength(5, ErrorMessage = "Too long.")]
    public string StringLength { get; set; }

    [Range(-5, 5, ErrorMessage = "Out of range.")]
    public int IntRange { get; set; }
}

[Fact]
public void CanValidateDataAnnotations()
{
    var services = new ServiceCollection();
    services.AddOptions<AnnotatedOptions>()
        .Configure(o =>
        {
            o.StringLength = "111111";
            o.IntRange = 10;
            o.Custom = "nowhere";
        })
        .ValidateDataAnnotations();

    var sp = services.BuildServiceProvider();

    var error = Assert.Throws<OptionsValidationException>(() => 
        sp.GetRequiredService<IOptionsMonitor<AnnotatedOptions>>().CurrentValue);
    ValidateFailure<AnnotatedOptions>(error, Options.DefaultName, 1,
        "DataAnnotation validation failed for members Required " +
            "with the error 'The Required field is required.'.",
        "DataAnnotation validation failed for members StringLength " +
            "with the error 'Too long.'.",
        "DataAnnotation validation failed for members IntRange " +
            "with the error 'Out of range.'.");
}

즉시 유효성 검사(시작 시 빠른 실패)는 향후 릴리스에서 고려 중입니다.

옵션 사후 구성

IPostConfigureOptions<TOptions>를 사용하여 사후 구성을 설정합니다. 사후 구성은 모든 IConfigureOptions<TOptions> 구성이 수행된 후에 실행됩니다.

services.PostConfigure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

PostConfigure를 사용하여 명명된 옵션을 사후 구성할 수 있습니다.

services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

모든 구성 인스턴스를 사후 구성하려면 PostConfigureAll을 사용합니다.

services.PostConfigureAll<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

시작하는 동안 옵션 액세스

Configure 메서드가 실행되기 전에 서비스가 만들어지므로 Startup.Configure에서 IOptions<TOptions>IOptionsMonitor<TOptions>를 사용할 수 있습니다.

public void Configure(IApplicationBuilder app, IOptionsMonitor<MyOptions> optionsAccessor)
{
    var option1 = optionsAccessor.CurrentValue.Option1;
}

Startup.ConfigureServices에서는 IOptions<TOptions> 또는 IOptionsMonitor<TOptions>를 사용하지 마세요. 서비스 등록의 순서 지정으로 인해 일관성 없는 옵션 상태가 존재할 수 있습니다.

옵션 패턴은 클래스를 사용하여 관련 설정 그룹을 나타냅니다. 구성 설정이 시나리오에 따라 별도 클래스로 격리된 경우 앱은 두 가지 중요한 소프트웨어 엔지니어링 원칙을 따릅니다.

옵션은 구성 데이터의 유효성을 검사하는 메커니즘도 제공합니다. 자세한 내용은 옵션 유효성 검사 섹션을 참조하세요.

예제 코드 살펴보기 및 다운로드 (다운로드 방법)

사전 요구 사항

Microsoft.AspNetCore.App 메타패키지를 참조하거나 Microsoft.Extensions.Options.ConfigurationExtensions 패키지에 대한 패키지 참조를 추가합니다.

옵션 인터페이스

IOptionsMonitor<TOptions>는 옵션을 검색하고 TOptions 인스턴스에 대한 옵션 알림을 관리하는 데 사용됩니다. IOptionsMonitor<TOptions>은 다음 시나리오를 지원합니다.

사후 구성 시나리오에서는 모든 IConfigureOptions<TOptions> 구성이 수행된 후 옵션을 설정하거나 변경할 수 있습니다.

IOptionsFactory<TOptions>는 새로운 옵션 인스턴스를 만듭니다. 단일 Create 메서드가 있습니다. 기본 구현에서는 등록된 모든 IConfigureOptions<TOptions>IPostConfigureOptions<TOptions>를 사용하며 먼저 구성을 모두 실행한 다음, 사후 구성을 수행합니다. IConfigureNamedOptions<TOptions>IConfigureOptions<TOptions>를 구별하며 적절한 인터페이스만 호출합니다.

IOptionsMonitorCache<TOptions>IOptionsMonitor<TOptions>에서 TOptions 인스턴스를 캐시하는 데 사용됩니다. IOptionsMonitorCache<TOptions>는 모니터의 옵션 인스턴스를 무효화하므로 값이 다시 계산됩니다(TryRemove). TryAdd를 사용하여 값을 수동으로 도입할 수 있습니다. 모든 명명된 인스턴스를 필요에 따라 다시 만들어야 하는 경우 Clear 메서드가 사용됩니다.

IOptionsSnapshot<TOptions>은 모든 요청에서 옵션을 다시 계산해야 하는 시나리오에서 유용합니다. 자세한 내용은 IOptionsSnapshot을 사용하여 구성 데이터 다시 로드를 참조하세요.

IOptions<TOptions>를 사용하여 옵션을 지원할 수 있습니다. 그러나 IOptions<TOptions>는 앞에서 설명한 IOptionsMonitor<TOptions>의 시나리오를 지원하지 않습니다. IOptions<TOptions> 인터페이스를 이미 사용하고 있으며 IOptionsMonitor<TOptions>에서 제공하는 시나리오가 필요하지 않은 기존 프레임워크 및 라이브러리에서는 IOptions<TOptions>를 계속 사용할 수 있습니다.

일반 옵션 구성

일반 옵션 구성은 샘플 앱에 예제 1로 설명되어 있습니다.

옵션 클래스는 매개 변수가 없는 public 생성자를 사용하는 비추상이어야 합니다. 다음 클래스 MyOptions에는 Option1Option2의 두 가지 속성이 있습니다. 기본 값 설정은 선택 사항이지만, 다음 예제의 클래스 생성자는 Option1의 기본값을 설정합니다. Option2에는 직접 속성을 초기화하여 설정된 기본값이 있습니다(Models/MyOptions.cs).

public class MyOptions
{
    public MyOptions()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

MyOptions 클래스는 Configure를 통해 서비스 컨테이너에 추가되고 구성에 바인딩됩니다.

// Example #1: General configuration
// Register the Configuration instance which MyOptions binds against.
services.Configure<MyOptions>(Configuration);

다음 페이지 모델은 IOptionsMonitor<TOptions>를 통해 생성자 종속성 주입을 사용하여 설정에 액세스합니다(Pages/Index.cshtml.cs).

private readonly MyOptions _options;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #1: Simple options
var option1 = _options.Option1;
var option2 = _options.Option2;
SimpleOptions = $"option1 = {option1}, option2 = {option2}";

샘플의 appsettings.json 파일은 option1option2의 값을 지정합니다.

{
  "option1": "value1_from_json",
  "option2": -1,
  "subsection": {
    "suboption1": "subvalue1_from_json",
    "suboption2": 200
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

앱을 실행할 때 페이지 모델의 OnGet 메서드는 옵션 클래스 값을 표시하는 문자열을 반환합니다.

option1 = value1_from_json, option2 = -1

참고

사용자 지정 ConfigurationBuilder를 사용하여 설정 파일에서 옵션 구성을 로드하는 경우 기본 경로가 올바르게 설정되었는지 확인합니다.

var configBuilder = new ConfigurationBuilder()
   .SetBasePath(Directory.GetCurrentDirectory())
   .AddJsonFile("appsettings.json", optional: true);
var config = configBuilder.Build();

services.Configure<MyOptions>(config);

CreateDefaultBuilder를 통해 설정 파일에서 옵션 구성을 로드하는 경우에는 명시적으로 기본 경로를 설정하지 않아도 됩니다.

대리자로 간단한 옵션 구성

대리자를 사용한 간단한 옵션 구성은 샘플 앱에 예제 2로 설명되어 있습니다.

대리자를 사용하여 옵션 값을 설정합니다. 샘플 앱에서는 MyOptionsWithDelegateConfig 클래스(Models/MyOptionsWithDelegateConfig.cs)를 사용합니다.

public class MyOptionsWithDelegateConfig
{
    public MyOptionsWithDelegateConfig()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

다음 코드에서 두 번째 IConfigureOptions<TOptions> 서비스가 서비스 컨테이너에 추가됩니다. MyOptionsWithDelegateConfig를 통해 바인딩을 구성하는 데 대리자를 사용합니다.

// Example #2: Options bound and configured by a delegate
services.Configure<MyOptionsWithDelegateConfig>(myOptions =>
{
    myOptions.Option1 = "value1_configured_by_delegate";
    myOptions.Option2 = 500;
});

Index.cshtml.cs:

private readonly MyOptionsWithDelegateConfig _optionsWithDelegateConfig;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #2: Options configured by delegate
var delegate_config_option1 = _optionsWithDelegateConfig.Option1;
var delegate_config_option2 = _optionsWithDelegateConfig.Option2;
SimpleOptionsWithDelegateConfig = 
    $"delegate_option1 = {delegate_config_option1}, " +
    $"delegate_option2 = {delegate_config_option2}";

여러 구성 공급자를 추가할 수 있습니다. 구성 공급자는 NuGet 패키지에서 사용할 수 있으며 등록된 순서대로 적용됩니다. 자세한 내용은 ASP.NET Core의 구성를 참조하세요.

Configure를 호출할 때마다 IConfigureOptions<TOptions> 서비스가 서비스 컨테이너에 추가됩니다. 위의 예제에서는 Option1Option2의 값은 모두 appsettings.json 에서 지정되지만 Option1Option2의 값은 구성된 대리자에 의해 재정의됩니다.

둘 이상의 구성 서비스를 활성화한 경우 마지막 지정된 구성 소스가 승리 하고 구성 값을 설정합니다. 앱을 실행할 때 페이지 모델의 OnGet 메서드는 옵션 클래스 값을 표시하는 문자열을 반환합니다.

delegate_option1 = value1_configured_by_delegate, delegate_option2 = 500

하위 옵션 구성

하위 옵션 구성은 샘플 앱에 예제 3으로 설명되어 있습니다.

앱은 앱의 특정 시나리오 그룹(클래스)에 적합한 옵션 클래스를 만들어야 합니다. 구성 값을 필요로 하는 앱의 부분은 사용하는 구성 값에만 액세스할 수 있어야 합니다.

옵션을 구성에 바인딩하는 경우 옵션 형식의 각 속성은 property[:sub-property:] 양식의 구성 키에 바인딩됩니다. 예를 들어 MyOptions.Option1 속성은 키 Option1에 바인딩되어 appsettings.jsonoption1 속성에서 읽힙니다.

다음 코드에서 세 번째 IConfigureOptions<TOptions> 서비스가 서비스 컨테이너에 추가됩니다. 이 서비스는 MySubOptionsappsettings.json 파일의 섹션 subsection에 바인딩합니다.

// Example #3: Suboptions
// Bind options using a sub-section of the appsettings.json file.
services.Configure<MySubOptions>(Configuration.GetSection("subsection"));

GetSection 메서드를 사용하려면 Microsoft.Extensions.Configuration 네임스페이스가 필요합니다.

샘플의 appsettings.json 파일은 suboption1suboption2에 대한 키로 subsection 멤버를 정의합니다.

{
  "option1": "value1_from_json",
  "option2": -1,
  "subsection": {
    "suboption1": "subvalue1_from_json",
    "suboption2": 200
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

MySubOptions 클래스는 SubOption1SubOption2 속성을 정의하여 옵션 값을 유지합니다(Models/MySubOptions.cs).

public class MySubOptions
{
    public MySubOptions()
    {
        // Set default values.
        SubOption1 = "value1_from_ctor";
        SubOption2 = 5;
    }
    
    public string SubOption1 { get; set; }
    public int SubOption2 { get; set; }
}

페이지 모델의 OnGet 메서드는 옵션 값이 포함된 문자열을 반환합니다(Pages/Index.cshtml.cs).

private readonly MySubOptions _subOptions;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #3: Suboptions
var subOption1 = _subOptions.SubOption1;
var subOption2 = _subOptions.SubOption2;
SubOptions = $"subOption1 = {subOption1}, subOption2 = {subOption2}";

앱을 실행할 때 OnGet 메서드는 하위 옵션 클래스 값을 표시하는 문자열을 반환합니다.

subOption1 = subvalue1_from_json, subOption2 = 200

직접 보기 주입 또는 보기 모델에 의해 제공되는 옵션

직접 보기 주입 또는 보기 모델에 의해 제공되는 옵션은 샘플 앱에 예제 4로 설명되어 있습니다.

옵션은 뷰 모델 또는 IOptionsMonitor<TOptions>를 보기에 직접 주입하여 제공할 수 있습니다(Pages/Index.cshtml.cs).

private readonly MyOptions _options;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #4: Bind options directly to the page
MyOptions = _options;

샘플 앱은 @inject 지시문을 사용하여 IOptionsMonitor<MyOptions>를 주입하는 방법을 보여 줍니다.

@page
@model IndexModel
@using Microsoft.Extensions.Options
@inject IOptionsMonitor<MyOptions> OptionsAccessor
@{
    ViewData["Title"] = "Options Sample";
}

<h1>@ViewData["Title"]</h1>

앱을 실행하면 옵션 값이 렌더링된 페이지에 표시됩니다.

옵션 값 Option1: value1_from_json 및 Option2: -1은 모델에서 로드되며 보기에 주입됩니다.

IOptionsSnapshot을 사용하여 구성 데이터 다시 로드

IOptionsSnapshot<TOptions>을 사용하여 구성 데이터를 다시 로드하는 방법은 샘플 앱에 예제 5로 설명되어 있습니다.

IOptionsSnapshot<TOptions>은 최소한의 처리 오버헤드로 옵션 다시 로드를 지원합니다.

옵션은 엑세스될 때 요청 당 한 번씩 계산되고 요청의 수명 동안 캐시됩니다.

다음 예제에는 appsettings.json 이 변경된 후 새 IOptionsSnapshot<TOptions>을 만드는 방법이 설명되어 있습니다(Pages/Index.cshtml.cs). 서버에 대한 여러 요청은 파일이 변경되고 구성이 다시 로드될 때까지 appsettings.json 파일에서 제공하는 상수 값을 반환합니다.

private readonly MyOptions _snapshotOptions;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #5: Snapshot options
var snapshotOption1 = _snapshotOptions.Option1;
var snapshotOption2 = _snapshotOptions.Option2;
SnapshotOptions = 
    $"snapshot option1 = {snapshotOption1}, " +
    $"snapshot option2 = {snapshotOption2}";

다음 이미지는 appsettings.json 파일에서 로드된 초기 option1option2 값을 보여 줍니다.

snapshot option1 = value1_from_json, snapshot option2 = -1

appsettings.json 파일의 값을 value1_from_json UPDATED200으로 변경합니다. appsettings.json 파일을 저장합니다. 옵션 값이 변경되었음을 확인하려면 브라우저를 새로 고칩니다.

snapshot option1 = value1_from_json UPDATED, snapshot option2 = 200

IConfigureNamedOptions로 명명된 옵션 지원

IConfigureNamedOptions<TOptions>로 명명된 옵션 지원은 샘플 앱에 예제 6으로 설명되어 있습니다.

앱은 명명된 옵션 지원을 통해 명명된 옵션 구성들을 구분할 수 있습니다. 샘플 앱에서 명명된 옵션은 ConfigureNamedOptions<TOptions>.Configure 확장 메서드를 호출하는 OptionsServiceCollectionExtensions.Configure를 사용하여 선언됩니다. 명명된 옵션은 대/소문자를 구분합니다.

// Example #6: Named options (named_options_1)
// Register the ConfigurationBuilder instance which MyOptions binds against.
// Specify that the options loaded from configuration are named
// "named_options_1".
services.Configure<MyOptions>("named_options_1", Configuration);

// Example #6: Named options (named_options_2)
// Specify that the options loaded from the MyOptions class are named
// "named_options_2".
// Use a delegate to configure option values.
services.Configure<MyOptions>("named_options_2", myOptions =>
{
    myOptions.Option1 = "named_options_2_value1_from_action";
});

샘플 앱은 Get을 사용하여 명명된 옵션에 액세스합니다(Pages/Index.cshtml.cs).

private readonly MyOptions _named_options_1;
private readonly MyOptions _named_options_2;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #6: Named options
var named_options_1 = 
    $"named_options_1: option1 = {_named_options_1.Option1}, " +
    $"option2 = {_named_options_1.Option2}";
var named_options_2 = 
    $"named_options_2: option1 = {_named_options_2.Option1}, " +
    $"option2 = {_named_options_2.Option2}";
NamedOptions = $"{named_options_1} {named_options_2}";

샘플 앱을 실행하면 명명된 옵션이 반환됩니다.

named_options_1: option1 = value1_from_json, option2 = -1
named_options_2: option1 = named_options_2_value1_from_action, option2 = 5

named_options_1 값은 appsettings.json 파일에서 로드되는 구성에서 제공됩니다. named_options_2 값은 다음에서 제공됩니다.

  • Option1에 대한 ConfigureServicesnamed_options_2 대리자.
  • MyOptions 클래스에서 제공되는 Option2에 대한 기본값.

ConfigureAll 메서드를 사용하여 모든 옵션 구성

ConfigureAll 메서드를 사용하여 모든 옵션 인스턴스를 구성합니다. 다음 코드는 모든 구성 인스턴스에 대해 Option1을 공통 값으로 구성합니다. 다음 코드를 Startup.ConfigureServices 메서드에 직접 추가합니다.

services.ConfigureAll<MyOptions>(myOptions => 
{
    myOptions.Option1 = "ConfigureAll replacement value";
});

코드를 추가한 후 샘플 앱을 실행하면 다음과 같은 결과가 생성됩니다.

named_options_1: option1 = ConfigureAll replacement value, option2 = -1
named_options_2: option1 = ConfigureAll replacement value, option2 = 5

참고

모든 옵션은 명명된 인스턴스입니다. 기존 IConfigureOptions<TOptions> 인스턴스는 string.EmptyOptions.DefaultName 인스턴스를 대상으로 지정한 것처럼 처리됩니다. IConfigureNamedOptions<TOptions>IConfigureOptions<TOptions>도 구현합니다. IOptionsFactory<TOptions>의 기본 구현에는 각 옵션을 적절하게 사용하기 위한 논리가 있습니다. null 명명된 옵션은 특정 명명된 인스턴스 대신 모든 명명된 인스턴스를 대상으로 지정하는 데 사용됩니다(ConfigureAllPostConfigureAll에서 이 규칙을 사용함).

OptionsBuilder API

OptionsBuilder<TOptions>TOptions 인스턴스를 구성하는 데 사용됩니다. OptionsBuilderAddOptions<TOptions>(string optionsName) 호출에 대한 단일 매개 변수이므로 모든 후속 호출에 나타나는 대신 명명된 옵션 생성을 간소화합니다. 옵션 유효성 검사 및 서비스 종속성을 허용하는 ConfigureOptions 오버로드는 OptionsBuilder를 통해서만 사용할 수 있습니다.

// Options.DefaultName = "" is used.
services.AddOptions<MyOptions>().Configure(o => o.Property = "default");

services.AddOptions<MyOptions>("optionalName")
    .Configure(o => o.Property = "named");

DI 서비스를 사용하여 옵션 구성

다음과 같은 두 가지 방법으로 옵션을 구성하는 동안 종속성 주입을 통해 다른 서비스에 액세스할 수 있습니다.

서비스를 만드는 것이 더 복잡하기 때문에 구성 대리자를 Configure에 전달하는 것이 좋습니다. 고유의 형식을 만드는 것은 Configure를 사용할 때 프레임워크가 수행하는 것과 동일합니다. Configure을 호출하면 지정된 일반 서비스 유형을 허용하는 생성자가 있는 일시적인 일반 IConfigureNamedOptions<TOptions>가 등록됩니다.

옵션 사후 구성

IPostConfigureOptions<TOptions>를 사용하여 사후 구성을 설정합니다. 사후 구성은 모든 IConfigureOptions<TOptions> 구성이 수행된 후에 실행됩니다.

services.PostConfigure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

PostConfigure를 사용하여 명명된 옵션을 사후 구성할 수 있습니다.

services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

모든 구성 인스턴스를 사후 구성하려면 PostConfigureAll을 사용합니다.

services.PostConfigureAll<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

시작하는 동안 옵션 액세스

Configure 메서드가 실행되기 전에 서비스가 만들어지므로 Startup.Configure에서 IOptions<TOptions>IOptionsMonitor<TOptions>를 사용할 수 있습니다.

public void Configure(IApplicationBuilder app, IOptionsMonitor<MyOptions> optionsAccessor)
{
    var option1 = optionsAccessor.CurrentValue.Option1;
}

Startup.ConfigureServices에서는 IOptions<TOptions> 또는 IOptionsMonitor<TOptions>를 사용하지 마세요. 서비스 등록의 순서 지정으로 인해 일관성 없는 옵션 상태가 존재할 수 있습니다.

추가 자료