前言
來啦!來啦!上一節一堆程式碼,是不是感覺甚是無味啊?沒關係,這裡結合上一節內容專注舉例演示,絕不廢話!走起~~~~~
正文
老規矩,一個WebApi專案走起,專案結構如下:
上一節中提到,Options是基於依賴注入的,所以我們需要將相關服務進行註冊,如下:
註冊完成之後就可以直接用啦,這裡新建了一個ComputerController進行測試:
執行走起:
通常在使用的時候,相關服務的配置會放到配置檔案中,比如資料庫連線字串等,所以在註冊的時候將初始化的內容從配置中獲取即可,如下:
跑起來~~~
通過以上的例項演示,可能會好奇為啥不直接用Configuration,我是這樣理解的:通過Options的話,服務不限制使用初始化方式,根據需求選擇,如果使用配置檔案取值,服務也和配置沒有直接關係,從而使得服務的使用沒有過多限制;另外服務內部使用也比較便捷,就單純操作Options物件;
哎呀,又來這一套,使用太簡單了,來點真貨唄,此時應該有小夥伴按捺不住了問:Options的相關服務是怎麼註冊的,初始化和上一節講的初始化有啥關係? 來來來,不急,以下慢慢品~~~
那從哪開始呢?
上一小節說的TOptions物件建立的三大步,建立內部直接New了(忘了的可以回顧一下上一節:跟我一起學.NetCore之選項(Options)核心型別簡介),那這裡就從註冊初始化的地方下手:
Services.Configure方法中肯定有事,不然一句程式碼咋就讓後面使用如此便捷呢,來,直接看原始碼:
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Extension methods for adding configuration related options services to the DI container.
/// </summary>
public static class OptionsConfigurationServiceCollectionExtensions
{
// 挨著的這三個方法都是IServiceCollection 的擴充套件方法
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration config) where TOptions : class
=> services.Configure<TOptions>(Options.Options.DefaultName, config);
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config) where TOptions : class
=> services.Configure<TOptions>(name, config, _ => { });
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration config, Action<BinderOptions> configureBinder)
where TOptions : class
=> services.Configure<TOptions>(Options.Options.DefaultName, config, configureBinder);
// 關鍵方法
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder)
where TOptions : class
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (config == null)
{
throw new ArgumentNullException(nameof(config));
}
// 註冊Options相關服務,這是關鍵方法
services.AddOptions();
// IOptionsChangeTokenSource 註冊,後續用於通知
services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config));
// 註冊從配置系統中獲取配置值的服務
return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
}
}
}
上面程式碼中services.AddOptions()是關鍵方法,再翻程式碼瞅瞅:
public static class OptionsServiceCollectionExtensions
{
// 恍然大悟吧,就是這個擴充套件方法,把服務都註冊好了,所以使用的時候才直接注入即可
public static IServiceCollection AddOptions(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
// 使用TryAdd的注入方式,為了對應服務型別只註冊一次
// 註冊IOptions<>,實現類是OptionsManager<>
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
// 註冊IOptionsSnapshot<>,實現類是OptionsManager<>
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
// 註冊IOptionsMonitor<>,實現類是OptionsMonitor<>
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
// 註冊IOptionsFactory<> ,實現類是OptionsFactory<>
services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
// 註冊IOptionsMonitorCache<>, 實現類是OptionsCache<>
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
return services;
}
// 對應建立TOption 初始化的兩小步,步驟1Configure
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions)
where TOptions : class
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (configureOptions == null)
{
throw new ArgumentNullException(nameof(configureOptions));
}
services.AddOptions();
services.AddSingleton<IConfigureOptions<TOptions>>(new ConfigureNamedOptions<TOptions>(name, configureOptions));
return services;
}
// 對應建立TOption 初始化的兩小步,步驟2 PostConfigure
public static IServiceCollection PostConfigure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions)
where TOptions : class
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (configureOptions == null)
{
throw new ArgumentNullException(nameof(configureOptions));
}
services.AddOptions();
services.AddSingleton<IPostConfigureOptions<TOptions>>(new PostConfigureOptions<TOptions>(name, configureOptions));
return services;
}
......省略了其他方法....
}
看了以上程式碼,是不是豁然開朗了,Options的相關服務註冊都是在AddOptions擴充套件方法中完成的,至於其中的Configure和PostConfigure實則對應著上一節的建立Options初始化的過程;以上註冊的核心型別的生命週期需要了解一下,後面舉例會關聯到:
-
IOptions<>:Singleton - 單例模式
-
IOptionsSnapshot<>:Scoped - 作用域範圍模式
-
IOptionsMonitor<>: Singleton - 單例模式
Configure是註冊時進行初始化,而PostConfigure一般會用在讀取到配置資料之後對資料進行加工,如下:
接下來說熱更新,也就是配置改變,服務對應的Options能獲取最新的值,程式碼稍微優化一下:
第一次訪問,第二次訪問前進行配置檔案修改,然後在訪問,執行結果如下:
可以看到,配置資料改變時,IOptionsSnapshot<>和IOptionsMonitor<>都能獲取到最新的值,IOptions<>沒有獲取到,簡單分析一下:
IOptons<>和IOptionsSnapshot<>其實內部是一樣的,只是注入的生命週期不一樣,前者是單例,後者是Scoped,這樣就使得後者每次請求都是不同的IOptionsSnapshot物件,從而就能獲取最新的值;而單例IOptions物件一致不變;那為什麼單例的IOptionsMonitor型別能改變呢,那是因為IOptionsMonitor提供了監聽改變的功能,上一節有簡單說明;
如果在配置資料改變時,需要通知Option怎麼辦呢?通過IOptionsMonitor
執行看結果,首先請求一次,然後修改配置檔案,就能看到實時監控了:
最後來說說Options驗證(也就是建立TOptions的第三步),主要是避免不合法的配置,導致程式在執行時業務邏輯出錯才能發現錯誤,從而導致程式迭代週期頻繁,使用者體驗差;驗證有以下三種方式:
-
直接註冊驗證函式
-
實現IValidateOptions
進行驗證 -
使用註解(Microsoft.Extensions.Options.DataAnnotations)
這裡用三種方式分別依次驗證Name,Cores,MemorySize 三個配置項,如下:
-
直接註冊驗證函式
-
實現IValidateOptions
進行驗證 -
使用註解(Microsoft.Extensions.Options.DataAnnotations)
三種驗證方式是相互獨立的,可以單獨使用,也可以組合使用;以上每一種方式對應圖片內容;
執行訪問不合法就報錯,這樣就避免配置不合法,導致業務邏輯不合法,如下
總結
Options(選項)的常規用法暫時就說這麼多了,結合上一小節是不是感覺清晰多了~~~下一節說說日誌(ILogger)
----------------------------------------------
一個被程式搞醜的帥小夥,關注"Code綜藝圈",識別關注跟我一起學~~~