{"id":1156,"date":"2025-05-27T14:48:04","date_gmt":"2025-05-27T06:48:04","guid":{"rendered":"https:\/\/www.hyy.net\/?p=1156"},"modified":"2025-05-27T14:48:04","modified_gmt":"2025-05-27T06:48:04","slug":"ultimate-asp-net-core-web-api-29-binding-configuration-and-options-pattern","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=1156","title":{"rendered":"Ultimate ASP.NET Core Web API 29 BINDING CONFIGURATION AND OPTIONS PATTERN"},"content":{"rendered":"<p>29 BINDING CONFIGURATION AND OPTIONS PATTERN<br \/>\n29 \u7ed1\u5b9a\u914d\u7f6e\u548c\u9009\u9879\u6a21\u5f0f<\/p>\n<p>In the previous chapter, we had to use our appsettings file to store some important values for our JWT configuration and read those values from it:\u200c<br \/>\n\u5728\u4e0a\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5fc5\u987b\u4f7f\u7528 appsettings \u6587\u4ef6\u6765\u5b58\u50a8 JWT \u914d\u7f6e\u7684\u4e00\u4e9b\u91cd\u8981\u503c\uff0c\u5e76\u4ece\u4e2d\u8bfb\u53d6\u8fd9\u4e9b\u503c\uff1a<\/p>\n<pre><code>&quot;JwtSettings&quot;: { &quot;validIssuer&quot;: &quot;CodeMazeAPI&quot;, &quot;validAudience&quot;: &quot;https:\/\/localhost:5001&quot;, &quot;expires&quot;: 5 },<\/code><\/pre>\n<p>To access these values, we\u2019ve used the GetSection method from the IConfiguration interface:<br \/>\n\u4e3a\u4e86\u8bbf\u95ee\u8fd9\u4e9b\u503c\uff0c\u6211\u4eec\u4f7f\u7528\u4e86 IConfiguration \u63a5\u53e3\u4e2d\u7684 GetSection \u65b9\u6cd5\uff1a<\/p>\n<pre><code>var jwtSettings = configuration.GetSection(&quot;JwtSettings&quot;);<\/code><\/pre>\n<p>The GetSection method gets a sub-section from the appsettings file based on the provided key.<br \/>\nGetSection \u65b9\u6cd5\u6839\u636e\u63d0\u4f9b\u7684\u952e\u4ece appsettings \u6587\u4ef6\u4e2d\u83b7\u53d6\u5b50\u90e8\u5206\u3002<\/p>\n<p>Once we extracted the sub-section, we\u2019ve accessed the specific values by using the jwtSettings variable of type IConfigurationSection, with the key provided inside the square brackets:<br \/>\n\u63d0\u53d6\u5b50\u90e8\u5206\u540e\uff0c\u6211\u4eec\u4f7f\u7528 IConfigurationSection \u7c7b\u578b\u7684 jwtSettings \u53d8\u91cf\u8bbf\u95ee\u4e86\u7279\u5b9a\u503c\uff0c\u5176\u4e2d\u952e\u5728\u65b9\u62ec\u53f7\u5185\u63d0\u4f9b\uff1a<\/p>\n<pre><code>ValidIssuer = jwtSettings[&quot;validIssuer&quot;],<\/code><\/pre>\n<p>This works great but it does have its flaws.<br \/>\n\u8fd9\u6548\u679c\u5f88\u597d\uff0c\u4f46\u5b83\u4e5f\u6709\u5176\u7f3a\u9677\u3002<\/p>\n<p>Having to type sections and keys to get the values can be repetitive and error-prone. We risk introducing errors to our code, and these kinds of errors can cost us a lot of time until we discover them since someone else can introduce them, and we won\u2019t notice them since a null result is returned when values are missing.<br \/>\n\u5fc5\u987b\u952e\u5165 sections \u548c keys \u624d\u80fd\u83b7\u53d6\u503c\u53ef\u80fd\u662f\u91cd\u590d\u4e14\u5bb9\u6613\u51fa\u9519\u7684\u3002\u6211\u4eec\u5192\u7740\u5c06\u9519\u8bef\u5f15\u5165\u4ee3\u7801\u7684\u98ce\u9669\uff0c\u8fd9\u4e9b\u7c7b\u578b\u7684\u9519\u8bef\u53ef\u80fd\u4f1a\u82b1\u8d39\u6211\u4eec\u5927\u91cf\u65f6\u95f4\uff0c\u76f4\u5230\u6211\u4eec\u53d1\u73b0\u5b83\u4eec\uff0c\u56e0\u4e3a\u5176\u4ed6\u4eba\u53ef\u80fd\u4f1a\u5f15\u5165\u5b83\u4eec\uff0c\u800c\u4e14\u6211\u4eec\u4e0d\u4f1a\u6ce8\u610f\u5230\u5b83\u4eec\uff0c\u56e0\u4e3a\u5f53\u503c\u7f3a\u5931\u65f6\u4f1a\u8fd4\u56de null \u7ed3\u679c\u3002<\/p>\n<p>To overcome this problem, we can bind the configuration data to strongly typed objects. To do that, we can use the Bind method.<br \/>\n\u4e3a\u4e86\u89e3\u51b3\u8fd9\u4e2a\u95ee\u9898\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u914d\u7f6e\u6570\u636e\u7ed1\u5b9a\u5230\u5f3a\u7c7b\u578b\u5bf9\u8c61\u3002\u4e3a\u6b64\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 Bind \u65b9\u6cd5\u3002<\/p>\n<h2>\u300029.1 Binding Configuration<\/h2>\n<p>29.1 \u7ed1\u5b9a\u914d\u7f6e<\/p>\n<p>To start with the binding process, we are going to create a new ConfigurationModels folder inside the Entities project, and a new JwtConfiguration class inside that folder:\u200c<br \/>\n\u8981\u5f00\u59cb\u7ed1\u5b9a\u8fc7\u7a0b\uff0c\u6211\u4eec\u5c06\u5728 Entities \u9879\u76ee\u4e2d\u521b\u5efa\u4e00\u4e2a\u65b0\u7684 ConfigurationModels \u6587\u4ef6\u5939\uff0c\u5e76\u5728\u8be5\u6587\u4ef6\u5939\u4e2d\u521b\u5efa\u4e00\u4e2a\u65b0\u7684 JwtConfiguration \u7c7b\uff1a<\/p>\n<pre><code>public class JwtConfiguration { public string Section { get; set; } = &quot;JwtSettings&quot;; public string? ValidIssuer { get; set; } public string? ValidAudience { get; set; } public string? Expires { get; set; } }<\/code><\/pre>\n<p>Then in the ServiceExtensions class, we are going to modify the ConfigureJWT method:<br \/>\n\u7136\u540e\u5728 ServiceExtensions \u7c7b\u4e2d\uff0c\u6211\u4eec\u5c06\u4fee\u6539 ConfigureJWT \u65b9\u6cd5\uff1a<\/p>\n<pre><code>public static void ConfigureJWT(this IServiceCollection services, IConfiguration configuration) { var jwtConfiguration = new JwtConfiguration(); configuration.Bind(jwtConfiguration.Section, jwtConfiguration); var secretKey = Environment.GetEnvironmentVariable(&quot;SECRET&quot;); services.AddAuthentication(opt =&gt; { opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options =&gt; { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = jwtConfiguration.ValidIssuer, ValidAudience = jwtConfiguration.ValidAudience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)) }; }); }<\/code><\/pre>\n<p>We create a new instance of the JwtConfiguration class and use the Bind method that accepts the section name and the instance object as parameters, to bind to the JwtSettings section directly and map configuration values to respective properties inside the JwtConfiguration class. Then, we just use those properties instead of string keys inside square brackets, to access required values.<br \/>\n\u6211\u4eec\u521b\u5efa JwtConfiguration \u7c7b\u7684\u65b0\u5b9e\u4f8b\uff0c\u5e76\u4f7f\u7528\u63a5\u53d7\u8282\u540d\u79f0\u548c\u5b9e\u4f8b\u5bf9\u8c61\u4f5c\u4e3a\u53c2\u6570\u7684 Bind \u65b9\u6cd5\uff0c\u76f4\u63a5\u7ed1\u5b9a\u5230 JwtSettings \u8282\uff0c\u5e76\u5c06\u914d\u7f6e\u503c\u6620\u5c04\u5230 JwtConfiguration \u7c7b\u4e2d\u7684\u76f8\u5e94\u5c5e\u6027\u3002\u7136\u540e\uff0c\u6211\u4eec\u53ea\u4f7f\u7528\u8fd9\u4e9b\u5c5e\u6027\u800c\u4e0d\u662f\u65b9\u62ec\u53f7\u5185\u7684\u5b57\u7b26\u4e32\u952e\u6765\u8bbf\u95ee\u6240\u9700\u7684\u503c\u3002<\/p>\n<p>There are two things to note here though. The first is that the names of the configuration data keys and class properties must match. The other is that if you extend the configuration, you need to extend the class as well, which can be a bit cumbersome, but it beats getting values by typing strings.<br \/>\n\u4e0d\u8fc7\uff0c\u8fd9\u91cc\u6709\u4e24\u4ef6\u4e8b\u9700\u8981\u6ce8\u610f\u3002\u9996\u5148\u662f\u914d\u7f6e\u6570\u636e\u952e\u548c\u7c7b\u5c5e\u6027\u7684\u540d\u79f0\u5fc5\u987b\u5339\u914d\u3002\u53e6\u4e00\u4e2a\u662f\uff0c\u5982\u679c\u4f60\u6269\u5c55\u914d\u7f6e\uff0c\u4f60\u4e5f\u9700\u8981\u6269\u5c55 class\uff0c\u8fd9\u53ef\u80fd\u6709\u70b9\u9ebb\u70e6\uff0c\u4f46\u5b83\u6bd4\u901a\u8fc7\u952e\u5165\u5b57\u7b26\u4e32\u6765\u83b7\u53d6\u503c\u8981\u597d\u3002<\/p>\n<p>Now, we can continue with the AuthenticationService class modification since we extract configuration values in two methods from this class:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u7ee7\u7eed\u4fee\u6539 AuthenticationService \u7c7b\uff0c\u56e0\u4e3a\u6211\u4eec\u4ece\u8fd9\u4e2a\u7c7b\u4e2d\u63d0\u53d6\u4e86\u4e24\u4e2a\u65b9\u6cd5\u7684\u914d\u7f6e\u503c\uff1a<\/p>\n<pre><code>... private readonly JwtConfiguration _jwtConfiguration; private User? _user; public AuthenticationService(ILoggerManager logger, IMapper mapper, UserManager&lt;User&gt; userManager, IConfiguration configuration) { _logger = logger; _mapper = mapper; _userManager = userManager; _configuration = configuration; _jwtConfiguration = new JwtConfiguration(); _configuration.Bind(_jwtConfiguration.Section, _jwtConfiguration); }<\/code><\/pre>\n<p>So, we add a readonly variable, and create an instance and execute binding inside the constructor.<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u6dfb\u52a0\u4e00\u4e2a readonly \u53d8\u91cf\uff0c\u5e76\u5728\u6784\u9020\u51fd\u6570\u4e2d\u521b\u5efa\u4e00\u4e2a\u5b9e\u4f8b\u5e76\u6267\u884c\u7ed1\u5b9a\u3002<\/p>\n<p>And since we\u2019re using the Bind() method we need to install the Microsoft.Extensions.Configuration.Binder NuGet package.<br \/>\n\u7531\u4e8e\u6211\u4eec\u4f7f\u7528\u7684\u662f Bind\uff08\uff09 \u65b9\u6cd5\uff0c\u56e0\u6b64\u9700\u8981\u5b89\u88c5 Microsoft.Extensions.Configuration.Binder NuGet \u5305\u3002<\/p>\n<p>After that, we can modify the GetPrincipalFromExpiredToken method by removing the GetSection part and modifying the TokenValidationParameters object creation:<br \/>\n\u4e4b\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u5220\u9664 GetSection \u90e8\u5206\u5e76\u4fee\u6539 TokenValidationParameters \u5bf9\u8c61\u521b\u5efa\u6765\u4fee\u6539 GetPrincipalFromExpiredToken \u65b9\u6cd5\uff1a<\/p>\n<pre><code>private ClaimsPrincipal GetPrincipalFromExpiredToken(string token) { var tokenValidationParameters = new TokenValidationParameters { ValidateAudience = true, ValidateIssuer = true,ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable(&quot;SECRET&quot;))), ValidateLifetime = true, ValidIssuer = _jwtConfiguration.ValidIssuer, ValidAudience = _jwtConfiguration.ValidAudience }; ... return principal; }<\/code><\/pre>\n<p>And let\u2019s do a similar thing for the GenerateTokenOptions method:<br \/>\n\u8ba9\u6211\u4eec\u5bf9 GenerateTokenOptions \u65b9\u6cd5\u6267\u884c\u7c7b\u4f3c\u7684\u4f5c\uff1a<\/p>\n<pre><code>private JwtSecurityToken GenerateTokenOptions(SigningCredentials signingCredentials, List&lt;Claim&gt; claims) { var tokenOptions = new JwtSecurityToken ( issuer: _jwtConfiguration.ValidIssuer, audience: _jwtConfiguration.ValidAudience, claims: claims, expires: DateTime.Now.AddMinutes(Convert.ToDouble(_jwtConfiguration.Expires)), signingCredentials: signingCredentials ); return tokenOptions; }<\/code><\/pre>\n<p>Excellent.At this point, we can start our application and use both requests from Postman\u2019s collection - 28-Refresh Token - to test our configuration.<br \/>\n\u975e\u5e38\u597d\u3002\u6b64\u65f6\uff0c\u6211\u4eec\u53ef\u4ee5\u542f\u52a8\u5e94\u7528\u7a0b\u5e8f\u5e76\u4f7f\u7528\u6765\u81ea Postman \u96c6\u5408\u7684\u4e24\u4e2a\u8bf7\u6c42 - 28-Refresh Token - \u6765\u6d4b\u8bd5\u6211\u4eec\u7684\u914d\u7f6e\u3002<\/p>\n<p>We should get the same responses as we did in a previous chapter, which proves that our configuration works as intended but now with a better code and less error-prone.<br \/>\n\u6211\u4eec\u5e94\u8be5\u5f97\u5230\u4e0e\u4e0a\u4e00\u7ae0\u76f8\u540c\u7684\u54cd\u5e94\uff0c\u8fd9\u8bc1\u660e\u6211\u4eec\u7684\u914d\u7f6e\u6309\u9884\u671f\u5de5\u4f5c\uff0c\u4f46\u73b0\u5728\u4ee3\u7801\u66f4\u597d\uff0c\u66f4\u4e0d\u5bb9\u6613\u51fa\u9519\u3002<\/p>\n<h2>29.2 Options Pattern<\/h2>\n<p>29.2 \u9009\u9879\u6a21\u5f0f<\/p>\n<p>In the previous section, we\u2019ve seen how we can bind configuration data to strongly typed objects. The options pattern gives us similar possibilities, but it offers a more structured approach and more features like validation, live reloading, and easier testing.\u200c<br \/>\n\u5728\u4e0a\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5df2\u7ecf\u770b\u5230\u4e86\u5982\u4f55\u5c06\u914d\u7f6e\u6570\u636e\u7ed1\u5b9a\u5230\u5f3a\u7c7b\u578b\u5bf9\u8c61\u3002options \u6a21\u5f0f\u4e3a\u6211\u4eec\u63d0\u4f9b\u4e86\u7c7b\u4f3c\u7684\u53ef\u80fd\u6027\uff0c\u4f46\u5b83\u63d0\u4f9b\u4e86\u4e00\u79cd\u66f4\u7ed3\u6784\u5316\u7684\u65b9\u6cd5\u548c\u66f4\u591a\u529f\u80fd\uff0c\u5982\u9a8c\u8bc1\u3001\u5b9e\u65f6\u91cd\u65b0\u52a0\u8f7d\u548c\u66f4\u8f7b\u677e\u7684\u6d4b\u8bd5\u3002<\/p>\n<p>Once we configure the class containing our configuration we can inject it via dependency injection with IOptions<T> and thus injecting only part of our configuration or rather only the part that we need.<br \/>\n\u4e00\u65e6\u6211\u4eec\u914d\u7f6e\u4e86\u5305\u542b\u6211\u4eec\u7684\u914d\u7f6e\u7684\u7c7b\uff0c\u6211\u4eec\u5c31\u53ef\u4ee5\u901a\u8fc7\u4f7f\u7528 IOptions \u7684\u4f9d\u8d56\u6ce8\u5165\u6765\u6ce8\u5165\u5b83\uff0c\u4ece\u800c\u53ea\u6ce8\u5165\u6211\u4eec\u914d\u7f6e\u7684\u4e00\u90e8\u5206\uff0c\u6216\u8005\u66f4\u786e\u5207\u5730\u8bf4\uff0c\u53ea\u6ce8\u5165\u6211\u4eec\u9700\u8981\u7684\u90e8\u5206\u3002<\/p>\n<p>If we need to reload the configuration without stopping the application, we can use the <code>IOptionsSnapshot&lt;T&gt;<\/code> interface or the <code>IOptionsMonitor&lt;T&gt;<\/code> interface depending on the situation. We\u2019ll see when these interfaces should be used and why.<br \/>\n\u5982\u679c\u6211\u4eec\u9700\u8981\u5728\u4e0d\u505c\u6b62\u5e94\u7528\u7a0b\u5e8f\u7684\u60c5\u51b5\u4e0b\u91cd\u65b0\u52a0\u8f7d\u914d\u7f6e\uff0c\u6211\u4eec\u53ef\u4ee5\u6839\u636e\u60c5\u51b5\u4f7f\u7528  <code>IOptionsSnapshot&lt;T&gt;<\/code> \u63a5\u53e3\u6216 <code>IOptionsMonitor&lt;T&gt;<\/code>\u63a5\u53e3\u3002\u6211\u4eec\u5c06\u4e86\u89e3\u4f55\u65f6\u5e94\u8be5\u4f7f\u7528\u8fd9\u4e9b\u63a5\u53e3\u4ee5\u53ca\u4e3a\u4ec0\u4e48\u3002<\/p>\n<p>The options pattern also provides a good validation mechanism that uses the widely used DataAnotations attributes to check if the configuration abides by the logical rules of our application.<br \/>\n\u9009\u9879\u6a21\u5f0f\u8fd8\u63d0\u4f9b\u4e86\u4e00\u79cd\u5f88\u597d\u7684\u9a8c\u8bc1\u673a\u5236\uff0c\u8be5\u673a\u5236\u4f7f\u7528\u5e7f\u6cdb\u4f7f\u7528\u7684 DataAnotations \u5c5e\u6027\u6765\u68c0\u67e5\u914d\u7f6e\u662f\u5426\u7b26\u5408\u5e94\u7528\u7a0b\u5e8f\u7684\u903b\u8f91\u89c4\u5219\u3002<\/p>\n<p>The testing of options is also easy because of the helper methods and easy to mock options classes.<br \/>\n\u7531\u4e8e\u6709\u8f85\u52a9\u65b9\u6cd5\u548c\u6613\u4e8e\u6a21\u62df\u7684\u9009\u9879\u7c7b\uff0c\u9009\u9879\u7684\u6d4b\u8bd5\u4e5f\u5f88\u5bb9\u6613\u3002<\/p>\n<h2>\u300029.2.1 Using IOptions\u200c<\/h2>\n<p>29.2.1 \u4f7f\u7528 IOptions<\/p>\n<p>We have already written a lot of code in the previous section that can be used with the IOptions interface, but we still have some more actions to do.<br \/>\n\u5728\u4e0a\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5df2\u7ecf\u7f16\u5199\u4e86\u5927\u91cf\u53ef\u4e0e IOptions \u63a5\u53e3\u4e00\u8d77\u4f7f\u7528\u7684\u4ee3\u7801\uff0c\u4f46\u6211\u4eec\u4ecd\u6709\u4e00\u4e9b\u4f5c\u8981\u6267\u884c\u3002<\/p>\n<p>The first thing we are going to do is to register and configure the JwtConfiguration class in the ServiceExtensions class:<br \/>\n\u6211\u4eec\u8981\u505a\u7684\u7b2c\u4e00\u4ef6\u4e8b\u662f\u5728 ServiceExtensions \u7c7b\u4e2d\u6ce8\u518c\u548c\u914d\u7f6e JwtConfiguration \u7c7b\uff1a<\/p>\n<pre><code>public static void AddJwtConfiguration(this IServiceCollection services, IConfiguration configuration) =&gt; services.Configure&lt;JwtConfiguration&gt;(configuration.GetSection(&quot;JwtSettings&quot;));<\/code><\/pre>\n<p>And call this method in the Program class:<br \/>\n\u5e76\u5728 Program \u7c7b\u4e2d\u8c03\u7528\u6b64\u65b9\u6cd5\uff1a<\/p>\n<pre><code>builder.Services.ConfigureJWT(builder.Configuration); builder.Services.AddJwtConfiguration(builder.Configuration);<\/code><\/pre>\n<p>Since we can use IOptions with DI, we are going to modify the ServiceManager class to support that:<br \/>\n\u7531\u4e8e\u6211\u4eec\u53ef\u4ee5\u5c06 IOptions \u4e0e DI \u4e00\u8d77\u4f7f\u7528\uff0c\u56e0\u6b64\u6211\u4eec\u5c06\u4fee\u6539 ServiceManager \u7c7b\u4ee5\u652f\u6301\u8fd9\u4e00\u70b9\uff1a<\/p>\n<pre><code>public ServiceManager(IRepositoryManager repositoryManager, ILoggerManager logger, IMapper mapper, IEmployeeLinks employeeLinks, UserManager&lt;User&gt; userManager, IOptions&lt;JwtConfiguration&gt; configuration)<\/code><\/pre>\n<p>We just replace the IConfiguration type with the IOptions type in the constructor.<br \/>\n\u6211\u4eec\u53ea\u662f\u5728\u6784\u9020\u51fd\u6570\u4e2d\u5c06 IConfiguration \u7c7b\u578b\u66ff\u6362\u4e3a IOptions \u7c7b\u578b\u3002<\/p>\n<p>For this, we need two additional namespaces:<br \/>\n\u4e3a\u6b64\uff0c\u6211\u4eec\u9700\u8981\u4e24\u4e2a\u989d\u5916\u7684\u547d\u540d\u7a7a\u95f4\uff1a<\/p>\n<pre><code>using Entities.ConfigurationModels; \nusing Microsoft.Extensions.Options;<\/code><\/pre>\n<p>Then, we can modify the AuthenticationService\u2019s constructor:<br \/>\n\u7136\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u4fee\u6539 AuthenticationService \u7684\u6784\u9020\u51fd\u6570\uff1a<\/p>\n<pre><code>private readonly ILoggerManager _logger; private readonly IMapper _mapper; private readonly UserManager&lt;User&gt; _userManager; private readonly IOptions&lt;JwtConfiguration&gt; _configuration; private readonly JwtConfiguration _jwtConfiguration; private User? _user; public AuthenticationService(ILoggerManager logger, IMapper mapper, UserManager&lt;User&gt; userManager, IOptions&lt;JwtConfiguration&gt; configuration) { _logger = logger; _mapper = mapper; _userManager = userManager; _configuration = configuration; _jwtConfiguration = _configuration.Value; }<\/code><\/pre>\n<p>And that\u2019s it.<br \/>\n\u5c31\u662f\u8fd9\u6837\u3002<\/p>\n<p>We inject IOptions inside the constructor and use the Value property to extract the JwtConfiguration object with all the populated properties. Nothing else has to change in this class.<br \/>\n\u6211\u4eec\u5728\u6784\u9020\u51fd\u6570\u4e2d\u6ce8\u5165 IOptions\uff0c\u5e76\u4f7f\u7528 Value \u5c5e\u6027\u63d0\u53d6\u5305\u542b\u6240\u6709\u586b\u5145\u5c5e\u6027\u7684 JwtConfiguration \u5bf9\u8c61\u3002\u8fd9\u4e2a\u7c7b\u4e2d\u6ca1\u6709\u5176\u4ed6\u4efb\u4f55\u4e1c\u897f\u9700\u8981\u6539\u53d8\u3002<\/p>\n<p>If we start the application again and send the same requests, we will still get valid results meaning that we\u2019ve successfully implemented IOptions in our project.<br \/>\n\u5982\u679c\u6211\u4eec\u518d\u6b21\u542f\u52a8\u5e94\u7528\u7a0b\u5e8f\u5e76\u53d1\u9001\u76f8\u540c\u7684\u8bf7\u6c42\uff0c\u6211\u4eec\u4ecd\u7136\u4f1a\u5f97\u5230\u6709\u6548\u7684\u7ed3\u679c\uff0c\u8fd9\u610f\u5473\u7740\u6211\u4eec\u5df2\u7ecf\u5728\u9879\u76ee\u4e2d\u6210\u529f\u5b9e\u73b0\u4e86 IOptions\u3002<\/p>\n<p>One more thing. We didn\u2019t modify anything inside the ServiceExtensions\/ConfigureJWT method. That\u2019s because this configuration happens during the service registration and not after services are built. This means that we can\u2019t resolve our required service here.<br \/>\n\u8fd8\u6709\u4e00\u4ef6\u4e8b\u3002\u6211\u4eec\u6ca1\u6709\u4fee\u6539 ServiceExtensions\/ConfigureJWT \u65b9\u6cd5\u4e2d\u7684\u4efb\u4f55\u5185\u5bb9\u3002\u8fd9\u662f\u56e0\u4e3a\u6b64\u914d\u7f6e\u53d1\u751f\u5728\u670d\u52a1\u6ce8\u518c\u671f\u95f4\uff0c\u800c\u4e0d\u662f\u5728\u6784\u5efa\u670d\u52a1\u4e4b\u540e\u3002\u8fd9\u610f\u5473\u7740\u6211\u4eec\u65e0\u6cd5\u5728\u6b64\u5904\u89e3\u51b3\u6240\u9700\u7684\u670d\u52a1\u3002<\/p>\n<p>Well, to be precise, we can use the BuildServiceProvider method to build a service provider containing all the services from the provided IServiceCollection, and thus being able to access the required service. But if you do that, you will create one more list of singleton services, which can be quite expensive depending on the size of your application. So, you should be careful with this method.<br \/>\n\u55ef\uff0c\u51c6\u786e\u5730\u8bf4\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 BuildServiceProvider \u65b9\u6cd5\u6784\u5efa\u4e00\u4e2a\u670d\u52a1\u63d0\u4f9b\u7a0b\u5e8f\uff0c\u5176\u4e2d\u5305\u542b\u6240\u63d0\u4f9b\u7684 IServiceCollection \u4e2d\u7684\u6240\u6709\u670d\u52a1\uff0c\u4ece\u800c\u80fd\u591f\u8bbf\u95ee\u6240\u9700\u7684\u670d\u52a1\u3002\u4f46\u662f\uff0c\u5982\u679c\u60a8\u8fd9\u6837\u505a\uff0c\u60a8\u5c06\u518d\u521b\u5efa\u4e00\u4e2a\u5355\u4f8b\u670d\u52a1\u5217\u8868\uff0c\u6839\u636e\u5e94\u7528\u7a0b\u5e8f\u7684\u5927\u5c0f\uff0c\u8fd9\u53ef\u80fd\u4f1a\u975e\u5e38\u6602\u8d35\u3002\u56e0\u6b64\uff0c\u60a8\u5e94\u8be5\u5c0f\u5fc3\u4f7f\u7528\u6b64\u65b9\u6cd5\u3002<\/p>\n<p>That said, using Binding to access configuration values is perfectly safe and cheap in this stage of the application\u2019s lifetime.<br \/>\n\u4e5f\u5c31\u662f\u8bf4\uff0c\u5728\u5e94\u7528\u7a0b\u5e8f\u751f\u547d\u5468\u671f\u7684\u8fd9\u4e2a\u9636\u6bb5\uff0c\u4f7f\u7528 Binding \u6765\u8bbf\u95ee\u914d\u7f6e\u503c\u662f\u5b8c\u5168\u5b89\u5168\u4e14\u6210\u672c\u4f4e\u5ec9\u7684\u3002<\/p>\n<h3>29.2.2 IOptionsSnapshot and IOptionsMonitor\u200c<\/h3>\n<p>The previous code looks great but if we want to change the value of Expires to 10 instead of 5 for example, we need to restart the application to do it. You can imagine how useful would be to have a published application and all you need to do is to modify the value in the configuration file without restarting the whole app.<br \/>\n\u524d\u9762\u7684\u4ee3\u7801\u770b\u8d77\u6765\u5f88\u68d2\uff0c\u4f46\u662f\u5982\u679c\u6211\u4eec\u60f3\u5c06 Expires \u7684\u503c\u66f4\u6539\u4e3a 10 \u800c\u4e0d\u662f 5\uff0c\u5219\u9700\u8981\u91cd\u65b0\u542f\u52a8\u5e94\u7528\u7a0b\u5e8f\u624d\u80fd\u6267\u884c\u6b64\u4f5c\u3002\u60a8\u53ef\u4ee5\u60f3\u8c61\u62e5\u6709\u4e00\u4e2a\u5df2\u53d1\u5e03\u7684\u5e94\u7528\u7a0b\u5e8f\u4f1a\u6709\u591a\u6709\u7528\uff0c\u60a8\u9700\u8981\u505a\u7684\u5c31\u662f\u4fee\u6539\u914d\u7f6e\u6587\u4ef6\u4e2d\u7684\u503c\uff0c\u800c\u65e0\u9700\u91cd\u65b0\u542f\u52a8\u6574\u4e2a\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>Well, there is a way to do it by using IOptionsSnapshot or IOptionsMonitor.<br \/>\n\u55ef\uff0c\u6709\u4e00\u79cd\u65b9\u6cd5\u53ef\u4ee5\u901a\u8fc7\u4f7f\u7528 IOptionsSnapshot \u6216 IOptionsMonitor \u6765\u5b9e\u73b0\u3002<\/p>\n<p>All we would have to do is to replace the <code>IOptions&lt;JwtConfiguration&gt;<\/code> type with the <code>IOptionsSnapshot&lt;JwtConfiguration&gt;<\/code> or <code>IOptionsMonitor&lt;JwtConfiguration&gt;<\/code> types inside the ServiceManager and AuthenticationService classes. Also if we use IOptionsMonitor, we can\u2019t use the Value property but the CurrentValue.<br \/>\n\u6211\u4eec\u6240\u8981\u505a\u7684\u5c31\u662f\u5c06 <code>IOptions&lt;JwtConfiguration&gt;<\/code> \u7c7b\u578b\u66ff\u6362\u4e3a ServiceManager \u548c AuthenticationService \u7c7b\u4e2d\u7684 <code>IOptionsSnapshot&lt;JwtConfiguration&gt;<\/code> \u548c <code>IOptionsMonitor&lt;JwtConfiguration&gt;<\/code> \u7c7b\u578b\u3002\u6b64\u5916\uff0c\u5982\u679c\u6211\u4eec\u4f7f\u7528 IOptionsMonitor\uff0c\u5219\u4e0d\u80fd\u4f7f\u7528 Value \u5c5e\u6027\uff0c\u800c\u53ea\u80fd\u4f7f\u7528 CurrentValue\u3002<\/p>\n<p>So the main difference between these two interfaces is that the IOptionsSnapshot service is registered as a scoped service and thus can\u2019t be injected inside the singleton service. On the other hand, IOptionsMonitor is registered as a singleton service and can be injected into any service lifetime.<br \/>\n\u56e0\u6b64\uff0c\u8fd9\u4e24\u4e2a\u63a5\u53e3\u4e4b\u95f4\u7684\u4e3b\u8981\u533a\u522b\u5728\u4e8e\uff0cIOptionsSnapshot \u670d\u52a1\u6ce8\u518c\u4e3a\u8303\u56f4\u670d\u52a1\uff0c\u56e0\u6b64\u4e0d\u80fd\u6ce8\u5165\u5230\u5355\u4e00\u5b9e\u4f8b\u670d\u52a1\u4e2d\u3002\u53e6\u4e00\u65b9\u9762\uff0cIOptionsMonitor \u6ce8\u518c\u4e3a\u5355\u4e00\u5b9e\u4f8b\u670d\u52a1\uff0c\u53ef\u4ee5\u6ce8\u5165\u5230\u4efb\u4f55\u670d\u52a1\u751f\u5b58\u671f\u4e2d\u3002<\/p>\n<p>To make the comparison even clearer, we have prepared the following list for you:<br \/>\n\u4e3a\u4e86\u4f7f\u6bd4\u8f83\u66f4\u52a0\u6e05\u6670\uff0c\u6211\u4eec\u4e3a\u60a8\u51c6\u5907\u4e86\u4ee5\u4e0b\u5217\u8868\uff1a<\/p>\n<p>IOptions<T>:<\/p>\n<p>\u2022 Is the original Options interface and it\u2019s better than binding the whole Configuration<br \/>\n\u662f\u539f\u59cb\u7684 Options \u63a5\u53e3\uff0c\u6bd4\u7ed1\u5b9a\u6574\u4e2a Configuration \u8981\u597d<\/p>\n<p>\u2022 Does not support configuration reloading<br \/>\n\u4e0d\u652f\u6301\u91cd\u65b0\u52a0\u8f7d\u914d\u7f6e<\/p>\n<p>\u2022 Is registered as a singleton service and can be injected anywhere<br \/>\n\u6ce8\u518c\u4e3a\u5355\u4f8b\u670d\u52a1\uff0c\u53ef\u4ee5\u5728\u4efb\u4f55\u5730\u65b9\u6ce8\u5165<\/p>\n<p>\u2022 Binds the configuration values only once at the registration, and returns the same values every time<br \/>\n\u6ce8\u518c\u65f6\u4ec5\u7ed1\u5b9a\u4e00\u6b21\u914d\u7f6e\u503c\uff0c\u5e76\u4e14\u6bcf\u6b21\u90fd\u8fd4\u56de\u76f8\u540c\u7684\u503c<\/p>\n<p>\u2022 Does not support named options<br \/>\n\u4e0d\u652f\u6301\u547d\u540d\u9009\u9879<\/p>\n<p>IOptionsSnapshot<T>:<\/p>\n<p>\u2022 Registered as a scoped service<br \/>\n\u6ce8\u518c\u4e3a\u8303\u56f4\u670d\u52a1<\/p>\n<p>\u2022 Supports configuration reloading<br \/>\n\u652f\u6301\u914d\u7f6e\u91cd\u65b0\u52a0\u8f7d<\/p>\n<p>\u2022 Cannot be injected into singleton services<br \/>\n\u4e0d\u80fd\u6ce8\u5165\u5230\u5355\u4f8b\u670d\u52a1<\/p>\n<p>\u2022 Values reload per request<br \/>\n\u6bcf\u4e2a\u8bf7\u6c42\u91cd\u65b0\u52a0\u8f7d\u503c<\/p>\n<p>\u2022 Supports named options<br \/>\n\u652f\u6301\u547d\u540d\u9009\u9879<\/p>\n<p>IOptionsMonitor<T>:<\/p>\n<p>\u2022 Registered as a singleton service<br \/>\n\u6ce8\u518c\u4e3a\u5355\u4f8b\u670d\u52a1<\/p>\n<p>\u2022 Supports configuration reloading<br \/>\n\u652f\u6301\u914d\u7f6e\u91cd\u65b0\u52a0\u8f7d<\/p>\n<p>\u2022 Can be injected into any service lifetime<br \/>\n\u53ef\u4ee5\u6ce8\u5165\u4efb\u4f55\u4f7f\u7528\u5bff\u547d<\/p>\n<p>\u2022 Values are cached and reloaded immediately<br \/>\n\u503c\u4f1a\u7acb\u5373\u7f13\u5b58\u5e76\u91cd\u65b0\u52a0\u8f7d<\/p>\n<p>\u2022 Supports named options<br \/>\n\u652f\u6301\u547d\u540d\u9009\u9879<\/p>\n<p>Having said that, we can see that if we don\u2019t want to enable live reloading or we don\u2019t need named options, we can simply use <code>IOptions&lt;T&gt;<\/code>. If we do, we can use either <code>IOptionsSnapshot&lt;T&gt;<\/code> or <code>IOptionsMonitor&lt;T&gt;<\/code>,but <code>IOptionsMonitor&lt;T&gt;<\/code> can be injected into other singleton services while <code>IOptionsSnapshot&lt;T&gt;<\/code> cannot.<br \/>\n\u8bdd\u867d\u5982\u6b64\uff0c\u6211\u4eec\u53ef\u4ee5\u770b\u5230\uff0c\u5982\u679c\u6211\u4eec\u4e0d\u60f3\u542f\u7528\u5b9e\u65f6\u91cd\u65b0\u52a0\u8f7d\u6216\u4e0d\u9700\u8981\u547d\u540d\u9009\u9879\uff0c\u6211\u4eec\u53ef\u4ee5\u7b80\u5355\u5730\u4f7f\u7528 <code>IOptions&lt;T&gt;<\/code>\u3002\u5982\u679c\u8fd9\u6837\u505a\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 <code>IOptionsSnapshot&lt;T&gt;<\/code> \u6216 <code>IOptionsMonitor&lt;T&gt;<\/code>\uff0c\u4f46 <code>IOptionsMonitor&lt;T&gt;<\/code> \u53ef\u4ee5\u6ce8\u5165\u5230\u5176\u4ed6\u5355\u4e00\u5b9e\u4f8b\u670d\u52a1\u4e2d\uff0c\u800c <code>IOptionsSnapshot&lt;T&gt;<\/code> \u5219\u4e0d\u80fd\u3002<\/p>\n<p>We have mentioned Named Options a couple of times so let\u2019s explain what that is.<br \/>\n\u6211\u4eec\u5df2\u7ecf\u63d0\u5230\u4e86\u51e0\u6b21 Named Options\uff0c\u6240\u4ee5\u8ba9\u6211\u4eec\u89e3\u91ca\u4e00\u4e0b\u5b83\u662f\u4ec0\u4e48\u3002<\/p>\n<p>Let\u2019s assume, just for example sake, that we have a configuration like this one:<br \/>\n\u8ba9\u6211\u4eec\u5047\u8bbe\uff0c\u53ea\u662f\u4e3a\u4e86\u8bf4\u660e\u539f\u56e0\uff0c\u6211\u4eec\u6709\u4e00\u4e2a\u8fd9\u6837\u7684\u914d\u7f6e\uff1a<\/p>\n<pre><code>&quot;JwtSettings&quot;: { &quot;validIssuer&quot;: &quot;CodeMazeAPI&quot;, &quot;validAudience&quot;: &quot;https:\/\/localhost:5001&quot;, &quot;expires&quot;: 5 }, &quot;JwtAPI2Settings&quot;: { &quot;validIssuer&quot;: &quot;CodeMazeAPI2&quot;, &quot;validAudience&quot;: &quot;https:\/\/localhost:5002&quot;, &quot;expires&quot;: 10 },<\/code><\/pre>\n<p>Instead of creating a new JwtConfiguration2 class that has the same properties as our existing JwtConfiguration class, we can add another configuration:<br \/>\n\u6211\u4eec\u53ef\u4ee5\u6dfb\u52a0\u53e6\u4e00\u4e2a\u914d\u7f6e\uff0c\u800c\u4e0d\u662f\u521b\u5efa\u4e00\u4e2a\u4e0e\u73b0\u6709 JwtConfiguration \u7c7b\u5177\u6709\u76f8\u540c\u5c5e\u6027\u7684\u65b0 JwtConfiguration2 \u7c7b\uff1a<\/p>\n<pre><code>services.Configure&lt;JwtConfiguration&gt;(&quot;JwtSettings&quot;, configuration.GetSection(&quot;JwtSettings&quot;)); services.Configure&lt;JwtConfiguration&gt;(&quot;JwtAPI2Settings&quot;, configuration.GetSection(&quot;JwtAPI2Settings&quot;));<\/code><\/pre>\n<p>Now both sections are mapped to the same configuration class, which makes sense. We don\u2019t want to create multiple classes with the same properties and just name them differently. This is a much better way of doing it.<br \/>\n\u73b0\u5728\uff0c\u8fd9\u4e24\u4e2a\u90e8\u5206\u90fd\u6620\u5c04\u5230\u540c\u4e00\u4e2a configuration class\uff0c\u8fd9\u662f\u6709\u9053\u7406\u7684\u3002\u6211\u4eec\u4e0d\u60f3\u521b\u5efa\u591a\u4e2a\u5177\u6709\u76f8\u540c\u5c5e\u6027\u7684\u7c7b\uff0c\u7136\u540e\u53ea\u662f\u4ee5\u4e0d\u540c\u7684\u65b9\u5f0f\u547d\u540d\u5b83\u4eec\u3002\u8fd9\u662f\u4e00\u79cd\u66f4\u597d\u7684\u65b9\u6cd5\u3002<\/p>\n<p>Calling the specific option is now done using the Get method with a section name as a parameter instead of the Value or CurrentValue properties:<br \/>\n\u73b0\u5728\uff0c\u4f7f\u7528 Get \u65b9\u6cd5\u5c06\u8282\u540d\u79f0\u4f5c\u4e3a\u53c2\u6570\uff0c\u800c\u4e0d\u662f Value \u6216 CurrentValue \u5c5e\u6027\u6765\u8c03\u7528\u7279\u5b9a\u9009\u9879\uff1a<\/p>\n<pre><code>_jwtConfiguration = _configuration.Get(&quot;JwtSettings&quot;);<\/code><\/pre>\n<p>That\u2019s it. All the rest is the same.<br \/>\n\u5c31\u662f\u8fd9\u6837\u3002\u5176\u4f59\u7684\u90fd\u662f\u4e00\u6837\u7684\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>29 BINDING CONFIGURATION AND OPTIONS PATTERN 29 \u7ed1\u5b9a\u914d\u7f6e\u548c\u9009\u9879\u6a21\u5f0f In the previous chapter, we had to use our appsettings file to store some important values for our JWT configuration and read those values from it:\u200c \u5728\u4e0a\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5fc5\u987b\u4f7f\u7528 appsettings \u6587\u4ef6\u6765\u5b58\u50a8 JWT \u914d\u7f6e\u7684\u4e00\u4e9b\u91cd\u8981\u503c\uff0c\u5e76\u4ece\u4e2d\u8bfb\u53d6\u8fd9\u4e9b\u503c\uff1a &quot;JwtSettings&quot;: { &quot;validIssuer&quot;: &quot;CodeMazeAPI&quot;, &quot;validAudience&quot;: &quot;https:\/\/localhost:5001&quot;, &quot;expires&quot;: 5 }, To access these values, we\u2019ve used the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1156","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1156","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1156"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1156\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1156"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1156"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1156"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}