跟我一起學.NetCore之Options實例演示及分析


前言

來啦!來啦!上一節一堆代碼,是不是感覺甚是無味啊?沒關系,這里結合上一節內容專注舉例演示,絕不廢話!走起~~~~~

正文

老規矩,一個WebApi項目走起,項目結構如下:

img

上一節中提到,Options是基於依賴注入的,所以我們需要將相關服務進行注冊,如下:

img

注冊完成之后就可以直接用啦,這里新建了一個ComputerController進行測試:

img

運行走起:

img

通常在使用的時候,相關服務的配置會放到配置文件中,比如數據庫連接字符串等,所以在注冊的時候將初始化的內容從配置中獲取即可,如下:

img

跑起來~~~

img

通過以上的實例演示,可能會好奇為啥不直接用Configuration,我是這樣理解的:通過Options的話,服務不限制使用初始化方式,根據需求選擇,如果使用配置文件取值,服務也和配置沒有直接關系,從而使得服務的使用沒有過多限制;另外服務內部使用也比較便捷,就單純操作Options對象;

哎呀,又來這一套,使用太簡單了,來點真貨唄,此時應該有小伙伴按捺不住了問:Options的相關服務是怎么注冊的,初始化和上一節講的初始化有啥關系? 來來來,不急,以下慢慢品~~~

那從哪開始呢?

上一小節說的TOptions對象創建的三大步,創建內部直接New了(忘了的可以回顧一下上一節:跟我一起學.NetCore之選項(Options)核心類型簡介),那這里就從注冊初始化的地方下手:

img

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一般會用在讀取到配置數據之后對數據進行加工,如下:

img

接下來說熱更新,也就是配置改變,服務對應的Options能獲取最新的值,代碼稍微優化一下:

img

第一次訪問,第二次訪問前進行配置文件修改,然后在訪問,運行結果如下:

img

可以看到,配置數據改變時,IOptionsSnapshot<>和IOptionsMonitor<>都能獲取到最新的值,IOptions<>沒有獲取到,簡單分析一下:

IOptons<>和IOptionsSnapshot<>其實內部是一樣的,只是注入的生命周期不一樣,前者是單例,后者是Scoped,這樣就使得后者每次請求都是不同的IOptionsSnapshot對象,從而就能獲取最新的值;而單例IOptions對象一致不變;那為什么單例的IOptionsMonitor類型能改變呢,那是因為IOptionsMonitor提供了監聽改變的功能,上一節有簡單說明;

如果在配置數據改變時,需要通知Option怎么辦呢?通過IOptionsMonitor 的OnChange監聽改變,如下:

img

運行看結果,首先請求一次,然后修改配置文件,就能看到實時監控了:

img

最后來說說Options驗證(也就是創建TOptions的第三步),主要是避免不合法的配置,導致程序在運行時業務邏輯出錯才能發現錯誤,從而導致程序迭代周期頻繁,用戶體驗差;驗證有以下三種方式:

  • 直接注冊驗證函數

  • 實現IValidateOptions 進行驗證

  • 使用注解(Microsoft.Extensions.Options.DataAnnotations)

這里用三種方式分別依次驗證Name,Cores,MemorySize 三個配置項,如下:

  • 直接注冊驗證函數

    img

  • 實現IValidateOptions 進行驗證

    img

  • 使用注解(Microsoft.Extensions.Options.DataAnnotations)

    img

三種驗證方式是相互獨立的,可以單獨使用,也可以組合使用;以上每一種方式對應圖片內容;

運行訪問不合法就報錯,這樣就避免配置不合法,導致業務邏輯不合法,如下

img

總結

Options(選項)的常規用法暫時就說這么多了,結合上一小節是不是感覺清晰多了~~~下一節說說日志(ILogger)

----------------------------------------------

一個被程序搞丑的帥小伙,關注"Code綜藝圈",識別關注跟我一起學~~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM