ASP.NET Core 2.2 基礎知識(七)【選項】
選項模式使用類來表示相關設定的組。 當配置設定
由方案隔離到單獨的類時,應用遵循兩個重要軟體工程原則:
介面分離原則 (ISP) 或封裝
– 依賴於配置設定的方案(類)僅依賴於其使用的配置設定。關注點分離
– 應用的不同部件的設定不彼此依賴或相互耦合。
選項還提供驗證配置資料的機制。 有關詳細資訊,請參閱選項驗證
部分。
系統必備
引用 Microsoft.AspNetCore.App 元包
或將包引用新增到 Microsoft.Extensions.Options.ConfigurationExtensions 包。
選項介面
IOptionsMonitor<TOptions>
用於檢索選項並管理 TOptions
例項的選項通知。
IOptionsMonitor<TOptions>
支援以下方案:
- 更改通知
命名選項
可過載配置
- 選擇性選項失效 (
IOptionsMonitorCache<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>
介面的現有框架和庫中繼續使用 IOptions<TOptions>
,並且不需要 IOptionsMonitor<TOptions>
提供的方案。
常規選項配置
常規選項配置已作為示例 #1
在示例應用中進行了演示。
選項類必須為包含公共無引數建構函式的非抽象類。 以下類 MyOptions
具有兩種屬性:Option1
和 Option2
。 設定預設值為可選,但以下示例中的類建構函式設定了 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
檔案指定 option1
和 option2
的值:
{
"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);
通過 xref:Microsoft.AspNetCore.WebHost.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>
服務新增到服務容器。 在前面的示例中,Option1
和 Option2
的值同時在 appsettings.json 中指定,但 Option1
和 Option2
的值被配置的委託替代。
當啟用多個配置服務時,指定的最後一個配置源優於其他源,由其設定配置值。 執行應用時,頁面模型的 OnGet
方法返回顯示選項類值的字串:
delegate_option1 = value1_configured_by_delgate, delegate_option2 = 500
子選項配置
子選項配置已作為示例 #3
在示例應用中進行了演示。
應用應建立適用於應用中特定方案組(類)的選項類。 需要配置值的部分應用應僅有權訪問其使用的配置值。
將選項繫結到配置時,選項型別中的每個屬性都將繫結到窗體 property[:sub-property:]
的配置鍵。 例如,MyOptions.Option1
屬性將繫結到從 appsettings.json 中的 option1
屬性讀取的鍵 Option1
。
在以下程式碼中,已向服務容器新增第三個 IConfigureOptions<TOptions>
服務。 它將 MySubOptions
繫結到 appsettings.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.Options.ConfigurationExtensions NuGet 包。 如果應用使用 Microsoft.AspNetCore.App metapackage 元包
(ASP.NET Core 2.1 或更高版本),將自動包含此包。
示例的 appsettings.json 檔案定義具有 suboption1
和 suboption2
的鍵的 subsection
成員:
{
"option1": "value1_from_json",
"option2": -1,
"subsection": {
"suboption1": "subvalue1_from_json",
"suboption2": 200
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
MySubOptions
類將屬性 SubOption1
和 SubOption2
定義為保留選項值 (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>
當應用執行時,選項值顯示在呈現的頁面中:
通過 IOptionsSnapshot
重新載入配置資料
通過 IOptionsSnapshot<TOptions>
重新載入配置資料已作為示例 #5
在示例應用中進行了演示。
IOptionsSnapshot<TOptions>
支援包含最小處理開銷的重新載入選項。
針對請求生存期訪問和快取選項時,每個請求只能計算一次選項。
以下示例演示如何在更改 appsettings.json (Pages/Index.cshtml.cs) 後建立新的 IOptionsSnapshot<TOptions>
。 在更改檔案和重新載入配置之前,針對伺服器的多個請求返回 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 檔案載入的初始 option1
和 option2
值:
snapshot option1 = value1_from_json, snapshot option2 = -1
將 appsettings.json 檔案中的值更改為 value1_from_json UPDATED
和 200
。 儲存 appsettings.json 檔案。 重新整理瀏覽器,檢視更新的選項值:
snapshot option1 = value1_from_json UPDATED, snapshot option2 = 200
包含 IConfigureNamedOptions
的命名選項支援
包含 IConfigureNamedOptions<TOptions>
的命名選項支援已作為示例 #6
在示例應用中進行了演示。
命名選項支援允許應用在命名選項配置之間進行區分。 在示例應用中,命名選項通過 OptionsServiceCollectionExtensions.Configure
進行宣告,其呼叫擴充套件方法 ConfigureNamedOptions<TOptions>.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
從配置中提供從 appsettings.json 檔案中載入的 named_options_1
值。 通過以下內容提供 named_options_2
值:
- 針對
Option1
的ConfigureServices
中的named_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.Empty
的Options.DefaultName
例項。IConfigureNamedOptions<TOptions>
還可實現IConfigureOptions<TOptions>
。IOptionsFactory<TOptions>
的預設實現具有適當地使用每個例項的邏輯。null
命名選項用於面向所有命名例項而不是某一特定命名例項(ConfigureAll
和PostConfigureAll
使用此約定)。
OptionsBuilder API
OptionsBuilder<TOptions>
用於配置 TOptions
例項。 OptionsBuilder
簡化了建立命名選項的過程,因為它只是初始 AddOptions<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");
配置 <TOptions、TDep1、...TDep4>
方法
使用 DI
中的服務,通過以樣本方式實現 IConfigure[Named]Options
來配置選項的方法非常詳細。 OptionsBuilder<TOptions>
上 ConfigureOptions
的過載允許使用最多五個服務來配置選項:
services.AddOptions<MyOptions>("optionalName")
.Configure<Service1, Service2, Service3, Service4, Service5>(
(o, s, s2, s3, s4, s5) =>
o.Property = DoSomethingWith(s, s2, s3, s4, s5));
過載會註冊臨時通用 IConfigureNamedOptions<TOptions>
,它具有接受指定的通用服務型別的建構函式。
選項驗證
藉助選項驗證,可以驗證已配置的選項。 使用驗證方法呼叫 Validate
。如果選項有效,方法返回 true
;如果無效,方法返回 false
:
// 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
。
選項驗證在選項例項建立後執行。 系統保證在選項例項首次獲得訪問時通過驗證。
重要
選項驗證無法防止在最初配置和驗證選項後發生選項修改。
Validate
方法接受 Func<TOptions, bool>
。 若要完全自定義驗證,請實現 IValidateOptions<TOptions>
,它支援:
- 驗證多種型別的選項:
class ValidateTwo : IValidateOptions<Option1>, IValidationOptions<Option2>
- 驗證取決於其他選項型別:
public DependsOnAnotherOptionValidator(IOptionsMonitor<AnotherOption> options)
IValidateOptions
驗證:
- 特定的命名選項例項。
- 所有選項(如果
name
為null
的話)。
通過介面實現返回 ValidateOptionsResult
:
public interface IValidateOptions<TOptions> where TOptions : class
{
ValidateOptionsResult Validate(string name, TOptions options);
}
通過呼叫 OptionsBuilder<TOptions>
上的 ValidateDataAnnotations
方法,可以從 Microsoft.Extensions.Options.DataAnnotations 包中獲得基於資料註釋的驗證。Microsoft.AspNetCore.App 元包
(ASP.NET Core 2.2 或更高版本)中包括 Microsoft.Extensions.Options.DataAnnotations
。
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>>().Value);
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";
});
在啟動期間訪問選項
IOptions<TOptions>
和 IOptionsMonitor<TOptions>
可用於 Startup.Configure
中,因為在 Configure
方法執行之前已生成服務。
public void Configure(IApplicationBuilder app, IOptionsMonitor<MyOptions> optionsAccessor)
{
var option1 = optionsAccessor.CurrentValue.Option1;
}
不使用 Startup.ConfigureServices
中的 IOptions<TOptions>
或 IOptionsMonitor<TOptions>
。 由於服務註冊的順序,可能存在不一致的選項狀態。
相關文章
- ASP.NET Core 2.2 基礎知識(十)【中介軟體】ASP.NET
- ASP.NET Core 2.2 基礎知識(十三)【伺服器】ASP.NET伺服器
- ASP.NET Core 2.2 基礎知識(六)【Configuration】ASP.NET
- ASP.NET Core 2.2 基礎知識(八)【日誌記錄】ASP.NET
- ASP.NET Core 2.2 基礎知識(九)【處理錯誤】ASP.NET
- ASP.NET Core基礎知識(四)【路由】ASP.NET路由
- ASP.NET Core基礎知識(一)【概述】ASP.NET
- ASP.NET Core基礎知識(二)【應用啟動】ASP.NET
- Java基礎知識七——方法Java
- JavaSE基礎知識分享(七)Java
- ASP.NET Core基礎知識(十一)【Host之Web 主機】ASP.NETWeb
- ASP.NET Core基礎知識(十四)【發出 HTTP 請求】ASP.NETHTTP
- ASP.NET Core 基礎知識(十二)【Host之通用主機】ASP.NET
- ASP.NET Core基礎知識(三)【依賴關係注入(服務)】ASP.NET
- ASP.NET程式安全的基礎知識ASP.NET
- ASP.NET Web Pages基礎知識---Razor 例項:顯示圖片ASP.NETWeb
- ASP.NET Core基礎知識(五)【環境(開發、分階段、生產)】ASP.NET
- ASP.NET core 2.2 截圖ASP.NET
- 理解ASP.NET Core - 選項(Options)ASP.NET
- Python基礎知識七 元組&字典&集合Python
- ASP.NET Core - 選項系統之選項驗證ASP.NET
- Python入門基礎知識例項,Python
- 基礎知識1——例項程式結構
- 小范筆記:ASP.NET Core API 基礎知識與Axios前端提交資料筆記ASP.NETAPIiOS前端
- Java基礎知識回顧之七 —– 總結篇Java
- Java基礎知識回顧之七 ----- 總結篇Java
- 基礎知識
- ASP.NET Core Authentication系列(三)Cookie選項ASP.NETCookie
- 一文了解.Net Core 3.1 Web API基礎知識WebAPI
- 第七章——程式設計語言基礎知識程式設計
- 遊戲基礎知識——“選擇”的設計方式遊戲
- AI 基礎知識AI
- Webpack 基礎知識Web
- Dart基礎知識Dart
- RabbitMQ基礎知識MQ
- webpack基礎知識Web
- javascript基礎知識JavaScript
- ThinkPHP基礎知識PHP