ASP.NET Core 學習筆記 第五篇 ASP.NET Core 中的選項


前言

還記得上一篇文章中所說的配置嗎?本篇文章算是上一篇的延續吧。在 .NET Core 中讀取配置文件大多數會為配置選項綁定一個POCO(Plain Old CLR Object)對象,並通過依賴注入使用它。而這種使用方式稱之為選項模式。而選項模式使是用類來提供對相關設置組的強類型訪問,同時選項還提供驗證配置數據的機制,是不是很強大,讓我們一點點揭開它的神秘面紗。

ASP.NET Core 6.0 Web API簡要說明

首先開發工具上需要VS2022了,這里非常推薦大家下載使用,在編碼上真的越來越符合我們開發者的使用了,提示也更加智能化,VS2022詳細說明,下載以及注冊碼可以參考我的上一篇博客,這里就不做詳細說明了VS2022傳送門(含注冊碼)
.NET 6.0 已經出來有一段時間了,為了跟進技術的進步,筆者后續的系列筆記也將使用.NET 6.0作為目標框架,那么.NET 6.0 ASP.NET Core Web API 中帶了了哪些變化呢?這里我做下簡要說明。

  1. 打開Visual Studio 2022,創建新項目,選擇“ASP.NET Core Web API”項目模板:
    image
    這里可以看見Use controllers選項默認選中的,取消該選項,則會創建最小Web API。
  2. 結構變化
    image
    這里可以看下Program.cs文件的變化
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();

var app = builder.Build();

// Configure the HTTP request pipeline.

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

可以發現我們熟知的Startup.cs不見了,現在,全部都在Program.cs中實現:

  • 在WebApplicationBuilder實例上使用Addxxx方法向DI容器注冊特定服務,類似Startup類的ConfigureServices方法實現。
  • 在WebApplication實例上使用Usexxx方法將一系列中間件加入到HTTP管道,類似Startup類的Configure方法實現。
  • DTO使用record定義,而且也放在同一個Program.cs文件中。
    說明: Visual Studio 2022默認使用 Kestrel Web服務器,而不是IIS Express。

配置綁定

首先創建一個json格式的配置文件

{
  "JsonConfig": {
    "Title": "MyTitle",
    "Name": "Tom"
  }
}

創建選項類

    public class MyOptions
    {
        public const string JsonConfig = "JsonConfig";

        public string? Title { get; set; }

        public string? Name { get; set; }
    }

選項類說明:

  • 必須是包含公共無參數構造函數的非抽象類。
  • 類型的所有公共讀寫屬性都已綁定。
  • 不會綁定字段。 在上面的代碼中,Position 未綁定。 由於使用了 Position 屬性,因此在將類綁定到配置提供程序時,不需要在應用中對字符串 "Position" 進行硬編碼。

使用 ConfigurationBinder 綁定

