當前位置: 妍妍網 > 碼農

使用 Options 模式在 ASP.NET Core中進行參數配置

2024-07-08碼農


ASP.NET Core 中的 選項模式 提供了一種以型別安全方式管理配置的可靠方法。這篇博文探討了 Options Pattern 、其優點以及如何在 ASP.NET Core 應用程式中實作它。

訂閱,因為更多即將到來。

如何管理 ASP.NET 核心應用程式中的配置?

每個 ASP.NET 應用程式都需要管理配置。

讓我們探討一下如何在 ASP.NET Core 套用中從appsettings.json進行管理:BlogPostConfiguration

{
"BlogPostConfiguration": {
"ScheduleInterval": 10,
"PublishCount": 5
}
}

管理配置的樸素方法是在 DI 容器中使用註冊為 Singleton 的自訂配置類:

publicrecordBlogPostConfiguration
{
publicint ScheduleInterval { get; init; }
publicint PublishCount { get; init; }
}
var configuration = newBlogPostConfiguration();
builder.Configuration.Bind("BlogPostConfiguration", configuration);
builder.Services.AddSingleton(configuration);

讓我們實作一個服務,該服務將使用此配置根據配置每 X 秒觸發一次部落格文章釋出作業。此作業應在每次叠代中獲取配置的部落格計數。簡化的實作如下:BackgroundService

public classBlogBackgroundService : BackgroundService
{
privatereadonlyIServiceScopeFactory _scopeFactory;
privatereadonlyBlogPostConfiguration _configuration;
privatereadonlyILogger<BlogBackgroundService> _logger;
publicBlogBackgroundService(
IServiceScopeFactory scopeFactory,
BlogPostConfiguration configuration,
ILogger<BlogBackgroundService> logger)
{
_scopeFactory = scopeFactory;
_configuration = configuration;
_logger = logger;
}
protectedoverrideasyncTaskExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Trigger blog publishment background job");
usingvar scope = _scopeFactory.CreateScope();
awaitusingvar dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var blogs = await dbContext.BlogPosts
.Take(_configuration.PublishCount)
.ToListAsync(cancellationToken: stoppingToken);
_logger.LogInformation("Publish {BlogsCount} blogs: {@Blogs}",
blogs.Count, blogs.Select(x => x.Title));
var delay = TimeSpan.FromSeconds(_configuration.ScheduleInterval);
await Task.Delay(delay, stoppingToken);
}
}
}




在這裏,我們將配置類直接註入到作業的建構函式中,並在方法中使用它。BlogPostConfigurationExecuteAsync

乍一看,這種方法似乎還可以,但它有幾個缺點:

  1. 配置是手動構建的,沒有任何驗證

  2. 配置註冊為單例,如果不重新啟動應用程式,則無法更改

  3. 配置與服務邏輯緊密耦合。這種方法降低了程式碼的靈活性和可維護性

  4. 測試可能更麻煩,因為配置與服務緊密繫結。模擬單元測試的配置需要更多的設定,並且容易出錯。

另一種方法是在每次我們需要讀取配置時註入作業的建構函式和呼叫方法。這種方法要糟糕得多,因為它會建立更多的配置與服務邏輯的耦合。IConfigurationGetp("").GetValue<T>()

更好的方法是使用 選項模式

ASP.NET Core 中期權模式的基礎知識

選項模式 是 ASP.NET Core 中的一種約定,它允許開發人員將配置設定對映到強型別類。

此模式具有以下優點:

  1. **型別安全:**配置值對映到強型別物件,減少因配置不正確而導致的錯誤

  2. **驗證:**支持配置值驗證

  3. **關註點分離:**配置邏輯與應用程式邏輯分離,使程式碼庫更簡潔,更易於維護。

  4. **易於測試:**在測試過程中可以輕松模擬配置,提高可測試性。

有三種方法可以使用 Options Pattern 獲取 ASP.NET 核心中的配置:和 。IOptionsIOptionsSnapshotIOptionsMonitor

IOptions

IOptions<T>是一種單一例項服務,在應用程式啟動時檢索一次配置值,並且在應用程式的生存期內不會更改。當應用程式執行後不需要更改配置值時,最好使用它。IOptions 是三者中效能最高的選項。

IOptionsSnapshot

IOptionsSnapshot<T>是一種作用域內的服務,每次在同一請求中存取配置值時,都會檢索這些值。它對於在不重新啟動應用程式的情況下處理配置更改非常有用。它具有效能成本,因為它為每個請求提供了 options 類的新例項。

