From 5d9232734753c10ec750fed387b70c6e1c56a8cd Mon Sep 17 00:00:00 2001
From: bicijinlian <bicijinlian@163.com>
Date: Wed, 21 Dec 2022 02:13:17 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=EF=BC=9A=E7=BC=96=E7=A8=8B?=
 =?UTF-8?q?=E4=BD=93=E9=AA=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../OptionsPattern.Sutdy.Experience.csproj    |   1 +
 .../OptionsPatternTest.cs                     | 232 +++++++++++++++++-
 OptionsPattern.Sutdy.Experience/Startup.cs    |  20 +-
 OptionsPattern.Sutdy.Experience/Usings.cs     |   2 +
 4 files changed, 230 insertions(+), 25 deletions(-)

diff --git a/OptionsPattern.Sutdy.Experience/OptionsPattern.Sutdy.Experience.csproj b/OptionsPattern.Sutdy.Experience/OptionsPattern.Sutdy.Experience.csproj
index 5ee861d..95983b8 100644
--- a/OptionsPattern.Sutdy.Experience/OptionsPattern.Sutdy.Experience.csproj
+++ b/OptionsPattern.Sutdy.Experience/OptionsPattern.Sutdy.Experience.csproj
@@ -20,6 +20,7 @@
     <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="7.0.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Xml" Version="7.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
     <PackageReference Include="Microsoft.Extensions.Options" Version="7.0.0" />
     <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