新建一個控制器來讀取配置文件。

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class OptionsController : ControllerBase
    {
        private readonly IConfiguration _configuration;

        public OptionsController(IConfiguration configuration)
        {
            _configuration=configuration;
        }

        public ContentResult GetOptions()
        {
            var options = new MyOptions();

            //方式1
            _configuration.GetSection(MyOptions.JsonConfig).Bind(options);

            //方式2
            _configuration.GetSection(MyOptions.JsonConfig).Get<MyOptions>();

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

說明:
這里的類型綁定有兩種方式分別為ConfigurationBinder.BindConfigurationBinder.Get ,
前者相對於后者更方便一些,在開發過程當中比較推薦使用后者做類型綁定。

依賴注入綁定

服務容器中綁定配置。

using OptionsTest.Model;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();

builder.Configuration.AddJsonFile("MyJsonConfig.json", optional: true, reloadOnChange: true);

//注入綁定
builder.Services.Configure<MyOptions>(builder.Configuration.GetSection(MyOptions.JsonConfig));

var app = builder.Build();

// Configure the HTTP request pipeline.

app.UseAuthorization();

app.MapControllers();

app.Run();

新建一個控制器來讀取配置文件。

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class Options2Controller : ControllerBase
    {
        private readonly MyOptions _options;

        public Options2Controller(IOptions<MyOptions> options)
        {
            _options = options.Value;
        }

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

選項接口

IOptions :

  • 不支持:
    • 在應用啟動后讀取配置數據。
    • 命名選項
    • 注冊為單一實例且可以注入到任何服務生存期。

IOptionsSnapshot :

  • 在每次請求時應重新計算選項的方案中有用。 有關詳細信息,請參閱使用 IOptionsSnapshot 讀取已更新的數據。
  • 注冊為范圍內,因此無法注入到單一實例服務。
  • 支持命名選項

IOptionsMonitor :

  • 用於檢索選項並管理 TOptions 實例的選項通知。
  • 注冊為單一實例且可以注入到任何服務生存期。
    • 支持:
    • 更改通知
    • 命名選項
    • 可重載配置
    • 選擇性選項失效 (IOptionsMonitorCache )

IOptionsSnapshot

在項目中如何在改變配置文件后,在不重啟項目的前提下自動加載配置文件呢?IOptionsSnapshot就支持讀取已更新的配置值。
在前面項目的基礎上略做改變就能做到,MyOptions.cs、MyJsonConfig.json、Program.cs不需要改變。在OptionsController中改用IOptionsSnapshot注入就可以了,代碼如下:

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class Options2Controller : ControllerBase
    {
        private readonly MyOptions _options;

        public Options2Controller(IOptionsSnapshot<MyOptions> options)
        {
            _options = options.Value;
        }

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

是不是很簡單,這樣每次改完配置文件就不需要重啟程序了。

IOptionsMonitor

MyOptions.cs、MyJsonConfig.json、Program.cs不需要改變。在OptionsController中改用IOptionsMonitor注入就可以了,代碼如下:

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class OptionsController : ControllerBase
    {
        private readonly MyOptions _options;

        public Options2Controller(IOptionsMonitor<MyOptions> options)
        {
            _options = options.Value;
        }

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

說明:

  • IOptionsMonitor 是一種單一示例服務,可隨時檢索當前選項值,這在單一實例依賴項中尤其有用。
  • IOptionsSnapshot 是一種作用域服務,並在構造 IOptionsSnapshot 對象時提供選項的快照。 選項快照旨在用於暫時性和有作用域的依賴項。

命名選項

命名選項:

  • 當多個配置節綁定到同一屬性時有用。
  • 區分大小寫。
    新建json配置文件
{
  "JsonConfig": {
    "Title": "MyTitle",
    "Name": "Tom"
  },
  "JsonConfig2": {
    "Title": "MyTitle2",
    "Name": "Jerry"
  }
}

在屬性相同的情況下,不需要創建兩個類,我們增加一個字段就可以了。

    public class MyOptions
    {
        public const string JsonConfig = "JsonConfig";

        public const string JsonConfig2 = "JsonConfig2";

        public string? Title { get; set; }

        public string? Name { get; set; }
    }

Program.cs中配置命名選項。

using OptionsTest3.Model;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Configuration.AddJsonFile("MyJsonConfig.json", optional: true, reloadOnChange: true);

//注入綁定
builder.Services.Configure<MyOptions>(MyOptions.JsonConfig,builder.Configuration.GetSection(MyOptions.JsonConfig));
builder.Services.Configure<MyOptions>(MyOptions.JsonConfig2, builder.Configuration.GetSection(MyOptions.JsonConfig2));
var app = builder.Build();


// Configure the HTTP request pipeline.

app.UseAuthorization();

app.MapControllers();

app.Run();

在Controller中讀取配置。

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class Options3Controller : ControllerBase
    {
        private readonly MyOptions _options;

        private readonly MyOptions _options2;

        public Options3Controller(IOptionsSnapshot<MyOptions> options)
        {
            _options = options.Get(MyOptions.JsonConfig);

            _options2 = options.Get(MyOptions.JsonConfig2);
        }

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

選項驗證

在配置文件中對於配置項的驗證是必不可少的。這樣可以保證程序配置的正確性。具體如何實現呢,且聽筆者慢慢道來。
首先創建一個簡單的Json配置文件。

{
  "JsonConfig": {
    "ID": 10000000,
    "Title": "MyTitle",
    "Name": "Tom"
  }
}

創建我們配置項的驗證Model。

    public class MyOptions
    {
        public const string JsonConfig = "JsonConfig";

        [Range(0, 1000,
        ErrorMessage = "值 {0}必須在 {1} 和 {2}之間。")]
        public int ID { get; set; }

        [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
        public string? Title { get; set; }

        public string? Name { get; set; }
    }

在Program.cs中配置我們的選項和驗證。

using OptionValidtate.Model;
using System.Configuration;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Configuration.AddJsonFile("MyJsonConfig.json", optional: true, reloadOnChange: true);
builder.Services.AddOptions<MyOptions>().Bind(builder.Configuration.GetSection(MyOptions.JsonConfig)).ValidateDataAnnotations();


var app = builder.Build();

// Configure the HTTP request pipeline.

app.UseAuthorization();

app.MapControllers();

app.Run();

說明:注意到和之前的代碼有什么不同了嗎?在我們正是通過調用ValidateDataAnnotations 以使用 DataAnnotations 來完成驗證。
在我們的Controller中讀取我們的配置。這里用到了ILogger,可以先不用關心,在后續筆者會詳細講解。

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class OptionsController : ControllerBase
    {
        private readonly ILogger<OptionsController> _logger;
        private readonly IOptions<MyOptions> _options;

        public OptionsController(IOptions<MyOptions> options, ILogger<OptionsController> logger)
        {
            _options = options;
            _logger = logger;
            try
            {
               var optionsValue = _options.Value;
            }
            catch (OptionsValidationException e)
            {
                foreach (var failure in e.Failures)
                {
                    _logger.LogError(failure);
                }
            }
        }

        public ContentResult GetOptions()
        {
            string msg;

            try
            {
                msg=$"ID:{_options.Value.ID}\nTitle:{_options.Value.Title}\nName:{_options.Value.Name}\n";
            }
            catch (OptionsValidationException e)
            {
                return Content(e.Message);

            }

            return Content(msg);
        }
    }

還記得咱們配置的ID驗證規則嗎?啟動程序看下我們的驗證已經生效了。
image

IValidateOptions復雜驗證的實現

如果上面的驗證還不滿足要求的話,沒關系我對上面的代碼略作修改,通過IValidateOptions就可以實現更加的復雜驗證。
新建MyValidateOptions類繼承IValidateOptions接口並實現。

    public class MyValidateOptions : IValidateOptions<MyOptions>
    {
          public MyOptions _options { get; private set; }

        public MyValidateOptions(IConfiguration config)
        {
            _options = config.GetSection(MyOptions.JsonConfig).Get<MyOptions>();
        }
        public ValidateOptionsResult Validate(string name, MyOptions options)
        {
            string msg = null;
            var rxValidate = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
            if (options.ID<0||options.ID>1000)
            {
                msg = $"{options.ID}必須在0-1000之間。";
            }
            if (string.IsNullOrEmpty(options.Title))
            {
                msg = $"{options.Title}必須符合正則要求。";
            }
            if (msg !=null)
            {
                return ValidateOptionsResult.Fail(msg);
            }

            return ValidateOptionsResult.Success;
        }
    }

在Program.cs中配置我們的選項和驗證。

using Microsoft.Extensions.Options;
using OptionValidtate.Model;
using System.Configuration;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Configuration.AddJsonFile("MyJsonConfig.json", optional: true, reloadOnChange: true);
builder.Services.AddOptions<MyOptions>().Bind(builder.Configuration.GetSection(MyOptions.JsonConfig));
builder.Services.AddSingleton<IValidateOptions<MyOptions>,MyValidateOptions>();

var app = builder.Build();

// Configure the HTTP request pipeline.

app.UseAuthorization();

app.MapControllers();

app.Run();

好了配置完畢,運行程序可以看到我們配置的驗證已經生效了。

image


免責聲明!

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



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