IOptions監視器

IOptionsMonitor<T>是一種單一例項服務,提供對配置值的即時更新。它允許訂閱更改通知,並在任何時間點提供選項的當前值。它非常適合需要動態更改配置值而無需重新啟動應用程式的場景。

這些類的行為不同。讓我們詳細了解一下這些選項中的每一個。

如何在 ASP.NET Core 中使用 IOptions

所有三個選項類在 DI 中的配置註冊是相同的。

讓我們使用選項模式重寫。首先,我們需要更新配置註冊以使用:BlogPostConfigurationAddOptions

builder.Services.AddOptions\<BlogPostConfiguration>()
.Bind(builder.Configuration.Getp(nameof(BlogPostConfiguration)));

現在,我們可以使用介面將此配置註入到後台服務中:IOptions

publicBlogBackgroundService(
IServiceScopeFactory scopeFactory,
IOptions<BlogPostConfiguration> options,
ILogger<BlogBackgroundService> logger)
{
_scopeFactory = scopeFactory;
_options = options;
_logger = logger;
}
protectedoverrideasyncTaskExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// ...
var blogs = await dbContext.BlogPosts
.Take(_options.Value.PublishCount)
.ToListAsync(cancellationToken: stoppingToken);
}
}

要獲取配置值,您需要使用 ._options.Value

如何在 ASP.NET Core 中使用 IOptionsSnapshot

為了最好地說明 和 之間的區別,讓我們建立兩個最小的 API 端點,它們使用以下類返回配置:IOptionsIOptionsSnapshot

app.MapGet("/api/configuration-singleton", (IOptions\<BlogPostConfiguration> options) =>
{
var configuration = options.Value;
return Results.Ok(configuration);
});
app.MapGet("/api/configuration-snapshot", (IOptionsSnapshot\<BlogPostConfiguration> options) =>
{
var configuration = options.Value;
return Results.Ok(configuration);
});

每次呼叫「configuration-singleton」終結點時,它始終返回相同的配置。

但是,如果更新appsettings.json檔並保存它,則下一次呼叫「configuration-snapshot」端點將呈現不同的結果:

如何在 ASP.NET Core 中使用 IOptionsMonitor

為了充分理解工作原理,讓我們嘗試在後台服務中更改為:IOptionsMonitorIOptionsIOptionsMonitor

public classBlogBackgroundServiceWithIOptionsMonitor : BackgroundService
{
privatereadonlyIServiceScopeFactory _scopeFactory;
privatereadonlyIOptionsMonitor<BlogPostConfiguration> _optionsMonitor;
privatereadonlyILogger<BlogBackgroundServiceWithIOptionsMonitor> _logger;
publicBlogBackgroundServiceWithIOptionsMonitor(
IServiceScopeFactory scopeFactory,
IOptionsMonitor<BlogPostConfiguration> optionsMonitor,
ILogger<BlogBackgroundServiceWithIOptionsMonitor> logger)
{
_scopeFactory = scopeFactory;
_optionsMonitor = optionsMonitor;
_logger = logger;
}
protectedoverrideasyncTaskExecuteAsync(CancellationToken stoppingToken)
{
_optionsMonitor.OnChange(newConfig =>
{
_logger.LogInformation("Configuration changed. ScheduleInterval - {ScheduleInterval}, PublishCount - {PublishCount}",
newConfig.ScheduleInterval, newConfig.PublishCount);
});
while (!stoppingToken.IsCancellationRequested)
{
// ...
var blogs = await dbContext.BlogPosts
.Take(_optionsMonitor.CurrentValue.PublishCount)
.ToListAsync(cancellationToken: stoppingToken);
_logger.LogInformation("Publish {BlogsCount} blogs: {@Blogs}",
blogs.Count, blogs.Select(x => x.Title));
var delay = TimeSpan.FromSeconds(_optionsMonitor.CurrentValue.ScheduleInterval);
await Task.Delay(delay, stoppingToken);
}
}
}




以下是值得一提的幾個要點。盡管是 Singleton 類,但它始終返回最新的配置值 using property。IOptionsMonitor_optionsMonitor.CurrentValue

此類具有一個委托的方法,該委托在保存appsettings.json時觸發。此方法可以呼叫兩次:OnChange

info: OptionsPattern.HostedServices.BlogBackgroundServiceWithIOptionsMonitor[0]
Configuration changed. ScheduleInterval - 2, PublishCount - 2
info: OptionsPattern.HostedServices.BlogBackgroundServiceWithIOptionsMonitor[0]
Configuration changed. ScheduleInterval - 2, PublishCount - 2