diff --git a/OptionsPattern.Sutdy.Experience/OptionsPatternTest.cs b/OptionsPattern.Sutdy.Experience/OptionsPatternTest.cs
index d4f8906..d6dabe5 100644
--- a/OptionsPattern.Sutdy.Experience/OptionsPatternTest.cs
+++ b/OptionsPattern.Sutdy.Experience/OptionsPatternTest.cs
@@ -1,13 +1,13 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Hosting.Internal;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Xunit;
 
 namespace OptionsPattern.Sutdy.Experience
 {
     /// <summary>
-    /// 6.1 Option模式 编程体验
+    /// 6.1 配置选项 编程体验
     /// </summary>
     public class OptionsPatternTest:IDisposable
     {
@@ -20,7 +20,225 @@ namespace OptionsPattern.Sutdy.Experience
         [Fact]
         public void Test()
         {
-            testOutput.WriteLine("Option模式 编程体验");
+            testOutput.WriteLine("6.1 配置选项 编程体验");
+        }
+
+        /// <summary>
+        /// 将配置绑定为 Option 对象
+        /// </summary>
+        [Fact]
+        public void BindConfiguration_As_OptionsObject_Test()
+        {
+            var configuration = new ConfigurationBuilder().AddJsonFile("Configs/appsettings.json",false,true).Build();
+
+            var appOption = new ServiceCollection()
+                .AddOptions()
+                .Configure<AppOption>(configuration)
+                .BuildServiceProvider()
+                .GetRequiredService<IOptions<AppOption>>().Value;
+
+            Assert.NotNull(configuration);
+            Assert.NotNull(appOption);
+
+            Assert.Equal("JsonAppNmae", appOption.AppName);
+            Assert.Equal(new Version(0,0,0,1), appOption.AppVersion);
+            Assert.Equal("json@163.com", appOption.EMail?.ReceiveAddress);
+            Assert.Equal("json", appOption.EMail?.Recipient);
+
+            testOutput.WriteLine("将配置绑定为 Option 对象");
+        }
+        
+        /// <summary>
+        /// 提供具名的 Options 对象
+        /// </summary>
+        [Fact]
+        public void Provide_NamedOptionsObject_Test()
+        {
+            var memoryData = new Dictionary<string, string?>() 
+            {
+                ["One:AppName"] = "OneAppName",
+                ["One:AppVersion"] = "1.1.1.1",
+                ["One:EMail:ReceiveAddress"] = "One@163.com",
+                ["One:EMail:Recipient"] = "One",
+
+                ["Two:AppName"] = "TwoAppName",
+                ["Two:AppVersion"] = "2.2.2.2",
+                ["Two:EMail:ReceiveAddress"] = "Two@163.com",
+                ["Two:EMail:Recipient"] = "Two",
+            };
+
+            var configuration = new ConfigurationBuilder().AddInMemoryCollection(memoryData).Build();
+
+            var serviceProvider = new ServiceCollection()
+                .AddOptions()
+                .Configure<AppOption>("One", configuration.GetSection("One"))
+                .Configure<AppOption>("Two", configuration.GetSection("Two"))
+                .BuildServiceProvider();
+
+             var optionsSnapshot = serviceProvider.GetRequiredService<IOptionsSnapshot<AppOption>>();
+
+            var one = optionsSnapshot.Get("One");
+            var two = optionsSnapshot.Get("Two");
+
+            Assert.NotNull(one);
+            Assert.NotNull(two);
+
+            Assert.Equal("OneAppName", one.AppName);
+            Assert.Equal(new Version(1,1,1,1), one.AppVersion);
+            Assert.Equal("One@163.com", one.EMail?.ReceiveAddress);
+            Assert.Equal("One", one.EMail?.Recipient);
+
+            Assert.Equal("TwoAppName", two.AppName);
+            Assert.Equal(new Version(2, 2, 2, 2), two.AppVersion);
+            Assert.Equal("Two@163.com", two.EMail?.ReceiveAddress);
+            Assert.Equal("Two", two.EMail?.Recipient);
+
+            testOutput.WriteLine("提供具名的 Options 对象");
+        }
+
+        /// <summary>
+        /// 直接初始化 Options 对象
+        /// (不使用IConfiguration)
+        /// </summary>
+        [Fact]
+        public void IniOptions_NoConfiguration_Test()
+        {
+            var appOption = new ServiceCollection()
+                .AddOptions()
+                .Configure<AppOption>(configOption =>
+                {
+                    configOption.AppName = "NoConfigurationAppName";
+                    configOption.AppVersion = new Version(0, 0, 0, 1);
+                    configOption.EMail = new ReceiveMailOption()
+                    {
+                        ReceiveAddress = "NoConfigurationAppName@163.com",
+                        Recipient = "NoConfigurationAppName",
+                    };
+                })
+                .BuildServiceProvider()
+                .GetRequiredService<IOptions<AppOption>>().Value;
+
+            Assert.NotNull(appOption);
+
+            Assert.Equal("NoConfigurationAppName", appOption.AppName);
+            Assert.Equal(new Version(0, 0, 0, 1), appOption.AppVersion);
+            Assert.Equal("NoConfigurationAppName@163.com", appOption.EMail?.ReceiveAddress);
+            Assert.Equal("NoConfigurationAppName", appOption.EMail?.Recipient);
+
+            testOutput.WriteLine("直接初始化 Options 对象");
+        }
+
+        /// <summary>
+        /// 根据依赖服务的 Options 设置
+        /// </summary>
+        [Theory]
+        [InlineData("pro")]
+        [InlineData("dev")]
+        [InlineData("test")]
+        public void SetOptions_By_DependentService_Test(string environmentName)
+        {
+            var services = new ServiceCollection();
+            services
+                .AddSingleton<IHostEnvironment>(new HostingEnvironment() { EnvironmentName = environmentName })
+                .AddOptions<AppOption>()// AddOptions() 与 AddOptions<TOption>() 返回的类型不一样。之后的 Configure 扩展不一样 
+                .Configure<IHostEnvironment>((appOption, hostEnv) => // 泛型参数确定 Action 参数
+                {
+                    appOption.AppName = hostEnv.EnvironmentName + nameof(appOption.AppName);
+                    appOption.AppVersion = new Version();
+                    appOption.EMail = new ReceiveMailOption()
+                    {
+                        ReceiveAddress = $"{hostEnv.EnvironmentName}@163.com",
+                        Recipient = hostEnv.EnvironmentName,
+                    };
+                });
+            var appOption = services
+                .BuildServiceProvider()
+                .GetRequiredService< IOptions<AppOption> >()
+                .Value;
+
+
+            Assert.NotNull(appOption);
+            Assert.Equal($"{environmentName}AppName",appOption.AppName);
+            Assert.Equal(new Version(),appOption.AppVersion);
+            Assert.Equal($"{environmentName}@163.com",appOption.EMail?.ReceiveAddress);
+            Assert.Equal($"{environmentName}",appOption.EMail?.Recipient);
+
+            testOutput.WriteLine("根据依赖服务的 Options 设置");
+        }
+
+        /// <summary>
+        /// 验证 Options 的有效性:通过
+        /// </summary>
+        [Fact]
+        public void Validate_Options_Success_Test()
+        {
+            var services = new ServiceCollection();
+            services
+                .AddOptions<AppOption>()
+                .Configure(configOption =>
+                {
+                    configOption.AppName = "ValidateAppName";
+                    configOption.AppVersion = new Version(0, 0, 0, 1);
+                    configOption.EMail = new ReceiveMailOption()
+                    {
+                        ReceiveAddress = "Validate@163.com",
+                        Recipient = "Validate",
+                    };
+                })
+                .Validate(option =>
+                { 
+                    return true;
+                });
+
+            //不发生异常
+            Action codeSnippet = () =>
+            {
+                _ = services
+                    .BuildServiceProvider()
+                    .GetService<IOptions<AppOption>>()
+                    ?.Value;
+            };
+
+            var exception = Record.Exception(codeSnippet);
+            Assert.Null(exception);
+
+            testOutput.WriteLine("验证 Options 的有效性:通过");
+        }
+
+        /// <summary>
+        /// 验证 Options 的有效性:无效并引发异常
+        /// </summary>
+        [Fact]
+        public void Validate_Options_Fail_Test()
+        {
+            var services = new ServiceCollection();
+            services
+                .AddOptions<AppOption>()
+                .Configure(configOption =>
+                {
+                    configOption.AppName = "ValidateAppName";
+                    configOption.AppVersion = new Version(0, 0, 0, 1);
+                    configOption.EMail = new ReceiveMailOption()
+                    {
+                        ReceiveAddress = "Validate@163.com",
+                        Recipient = "Validate",
+                    };
+                })
+                .Validate(option => //返回false,表示验证错误,会抛出异常
+                {
+                    return false;
+                });
+
+            //引发异常
+            Assert.Throws<OptionsValidationException>(() => 
+            {
+                _ = services
+                   .BuildServiceProvider()
+                   .GetService<IOptions<AppOption>>()
+                   ?.Value;
+            });
+
+            testOutput.WriteLine("验证 Options 的有效性:无效并引发异常");
         }
 
         public void Dispose()
diff --git a/OptionsPattern.Sutdy.Experience/Startup.cs b/OptionsPattern.Sutdy.Experience/Startup.cs
index 04de861..6e95ca1 100644
--- a/OptionsPattern.Sutdy.Experience/Startup.cs
+++ b/OptionsPattern.Sutdy.Experience/Startup.cs
@@ -34,31 +34,16 @@ namespace OptionsPattern.Sutdy.Experience
             //设置主机配置
             hostBuilder.ConfigureHostConfiguration(builder =>
             {
-
+                
             });
 
             //设置应用配置
             hostBuilder.ConfigureAppConfiguration((context, builder) =>
             {
-
+                
             });
-
-            //集成 Opentelemetry
-            //var tracerProvider = Sdk.CreateTracerProviderBuilder()
-            //    .AddSource("Xunit.DependencyInjection")
-            //    .AddConsoleExporter();
-
         }
 
-        /// <summary>
-        /// 配置服务方法
-        /// (不支持重载)
-        /// </summary>
-        //public void ConfigureServices(IServiceCollection services)
-        //{
-
-        //}
-
         /// <summary>
         /// 配置服务方法
         /// 注入或用途 IConfiguration IHostEnvironment 请使用  context.xx;
@@ -66,7 +51,6 @@ namespace OptionsPattern.Sutdy.Experience
         public void ConfigureServices(IServiceCollection services, HostBuilderContext context)
         {
 
-
         }
 
         /// <summary>
diff --git a/OptionsPattern.Sutdy.Experience/Usings.cs b/OptionsPattern.Sutdy.Experience/Usings.cs
index ce36fb1..7c4f0e9 100644
--- a/OptionsPattern.Sutdy.Experience/Usings.cs
+++ b/OptionsPattern.Sutdy.Experience/Usings.cs
@@ -7,6 +7,8 @@ global using System.Collections.Generic;
 global using System.Collections.Concurrent;
 global using System.Collections.Specialized;
 
+global using Microsoft.Extensions.DependencyInjection;
+
 global using Microsoft.Extensions.Configuration;
 global using Microsoft.Extensions.Configuration.Memory;
 global using Microsoft.Extensions.Configuration.EnvironmentVariables;