這可能因檔案系統而異,檔案系統可能會觸發更新作業系統中保存的檔和檔關閉事件的配置。IOptionsMonitor

選項模式中的驗證

如前所述,ASP.NET Core 中的期權模式支持驗證。它支持 2 種型別的驗證:數據註釋和自訂驗證。

數據註釋驗證基於內容驗證,我不喜歡。這種型別的驗證透過使用驗證邏輯汙染配置類來破壞單一責任原則。

我更喜歡使用自訂驗證。讓我們看一下如何添加驗證。BlogPostConfiguration

首先,讓我們擴充套件 DI 容器中的配置註冊,並添加 和 方法呼叫:ValidateDataAnnotationsValidateOnStart

builder.Services.AddOptions<BlogPostConfiguration>()
.Bind(builder.Configuration.Getp(nameof(BlogPostConfiguration)))
.ValidateDataAnnotations()
.ValidateOnStart();

無論選擇哪種驗證型別,我們都需要呼叫該方法。ValidateDataAnnotations

ValidateOnStart方法在 Core 套用啟動時觸發驗證 ASP.NET 並在 appsettings.json 中更新配置時觸發驗證。這對於在應用程式啟動之前及早捕獲錯誤特別有用。

為了進行驗證,我們將使用 FluentValidation 庫:

public classBlogPostConfigurationValidator : AbstractValidator<BlogPostConfiguration>
{
publicBlogPostConfigurationValidator()
{
RuleFor(x => x.ScheduleInterval).GreaterThan(0);
RuleFor(x => x.PublishCount).GreaterThan(0);
}
}

現在,讓我們透過實作介面來建立自訂選項驗證器:IValidateOptions<T>

public classBlogPostConfigurationValidationOptions : IValidateOptions<BlogPostConfiguration>
{
privatereadonlyIServiceScopeFactory _scopeFactory;
publicBlogPostConfigurationValidationOptions(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
publicValidateOptionsResultValidate(string? name, BlogPostConfiguration options)
{
usingvar scope = _scopeFactory.CreateScope();
var validator = scope.ServiceProvider.GetRequiredService<IValidator<BlogPostConfiguration>>();
var result = validator.Validate(options);
if (result.IsValid)
{
return ValidateOptionsResult.Success;
}
var errors = result.Errors.Select(error => $"{error.PropertyName}: {error.ErrorMessage}").ToList();
return ValidateOptionsResult.Fail(errors);
}
}


BlogPostConfigurationValidationOptions必須註冊一個單一例項,這就是我們從服務範圍工廠解析範圍的原因。IValidator<BlogPostConfiguration>

最後,您需要在 DI 中註冊驗證器和驗證選項:

builder.Services.AddValidatorsFromAssemblyContaining(typeof(BlogPostConfigurationValidator));
builder.Services.AddSingleton<IValidateOptions<BlogPostConfiguration>, BlogPostConfigurationValidationOptions>();

在以下情況下呼叫該方法:Validate

  • 應用程式啟動

  • 配置已於 appsettings.json 年更新

  • 使用選項模式管理其他檔中的配置

    Core 中 Options Pattern 的真正強大之處在於 ASP.NET 您可以使用 Options 類解析來自任何來源的配置。

    在上面的所有範例中,我們都在標準appsettings.json中管理配置。同樣,您可以管理任何其他 JSON 檔中的配置。

    讓我們建立一個「custom.settings.json」檔:

    {
    "BlogLimitsConfiguration": {
    "MaxBlogsPerDay": 3
    }
    }

    然後我們可以將這個檔添加到物件中,並為其配置添加選項:Configuration

    builder.Configuration.AddJsonFile("custom.settings.json", true, true);
    builder.Services.AddOptions<BlogLimitsConfiguration>()
    .Bind(builder.Configuration.Getp(nameof(BlogLimitsConfiguration)));

    現在我們可以與任何 Options 類一起使用,例如:BlogLimitsConfiguration

    app.MapGet("/api/configuration-custom", (IOptions\<BlogLimitsConfiguration> options) =>
    {
    var configuration = options.Value;
    return Results.Ok(configuration);
    });

    您甚至可以建立自訂選項配置提供程式,以便從資料庫、redis 或任何其他儲存中讀取配置。外部 Nuget 包中有許多現成的配置提供程式,例如,使用 Options 類從 Azure、AWS 存取配置。

    如果你喜歡我的文章,請給我一個贊!謝謝