From 5a94da8fe898b047fb271f4c2438f9a666f4fff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E5=B0=8F=E7=AB=A0?= Date: Sun, 28 Mar 2021 13:05:42 +0800 Subject: [PATCH 001/424] Create README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..aff9c55d --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# sample.dotblog +範例程式 From 0f870f873ca074edf089f0bd07faa33ac3256d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E5=B0=8F=E7=AB=A0?= Date: Sun, 28 Mar 2021 13:06:09 +0800 Subject: [PATCH 002/424] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index aff9c55d..af9e31ca 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # sample.dotblog +https://dotblogs.com.tw/yc421206/ 範例程式 From 2a697aaafab53c39b4be0abae9e6398331118b7a Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Tue, 30 Mar 2021 15:05:22 +0800 Subject: [PATCH 003/424] add test case --- .../NetFx48/SurveyJsonConfigurationTests.cs | 97 +++++++++++++------ 1 file changed, 65 insertions(+), 32 deletions(-) diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs index 6505ff81..62052544 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs @@ -10,28 +10,6 @@ namespace NetFx48 [TestClass] public class SurveyJsonConfigurationTests { - [TestMethod] - public void 記憶體組態() - { - var configBuilder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddInMemoryCollection(new Dictionary() - { - {"Player:AppId","player1"}, - {"Player:Key","1234567890"}, - {"ConnectionStrings:DefaultConnectionString","Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;"}, - }) - ; - var configRoot = configBuilder.Build(); - - //讀取組態 - - Console.WriteLine($"AppId = {configRoot["AppId"]}"); - Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); - Console.WriteLine($"Key = {configRoot["Player:Key"]}"); - Console.WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); - } - [TestMethod] public void 切換組態() { @@ -50,12 +28,12 @@ public void 切換組態() .AddJsonFile("appsettings.json", false, true) .AddJsonFile($"appsettings.{environmentName}.json", true, true) ; - var configRoot = configBuilder.Build(); + var config = configBuilder.Build(); //讀取組態 - Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); - Console.WriteLine($"Key = {configRoot["Player:Key"]}"); - Console.WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); + Console.WriteLine($"AppId = {config["Player:AppId"]}"); + Console.WriteLine($"Key = {config["Player:Key"]}"); + Console.WriteLine($"Connection String = {config["ConnectionStrings:DefaultConnectionString"]}"); } [TestMethod] @@ -65,14 +43,40 @@ public void 手動實例化ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", true, true) ; - var configRoot = configBuilder.Build(); + var config = configBuilder.Build(); + + //讀取組態 + + Console.WriteLine($"AppId = {config["AppId"]}"); + Console.WriteLine($"AppId = {config["Player:AppId"]}"); + Console.WriteLine($"Key = {config["Player:Key"]}"); + Console.WriteLine($"Connection String = {config["ConnectionStrings:DefaultConnectionString"]}"); + } + + [TestMethod] + public void 記憶體組態() + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddInMemoryCollection(new Dictionary + { + {"Player:AppId", "player1"}, + {"Player:Key", "1234567890"}, + { + "ConnectionStrings:DefaultConnectionString", + "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" + }, + }) + ; + + var config = configBuilder.Build(); //讀取組態 - Console.WriteLine($"AppId = {configRoot["AppId"]}"); - Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); - Console.WriteLine($"Key = {configRoot["Player:Key"]}"); - Console.WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); + Console.WriteLine($"AppId = {config["AppId"]}"); + Console.WriteLine($"AppId = {config["Player:AppId"]}"); + Console.WriteLine($"Key = {config["Player:Key"]}"); + Console.WriteLine($"Connection String = {config["ConnectionStrings:DefaultConnectionString"]}"); } [TestMethod] @@ -81,6 +85,20 @@ public void 通過Host() using var host = CreateHostBuilder(null).Build(); } + [TestMethod] + public void 讀取設定檔_GetSection() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + var config = builder.Build(); + + Console.WriteLine($"AppId = {config.GetSection("AppId")}"); + Console.WriteLine($"AppId = {config.GetSection("Player:AppId")}"); + Console.WriteLine($"Key = {config.GetSection("Player:Key")}"); + Console.WriteLine($"Connection String = {config.GetSection("ConnectionStrings:DefaultConnectionString")}"); + } + [TestMethod] public void 讀取設定檔_TryGet() { @@ -112,10 +130,25 @@ public void 讀取設定檔_綁定() Console.WriteLine($"Connection String = {appSetting.ConnectionStrings.DefaultConnectionString}"); } + [TestMethod] + public void 讀取設定檔_綁定_Get() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + var config = builder.Build(); + var player = config.GetSection("Player").Get(); + var appSetting = config.Get(); + + Console.WriteLine($"AppId = {player.AppId}"); + Console.WriteLine($"Key = {appSetting.Player.Key}"); + Console.WriteLine($"Connection String = {appSetting.ConnectionStrings.DefaultConnectionString}"); + } + private static IHostBuilder CreateHostBuilder(string[] args) { return Host.CreateDefaultBuilder(args) - .ConfigureAppConfiguration(( config) => + .ConfigureAppConfiguration(config => { config.Sources.Clear(); config.AddJsonFile("appsettings.json", true, true); From 03fb61f448236767d0f91da1b2ae139bf5793254 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Tue, 30 Mar 2021 15:47:01 +0800 Subject: [PATCH 004/424] di appservice --- .../NetCore/Lab.Config/NetFx48/AppService.cs | 19 ++++++++++++++++ .../NetFx48/{ => Models}/AppSetting.cs | 0 .../NetFx48/{ => Models}/ConnectionStrings.cs | 0 .../Lab.Config/NetFx48/{ => Models}/Player.cs | 0 .../NetFx48/SurveyJsonConfigurationTests.cs | 22 +++++++++++++++++++ 5 files changed, 41 insertions(+) create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/AppService.cs rename Configuration/NetCore/Lab.Config/NetFx48/{ => Models}/AppSetting.cs (100%) rename Configuration/NetCore/Lab.Config/NetFx48/{ => Models}/ConnectionStrings.cs (100%) rename Configuration/NetCore/Lab.Config/NetFx48/{ => Models}/Player.cs (100%) diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs new file mode 100644 index 00000000..f910244c --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Configuration; + +namespace NetFx48 +{ + public class AppService + { + private readonly IConfiguration _config; + + public AppService(IConfiguration config) + { + this._config = config; + } + + public string GetPlayerId() + { + return this._config.GetSection("Player:AppId").Value; + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppSetting.cs b/Configuration/NetCore/Lab.Config/NetFx48/Models/AppSetting.cs similarity index 100% rename from Configuration/NetCore/Lab.Config/NetFx48/AppSetting.cs rename to Configuration/NetCore/Lab.Config/NetFx48/Models/AppSetting.cs diff --git a/Configuration/NetCore/Lab.Config/NetFx48/ConnectionStrings.cs b/Configuration/NetCore/Lab.Config/NetFx48/Models/ConnectionStrings.cs similarity index 100% rename from Configuration/NetCore/Lab.Config/NetFx48/ConnectionStrings.cs rename to Configuration/NetCore/Lab.Config/NetFx48/Models/ConnectionStrings.cs diff --git a/Configuration/NetCore/Lab.Config/NetFx48/Player.cs b/Configuration/NetCore/Lab.Config/NetFx48/Models/Player.cs similarity index 100% rename from Configuration/NetCore/Lab.Config/NetFx48/Player.cs rename to Configuration/NetCore/Lab.Config/NetFx48/Models/Player.cs diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs index 62052544..26ef08b5 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -53,6 +54,27 @@ public void 手動實例化ConfigurationBuilder() Console.WriteLine($"Connection String = {config["ConnectionStrings:DefaultConnectionString"]}"); } + [TestMethod] + public void 注入Configuration() + { + var builder = Host.CreateDefaultBuilder(null) + .ConfigureAppConfiguration(config => + { + config.Sources.Clear(); + config.AddJsonFile("appsettings.json", true, true); + }) + .ConfigureServices(service => + { + //DI + service.AddScoped(typeof(AppService)); + }); + var host = builder.Build(); + + var appService = host.Services.GetService(); + var playerId = appService.GetPlayerId(); + Console.WriteLine($"AppId = {playerId}"); + } + [TestMethod] public void 記憶體組態() { From 4ba23311bee57a269b62fbc59c380d421de90da1 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Tue, 30 Mar 2021 15:52:56 +0800 Subject: [PATCH 005/424] refactor --- Configuration/NetCore/Lab.Config/NetFx48/AppService.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs index f910244c..40388117 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs @@ -2,7 +2,12 @@ namespace NetFx48 { - public class AppService + public interface IAppService + { + string GetPlayerId(); + } + + public class AppService : IAppService { private readonly IConfiguration _config; From c6496651fbde182a9edd0212c532a69bcf774af4 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Tue, 30 Mar 2021 17:20:35 +0800 Subject: [PATCH 006/424] =?UTF-8?q?add=20=E5=AF=A6=E4=BE=8B=E5=8C=96JsonCo?= =?UTF-8?q?nfigurationProvider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NetFx48/SurveyJsonConfigurationTests.cs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs index 26ef08b5..8fdcb1e1 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -16,11 +17,11 @@ public void 切換組態() { string environmentName; #if DEBUG - environmentName = "Development"; + environmentName = "Development"; #elif QA - environmentName = "QA"; + environmentName = "QA"; #elif STAGING - environmentName = "Staging"; + environmentName = "Staging"; #elif RELEASE environmentName = "Production"; #endif @@ -107,6 +108,20 @@ public void 通過Host() using var host = CreateHostBuilder(null).Build(); } + [TestMethod] + public void 實例化JsonConfigurationProvider() + { + var configProvider = new JsonConfigurationProvider(new JsonConfigurationSource + { + Optional = false, + Path = "appsettings.json", + ReloadOnChange = true + }); + configProvider.Load(); + configProvider.TryGet("Player:AppId", out var appId); + Console.WriteLine($"AppId = {appId}"); + } + [TestMethod] public void 讀取設定檔_GetSection() { From eff10703ee5951b3844d87e4f209a09618bc82ac Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Tue, 30 Mar 2021 18:13:27 +0800 Subject: [PATCH 007/424] add SurveyCommandConfigurationTests --- .../NetCore/Lab.Config/NetFx48/NetFx48.csproj | 1 + .../SurveyCommandConfigurationTests.cs | 68 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/SurveyCommandConfigurationTests.cs diff --git a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj index 693d138f..21e71a51 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj +++ b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj @@ -26,6 +26,7 @@ + diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyCommandConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyCommandConfigurationTests.cs new file mode 100644 index 00000000..1ed04512 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyCommandConfigurationTests.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Configuration.CommandLine; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NetFx48 +{ + [TestClass] + public class SurveyCommandConfigurationTests + { + [TestMethod] + public void 命令對應() + { + string[] args = {"-i=1234567890", "-c=app.json"}; + + var map = new Dictionary + { + {"-i", "--AppId"}, + {"-c", "--Config"} + }; + + var provider = new CommandLineConfigurationProvider(args, map); + provider.Load(); + + provider.TryGet("AppId", out var appId); + Console.WriteLine($"{args.First()}\r\n" + + $"AppId:{appId}"); + } + + [TestMethod] + public void 傳參數給應用程式_使用Host() + { + // string[] args = {"/appId 1234567890"}; + string[] args = {"/AppId=1234567890"}; + var builder = Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration(config => + { + // config.Sources.Clear(); + // config.AddJsonFile("appsettings.json", true, true); + // config.AddCommandLine(args); + var configRoot = config.Build(); + Console.WriteLine($"AppId = {configRoot["AppId"]}"); + }) + .ConfigureServices(service => + { + //DI + service.AddScoped(typeof(AppService)); + }); + var host = builder.Build(); + } + + [TestMethod] + [DataRow(new[] {"--AppId=1234567890"})] + [DataRow(new[] {"/AppId=1234567890"})] + [DataRow(new[] {"AppId=1234567890"})] + public void 實例化CommandLineConfigurationProvider(string[] args) + { + var provider = new CommandLineConfigurationProvider(args); + provider.Load(); + provider.TryGet("AppId", out var appId); + Console.WriteLine($"{args.First()}\r\n" + + $"AppId:{appId}"); + } + } +} \ No newline at end of file From a41d2714d91a6de46d6d1e1700ee5c9aa30b7387 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 31 Mar 2021 09:26:19 +0800 Subject: [PATCH 008/424] refactor --- .../SurveyCommandConfigurationTests.cs | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyCommandConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyCommandConfigurationTests.cs index 1ed04512..7c00b012 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyCommandConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyCommandConfigurationTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.CommandLine; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -15,40 +16,51 @@ public class SurveyCommandConfigurationTests public void 命令對應() { string[] args = {"-i=1234567890", "-c=app.json"}; - + var map = new Dictionary { - {"-i", "--AppId"}, - {"-c", "--Config"} + {"-i", "AppId"}, + {"-c", "Config"} }; - + var provider = new CommandLineConfigurationProvider(args, map); provider.Load(); - + provider.TryGet("AppId", out var appId); + provider.TryGet("Config", out var configPath); Console.WriteLine($"{args.First()}\r\n" + - $"AppId:{appId}"); + $"AppId:{appId}\r\n" + + $"ConfigPath:{configPath}"); } [TestMethod] - public void 傳參數給應用程式_使用Host() + [DataRow(new[] {"-i=1234567890", "-c=app.json"})] + public void 命令對應_Host(string[] args) { - // string[] args = {"/appId 1234567890"}; - string[] args = {"/AppId=1234567890"}; - var builder = Host.CreateDefaultBuilder(args) + var map = new Dictionary + { + {"-i", "AppId"}, + {"-c", "Config"} + }; + var builder = Host.CreateDefaultBuilder() .ConfigureAppConfiguration(config => { // config.Sources.Clear(); - // config.AddJsonFile("appsettings.json", true, true); - // config.AddCommandLine(args); + config.AddCommandLine(args, map); var configRoot = config.Build(); - Console.WriteLine($"AppId = {configRoot["AppId"]}"); + + var appId = configRoot["AppId"]; + var configPath = configRoot["Config"]; + Console.WriteLine($"{args.First()}\r\n" + + $"AppId:{appId}\r\n" + + $"ConfigPath:{configPath}"); }) .ConfigureServices(service => { //DI service.AddScoped(typeof(AppService)); - }); + }) + ; var host = builder.Build(); } From c069b373210a97d1fc4e1de17dc9d684fa5a1fc1 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 31 Mar 2021 12:10:52 +0800 Subject: [PATCH 009/424] add SurveyKeyPerFileConfigurationTests --- .../NetCore/Lab.Config/NetFx48/NetFx48.csproj | 4 +++ .../SurveyKeyPerFileConfigurationTests.cs | 27 +++++++++++++++++++ .../NetFx48/keys/aws/web/NewFile1.txt | 1 + 3 files changed, 32 insertions(+) create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/SurveyKeyPerFileConfigurationTests.cs create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/keys/aws/web/NewFile1.txt diff --git a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj index 21e71a51..f36333af 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj +++ b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj @@ -27,6 +27,7 @@ + @@ -48,6 +49,9 @@ Always + + Always + diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyKeyPerFileConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyKeyPerFileConfigurationTests.cs new file mode 100644 index 00000000..a0cb8de4 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyKeyPerFileConfigurationTests.cs @@ -0,0 +1,27 @@ +using System; +using System.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NetFx48 +{ + [TestClass] + public class SurveyKeyPerFileConfigurationTests + { + [TestMethod] + public void 手動實例化ConfigurationBuilder() + { + var expected = "我是檔案內容"; + var folderPath = Path.Combine(Directory.GetCurrentDirectory(), "keys/aws/web"); + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddKeyPerFile(folderPath,false); + var config = configBuilder.Build(); + + //讀取組態 + var actual = config["NewFile1.txt"]; + Console.WriteLine($"NewFile1.txt = {actual}"); + Assert.AreEqual(expected,actual); + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/keys/aws/web/NewFile1.txt b/Configuration/NetCore/Lab.Config/NetFx48/keys/aws/web/NewFile1.txt new file mode 100644 index 00000000..8c7cec77 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/keys/aws/web/NewFile1.txt @@ -0,0 +1 @@ +我是檔案內容 \ No newline at end of file From ba29ae626f4bb3c64a0634880bf0346bef6f91da Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 31 Mar 2021 17:34:52 +0800 Subject: [PATCH 010/424] =?UTF-8?q?=E7=92=B0=E5=A2=83=E8=AE=8A=E6=95=B8?= =?UTF-8?q?=E5=88=87=E6=8F=9B=E7=B5=84=E6=85=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NetCore/Lab.Config/NetFx48/NetFx48.csproj | 5 ++ ...yEnvironmentVariablesConfigurationTests.cs | 73 +++++++++++++++++++ .../Lab.Config/NetFx48/appsettings.test.json | 10 +++ 3 files changed, 88 insertions(+) create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/appsettings.test.json diff --git a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj index f36333af..594af989 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj +++ b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj @@ -37,6 +37,11 @@ Always PreserveNewest + + true + Always + PreserveNewest + diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs new file mode 100644 index 00000000..53043e3f --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs @@ -0,0 +1,73 @@ +using System; +using System.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NetFx48 +{ + [TestClass] + public class SurveyEnvironmentVariablesConfigurationTests + { + [TestMethod] + public void Host實例化ConfigurationBuilder() + { + var builder = Host.CreateDefaultBuilder() + .ConfigureAppConfiguration((hosting, configBuilder) => + { + // config.Sources.Clear(); + + configBuilder.AddEnvironmentVariables("Custom_"); + var configRoot = configBuilder.Build(); + + //讀取組態 + Console + .WriteLine($"ASPNETCORE_ENVIRONMENT = {configRoot["ASPNETCORE_ENVIRONMENT"]}"); + Console + .WriteLine($"DOTNET_ENVIRONMENT2 = {configRoot["DOTNET_ENVIRONMENT2"]}"); + Console + .WriteLine($"CUSTOM_ENVIRONMENT1 = {configRoot["CUSTOM_ENVIRONMENT1"]}"); + Console + .WriteLine($"ENVIRONMENT1 = {configRoot["ENVIRONMENT1"]}"); + }) + ; + builder.Build(); + } + + [TestMethod] + public void 切換組態設定() + { + var builder = Host.CreateDefaultBuilder() + .ConfigureAppConfiguration((hosting, configBuilder) => + { + // config.Sources.Clear(); + var environmentName = hosting.Configuration["ENVIRONMENT2"]; + configBuilder.AddJsonFile("appsettings.json",false,true); + configBuilder.AddJsonFile($"appsettings.{environmentName}.json",true,true); + + var configRoot = configBuilder.Build(); + + //讀取組態 + Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); + Console.WriteLine($"Key = {configRoot["Player:Key"]}"); + Console.WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); + }) + ; + builder.Build(); + } + + [TestMethod] + public void 手動實例化ConfigurationBuilder() + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddEnvironmentVariables("ASPNETCORE_") + ; + + var configRoot = configBuilder.Build(); + + //讀取組態 + Console.WriteLine($"ENVIRONMENT = {configRoot["ENVIRONMENT"]}"); + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.test.json b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.test.json new file mode 100644 index 00000000..c339236f --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.test.json @@ -0,0 +1,10 @@ +{ + "ConnectionStrings": { + "DefaultConnectionString": "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb.Test;Trusted_Connection=True;" + }, + + "Player": { + "AppId": "test", + "Key": "test1234567890" + } +} \ No newline at end of file From 7ac2e8481d1e394baf607ed0338028ee3d71dd3b Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 31 Mar 2021 19:24:01 +0800 Subject: [PATCH 011/424] add miss json file --- .../Lab.Config/AspNetCore3/appsettings.json | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json b/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json new file mode 100644 index 00000000..6159d462 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json @@ -0,0 +1,27 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + + "ConnectionStrings": { + "DefaultConnectionString": "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" + }, + "Player": { + "AppId": "test", + "Key": "12345678990" + }, + + "Player1": { + "AppId": "testApp", + "Key": "12345678990" + }, + "Player2": { + "AppId": "testApp", + "Key": "12345678990" + } +} \ No newline at end of file From 1ef79b4c4ab19d1b4f2f9547d787fb52a4c139cd Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 31 Mar 2021 23:39:09 +0800 Subject: [PATCH 012/424] add options pattern sample --- .../NetCore/Lab.Config/NetFx48/AppService.cs | 23 ++++++++++ .../Lab.Config/NetFx48/Models/AppSetting.cs | 8 ++++ .../NetCore/Lab.Config/NetFx48/NetFx48.csproj | 1 + .../Lab.Config/NetFx48/SurveyOptionTests.cs | 42 +++++++++++++++++++ 4 files changed, 74 insertions(+) create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs index 40388117..5e1f91dc 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace NetFx48 { @@ -21,4 +22,26 @@ public string GetPlayerId() return this._config.GetSection("Player:AppId").Value; } } + + public class AppServiceWithOption : IAppService + { + private readonly AppSetting1 _appSetting; + + public AppServiceWithOption(IOptions options) + { + this._appSetting = options.Value; + } + public AppServiceWithOption(IOptionsSnapshot options) + { + this._appSetting = options.Value; + } + public AppServiceWithOption(IOptionsMonitor options) + { + this._appSetting = options.CurrentValue; + } + public string GetPlayerId() + { + return this._appSetting.Player.AppId; + } + } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/Models/AppSetting.cs b/Configuration/NetCore/Lab.Config/NetFx48/Models/AppSetting.cs index fb48e8d3..21ad3ba8 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/Models/AppSetting.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/Models/AppSetting.cs @@ -6,4 +6,12 @@ public struct AppSetting public ConnectionStrings ConnectionStrings { get; set; } } + + public class AppSetting1 + { + public Player Player { get; set; } + + public ConnectionStrings ConnectionStrings { get; set; } + } + } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj index 594af989..679a98ac 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj +++ b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj @@ -28,6 +28,7 @@ + diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs new file mode 100644 index 00000000..8d0c27c2 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NetFx48 +{ + [TestClass] + public class SurveyOptionTests + { + [TestMethod] + public void `JOption() + { + var builder = Host.CreateDefaultBuilder() + .ConfigureAppConfiguration((hosting, configBuilder) => + { + // 1.ŪպA + var environmentName = hosting.Configuration["ENVIRONMENT2"]; + configBuilder.AddJsonFile("appsettings.json",false,true); + configBuilder.AddJsonFile($"appsettings.{environmentName}.json",true,true); + }) + .ConfigureServices((hosting, services) => + { + // 2.`J Options + services.AddOptions(); + // 3. `J IConfiguration + services.Configure(hosting.Configuration); + + //`JLA + services.AddSingleton(); + }) + ; + var host = builder.Build(); + var service = host.Services.GetService(); + var playerId = service.GetPlayerId(); + Console.WriteLine($"PlayerId={playerId}"); + } + } +} \ No newline at end of file From 26d2b857fd4487a1747ba95c7407cf641d555d63 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 1 Apr 2021 10:13:29 +0800 Subject: [PATCH 013/424] refactor --- .../Controllers/DefaultController.cs | 25 +++++++ .../Controllers/WeatherForecastController.cs | 48 +++++++------ .../AspNetCore3/ServiceCollectionEx.cs | 70 +++++++++---------- .../NetCore/Lab.Config/NetFx48/AppService.cs | 9 +-- 4 files changed, 86 insertions(+), 66 deletions(-) create mode 100644 Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs new file mode 100644 index 00000000..1a685ecc --- /dev/null +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs @@ -0,0 +1,25 @@ +using Lab.Infra; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace AspNetCore3.Controllers +{ + [ApiController] + [Route("[controller]")] + public class DefaultController : ControllerBase + { + public IActionResult Get() + { + var setting = this.GetAppSetting(); + return this.Ok(setting); + } + + private AppSetting GetAppSetting() + { + var serviceProvider = this.HttpContext.RequestServices; + var options = serviceProvider.GetService>(); + return options?.Value; + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs index 2fb9663f..4fc05dd9 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs @@ -25,26 +25,26 @@ public class WeatherForecastController : ControllerBase private Player _player2; // TODO:依賴 AppSetting - public WeatherForecastController(AppSetting appSetting) - { - this._appSetting = appSetting; - } + // public WeatherForecastController(AppSetting appSetting) + // { + // this._appSetting = appSetting; + // } // TODO:依賴 IOptions - //public WeatherForecastController(IOptions options) - //{ - // try - // { - // this._appSetting = options.Value; - // } - // catch (OptionsValidationException ex) - // { - // foreach (var failure in ex.Failures) - // { - // Console.WriteLine(failure); - // } - // } - //} + public WeatherForecastController(IOptions options) + { + try + { + this._appSetting = options.Value; + } + catch (OptionsValidationException ex) + { + foreach (var failure in ex.Failures) + { + Console.WriteLine(failure); + } + } + } // TODO:依賴 IOptionsSnapshot //public WeatherForecastController(IOptionsSnapshot options) @@ -66,11 +66,13 @@ public WeatherForecastController(AppSetting appSetting) // this._config = config; //} - //public WeatherForecastController(IOptions options, IConfiguration config) - //{ - // this._config = config; - // this._appSetting = options.Value; - //} + // public WeatherForecastController(IOptions options, IConfiguration config) + // { + // this._config = config; + // var appSetting = new AppSetting(); + // config.Bind(appSetting); + // this._appSetting = options.Value; + // } //public WeatherForecastController(ILogger logger) //{ diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/ServiceCollectionEx.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/ServiceCollectionEx.cs index 1134ef0e..907ebd43 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/ServiceCollectionEx.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/ServiceCollectionEx.cs @@ -1,35 +1,35 @@ -using System; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace AspNetCore3 -{ - public static class ServiceCollectionEx - { - /// - /// Inject AddSingleton - /// - /// - /// - /// - /// - public static TConfig Configure(this IServiceCollection services, IConfiguration configuration) - where TConfig : class, new() - { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } - - if (configuration == null) - { - throw new ArgumentNullException(nameof(configuration)); - } - - var config = Activator.CreateInstance(); - configuration.Bind(config); - services.AddSingleton(config); - return config; - } - } -} \ No newline at end of file +// using System; +// using Microsoft.Extensions.Configuration; +// using Microsoft.Extensions.DependencyInjection; +// +// namespace AspNetCore3 +// { +// public static class ServiceCollectionEx +// { +// /// +// /// Inject AddSingleton +// /// +// /// +// /// +// /// +// /// +// public static TConfig Configure(this IServiceCollection services, IConfiguration configuration) +// where TConfig : class, new() +// { +// if (services == null) +// { +// throw new ArgumentNullException(nameof(services)); +// } +// +// if (configuration == null) +// { +// throw new ArgumentNullException(nameof(configuration)); +// } +// +// var config = Activator.CreateInstance(); +// configuration.Bind(config); +// services.AddSingleton(config); +// return config; +// } +// } +// } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs index 5e1f91dc..7bf16e6f 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs @@ -31,14 +31,7 @@ public AppServiceWithOption(IOptions options) { this._appSetting = options.Value; } - public AppServiceWithOption(IOptionsSnapshot options) - { - this._appSetting = options.Value; - } - public AppServiceWithOption(IOptionsMonitor options) - { - this._appSetting = options.CurrentValue; - } + public string GetPlayerId() { return this._appSetting.Player.AppId; From bd1e032ebe46684e3bba842e3114a6705b6ed550 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 1 Apr 2021 11:17:08 +0800 Subject: [PATCH 014/424] refactor --- .../Controllers/DefaultController.cs | 20 +++++++- .../NetCore/Lab.Config/AspNetCore3/Program.cs | 9 ++-- .../Properties/launchSettings.json | 3 +- ...tting.cs => StartupInjectionAppSetting.cs} | 4 +- .../StartupInjectionIOptionsMonitor.cs | 51 +++++++++++++++++++ ...IOptions.cs => StartupInjectionOptions.cs} | 4 +- ....cs => StartupInjectionOptionsSnapshot.cs} | 4 +- .../Lab.Config/AspNetCore3/appsettings.json | 8 +-- 8 files changed, 87 insertions(+), 16 deletions(-) rename Configuration/NetCore/Lab.Config/AspNetCore3/{Startup_InjectionIAppSetting.cs => StartupInjectionAppSetting.cs} (91%) create mode 100644 Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs rename Configuration/NetCore/Lab.Config/AspNetCore3/{Startup_InjectionIOptions.cs => StartupInjectionOptions.cs} (91%) rename Configuration/NetCore/Lab.Config/AspNetCore3/{Startup_InjectionIOptionsSnapshot.cs => StartupInjectionOptionsSnapshot.cs} (93%) diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs index 1a685ecc..612b0b05 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs @@ -1,3 +1,4 @@ +using System; using Lab.Infra; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; @@ -11,7 +12,8 @@ public class DefaultController : ControllerBase { public IActionResult Get() { - var setting = this.GetAppSetting(); + // var setting = this.GetAppSetting(); + var setting = this.GetAppSettingMonitor(); return this.Ok(setting); } @@ -21,5 +23,21 @@ private AppSetting GetAppSetting() var options = serviceProvider.GetService>(); return options?.Value; } + + private AppSetting GetAppSettingMonitor() + { + var serviceProvider = this.HttpContext.RequestServices; + var appsSettingOptions = serviceProvider.GetService>(); + var playerOption = serviceProvider.GetService>(); + var player1 = playerOption.Get("Player1"); + var player2 = playerOption.Get("Player2"); + Console.WriteLine($"player1={player1.AppId}"); + Console.WriteLine($"player2={player2.AppId}"); + + // var appSetting = options.Get("Player1"); + // return options?.CurrentValue; + + return null; + } } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs index c9edcdf5..a78569e1 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs @@ -20,10 +20,11 @@ public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { - //webBuilder.UseStartup(); - //webBuilder.UseStartup(); - //webBuilder.UseStartup(); - webBuilder.UseStartup(); + // webBuilder.UseStartup(); + // webBuilder.UseStartup(); + // webBuilder.UseStartup(); + webBuilder.UseStartup(); + // webBuilder.UseStartup(); }); } } diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Properties/launchSettings.json b/Configuration/NetCore/Lab.Config/AspNetCore3/Properties/launchSettings.json index 886261e3..649d735f 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Properties/launchSettings.json +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Properties/launchSettings.json @@ -12,7 +12,8 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "weatherforecast", + "//launchUrl": "weatherforecast", + "launchUrl": "default", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Startup_InjectionIAppSetting.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionAppSetting.cs similarity index 91% rename from Configuration/NetCore/Lab.Config/AspNetCore3/Startup_InjectionIAppSetting.cs rename to Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionAppSetting.cs index 59fd68f8..877559e9 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Startup_InjectionIAppSetting.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionAppSetting.cs @@ -7,11 +7,11 @@ namespace AspNetCore3 { - public class Startup_InjectionIAppSetting + public class StartupInjectionAppSetting { public IConfiguration Configuration { get; } - public Startup_InjectionIAppSetting(IConfiguration configuration) + public StartupInjectionAppSetting(IConfiguration configuration) { this.Configuration = configuration; } diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs new file mode 100644 index 00000000..c14cd603 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs @@ -0,0 +1,51 @@ +using Lab.Infra; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace AspNetCore3 +{ + public class StartupInjectionOptionsMonitor + { + public IConfiguration Configuration { get; } + + public StartupInjectionOptionsMonitor(IConfiguration configuration) + { + this.Configuration = configuration; + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + + //`J IOptions + services.AddOptions(); + + //`J IConfiguration + services.Configure(this.Configuration); + services.Configure("Player", this.Configuration.GetSection("Player")); + services.Configure("Player1", this.Configuration.GetSection("Player1")); + services.Configure("Player2", this.Configuration.GetSection("Player2")); + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Startup_InjectionIOptions.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs similarity index 91% rename from Configuration/NetCore/Lab.Config/AspNetCore3/Startup_InjectionIOptions.cs rename to Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs index 716c5e74..70ec5f2d 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Startup_InjectionIOptions.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs @@ -7,11 +7,11 @@ namespace AspNetCore3 { - public class Startup_InjectionIOptions + public class StartupInjectionOptions { public IConfiguration Configuration { get; } - public Startup_InjectionIOptions(IConfiguration configuration) + public StartupInjectionOptions(IConfiguration configuration) { this.Configuration = configuration; } diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Startup_InjectionIOptionsSnapshot.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs similarity index 93% rename from Configuration/NetCore/Lab.Config/AspNetCore3/Startup_InjectionIOptionsSnapshot.cs rename to Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs index ee727426..3b5933bd 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Startup_InjectionIOptionsSnapshot.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs @@ -7,11 +7,11 @@ namespace AspNetCore3 { - public class Startup_InjectionIOptionsSnapshot + public class StartupInjectionOptionsSnapshot { public IConfiguration Configuration { get; } - public Startup_InjectionIOptionsSnapshot(IConfiguration configuration) + public StartupInjectionOptionsSnapshot(IConfiguration configuration) { this.Configuration = configuration; } diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json b/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json index 6159d462..8ee8b905 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json @@ -17,11 +17,11 @@ }, "Player1": { - "AppId": "testApp", - "Key": "12345678990" + "AppId": "player1", + "Key": "player1_123456" }, "Player2": { - "AppId": "testApp", - "Key": "12345678990" + "AppId": "player2", + "Key": "player2_123456" } } \ No newline at end of file From fdf1d405ba23a5048d54d2adea7c548389f7161f Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 1 Apr 2021 11:52:13 +0800 Subject: [PATCH 015/424] refactor --- .../NetFx48/SurveyJsonConfigurationTests.cs | 70 ++++++++++++------- .../SurveyKeyPerFileConfigurationTests.cs | 4 +- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs index 8fdcb1e1..537cba32 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs @@ -30,12 +30,12 @@ public void 切換組態() .AddJsonFile("appsettings.json", false, true) .AddJsonFile($"appsettings.{environmentName}.json", true, true) ; - var config = configBuilder.Build(); + var configRoot = configBuilder.Build(); //讀取組態 - Console.WriteLine($"AppId = {config["Player:AppId"]}"); - Console.WriteLine($"Key = {config["Player:Key"]}"); - Console.WriteLine($"Connection String = {config["ConnectionStrings:DefaultConnectionString"]}"); + Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); + Console.WriteLine($"Key = {configRoot["Player:Key"]}"); + Console.WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); } [TestMethod] @@ -45,14 +45,14 @@ public void 手動實例化ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", true, true) ; - var config = configBuilder.Build(); + var configRoot = configBuilder.Build(); //讀取組態 - Console.WriteLine($"AppId = {config["AppId"]}"); - Console.WriteLine($"AppId = {config["Player:AppId"]}"); - Console.WriteLine($"Key = {config["Player:Key"]}"); - Console.WriteLine($"Connection String = {config["ConnectionStrings:DefaultConnectionString"]}"); + Console.WriteLine($"AppId = {configRoot["AppId"]}"); + Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); + Console.WriteLine($"Key = {configRoot["Player:Key"]}"); + Console.WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); } [TestMethod] @@ -92,14 +92,14 @@ public void 記憶體組態() }) ; - var config = configBuilder.Build(); + var configRoot = configBuilder.Build(); //讀取組態 - Console.WriteLine($"AppId = {config["AppId"]}"); - Console.WriteLine($"AppId = {config["Player:AppId"]}"); - Console.WriteLine($"Key = {config["Player:Key"]}"); - Console.WriteLine($"Connection String = {config["ConnectionStrings:DefaultConnectionString"]}"); + Console.WriteLine($"AppId = {configRoot["AppId"]}"); + Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); + Console.WriteLine($"Key = {configRoot["Player:Key"]}"); + Console.WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); } [TestMethod] @@ -128,12 +128,30 @@ public void 讀取設定檔_GetSection() var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json"); - var config = builder.Build(); + var configRoot = builder.Build(); - Console.WriteLine($"AppId = {config.GetSection("AppId")}"); - Console.WriteLine($"AppId = {config.GetSection("Player:AppId")}"); - Console.WriteLine($"Key = {config.GetSection("Player:Key")}"); - Console.WriteLine($"Connection String = {config.GetSection("ConnectionStrings:DefaultConnectionString")}"); + Console.WriteLine($"AppId = {configRoot.GetSection("AppId")}"); + Console.WriteLine($"AppId = {configRoot.GetSection("Player:AppId")}"); + Console.WriteLine($"Key = {configRoot.GetSection("Player:Key")}"); + Console.WriteLine($"Connection String = {configRoot.GetSection("ConnectionStrings:DefaultConnectionString")}"); + } + + [TestMethod] + public void 讀取設定檔_GetChild() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + var configRoot = builder.Build(); + var firstSections = configRoot.GetChildren(); + foreach (var firstSection in firstSections) + { + var secondSections = firstSection.GetChildren(); + foreach (var secondSection in secondSections) + { + Console.WriteLine($"{secondSection.Key}={secondSection.Value}\tPath={secondSection.Path}"); + } + } } [TestMethod] @@ -142,10 +160,10 @@ public void 讀取設定檔_TryGet() var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json"); - var config = builder.Build(); + var configRoot = builder.Build(); //TryGet - foreach (var provider in config.Providers) + foreach (var provider in configRoot.Providers) { provider.TryGet("Player:AppId", out var value); Console.WriteLine($"AppId = {value}"); @@ -158,10 +176,10 @@ public void 讀取設定檔_綁定() var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json"); - var config = builder.Build(); + var configRoot = builder.Build(); var appSetting = new AppSetting(); - config.Bind(appSetting); + configRoot.Bind(appSetting); Console.WriteLine($"AppId = {appSetting.Player.AppId}"); Console.WriteLine($"Key = {appSetting.Player.Key}"); Console.WriteLine($"Connection String = {appSetting.ConnectionStrings.DefaultConnectionString}"); @@ -173,9 +191,9 @@ public void 讀取設定檔_綁定_Get() var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json"); - var config = builder.Build(); - var player = config.GetSection("Player").Get(); - var appSetting = config.Get(); + var configRoot = builder.Build(); + var player = configRoot.GetSection("Player").Get(); + var appSetting = configRoot.Get(); Console.WriteLine($"AppId = {player.AppId}"); Console.WriteLine($"Key = {appSetting.Player.Key}"); diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyKeyPerFileConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyKeyPerFileConfigurationTests.cs index a0cb8de4..5205b0f4 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyKeyPerFileConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyKeyPerFileConfigurationTests.cs @@ -16,10 +16,10 @@ public void 手動實例化ConfigurationBuilder() var configBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddKeyPerFile(folderPath,false); - var config = configBuilder.Build(); + var configRoot = configBuilder.Build(); //讀取組態 - var actual = config["NewFile1.txt"]; + var actual = configRoot["NewFile1.txt"]; Console.WriteLine($"NewFile1.txt = {actual}"); Assert.AreEqual(expected,actual); } From 5afb35a710048c1b5cda0031a0c769384a84cf10 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 1 Apr 2021 13:39:04 +0800 Subject: [PATCH 016/424] refactor --- .../Controllers/DefaultController.cs | 15 +- .../Controllers/WeatherForecastController.cs | 190 +++++++++--------- .../StartupInjectionIOptionsMonitor.cs | 4 +- .../Lab.Config/AspNetCore3/appsettings.json | 12 +- .../NetFx48/AppServiceWithOptionsMonitor.cs | 26 +++ .../NetFx48/AppServiceWithOptionsSnapshot.cs | 26 +++ .../Lab.Config/NetFx48/Models/Player.cs | 7 + .../Lab.Config/NetFx48/SurveyOptionTests.cs | 81 +++++++- 8 files changed, 248 insertions(+), 113 deletions(-) create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/AppServiceWithOptionsMonitor.cs create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/AppServiceWithOptionsSnapshot.cs diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs index 612b0b05..48fe7d64 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs @@ -10,6 +10,8 @@ namespace AspNetCore3.Controllers [Route("[controller]")] public class DefaultController : ControllerBase { + // [Route("controller/appsettings")] + [Route("appsettings")] public IActionResult Get() { // var setting = this.GetAppSetting(); @@ -17,6 +19,15 @@ public IActionResult Get() return this.Ok(setting); } + [Route("players/{id}")] + public IActionResult GetPlayer(int id) + { + var serviceProvider = this.HttpContext.RequestServices; + var playerOption = serviceProvider.GetService>(); + var player = playerOption.Get($"Player{id}"); + return this.Ok(player); + } + private AppSetting GetAppSetting() { var serviceProvider = this.HttpContext.RequestServices; @@ -35,9 +46,7 @@ private AppSetting GetAppSettingMonitor() Console.WriteLine($"player2={player2.AppId}"); // var appSetting = options.Get("Player1"); - // return options?.CurrentValue; - - return null; + return appsSettingOptions?.CurrentValue; } } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs index 4fc05dd9..f50947bf 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs @@ -1,95 +1,95 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Lab.Infra; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace AspNetCore3.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - private AppSetting _appSetting; - private IConfiguration _config; - private Player _player1; - private Player _player2; - - // TODO:依賴 AppSetting - // public WeatherForecastController(AppSetting appSetting) - // { - // this._appSetting = appSetting; - // } - - // TODO:依賴 IOptions - public WeatherForecastController(IOptions options) - { - try - { - this._appSetting = options.Value; - } - catch (OptionsValidationException ex) - { - foreach (var failure in ex.Failures) - { - Console.WriteLine(failure); - } - } - } - - // TODO:依賴 IOptionsSnapshot - //public WeatherForecastController(IOptionsSnapshot options) - //{ - // this._player1 = options.Get("Player1"); - // this._player2 = options.Get("Player2"); - //} - - //// TODO:依賴 IOptionsMonitor - //public WeatherForecastController(IOptionsMonitor options) - //{ - // this._player1 = options.Get("Player1"); - // this._player2 = options.Get("Player2"); - //} - - // TODO:依賴 IConfiguration - //public WeatherForecastController(IConfiguration config) - //{ - // this._config = config; - //} - - // public WeatherForecastController(IOptions options, IConfiguration config) - // { - // this._config = config; - // var appSetting = new AppSetting(); - // config.Bind(appSetting); - // this._appSetting = options.Value; - // } - - //public WeatherForecastController(ILogger logger) - //{ - // _logger = logger; - //} - - [HttpGet] - public IEnumerable Get() - { - var rng = new Random(); - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateTime.Now.AddDays(index), - TemperatureC = rng.Next(-20, 55), - Summary = Summaries[rng.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} \ No newline at end of file +// using System; +// using System.Collections.Generic; +// using System.Linq; +// using Lab.Infra; +// using Microsoft.AspNetCore.Mvc; +// using Microsoft.Extensions.Configuration; +// using Microsoft.Extensions.Logging; +// using Microsoft.Extensions.Options; +// +// namespace AspNetCore3.Controllers +// { +// [ApiController] +// [Route("[controller]")] +// public class WeatherForecastController : ControllerBase +// { +// private static readonly string[] Summaries = +// { +// "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +// }; +// +// private readonly ILogger _logger; +// private AppSetting _appSetting; +// private IConfiguration _config; +// private Player _player1; +// private Player _player2; +// +// // TODO:依賴 AppSetting +// // public WeatherForecastController(AppSetting appSetting) +// // { +// // this._appSetting = appSetting; +// // } +// +// // TODO:依賴 IOptions +// public WeatherForecastController(IOptions options) +// { +// try +// { +// this._appSetting = options.Value; +// } +// catch (OptionsValidationException ex) +// { +// foreach (var failure in ex.Failures) +// { +// Console.WriteLine(failure); +// } +// } +// } +// +// // TODO:依賴 IOptionsSnapshot +// //public WeatherForecastController(IOptionsSnapshot options) +// //{ +// // this._player1 = options.Get("Player1"); +// // this._player2 = options.Get("Player2"); +// //} +// +// //// TODO:依賴 IOptionsMonitor +// //public WeatherForecastController(IOptionsMonitor options) +// //{ +// // this._player1 = options.Get("Player1"); +// // this._player2 = options.Get("Player2"); +// //} +// +// // TODO:依賴 IConfiguration +// //public WeatherForecastController(IConfiguration config) +// //{ +// // this._config = config; +// //} +// +// // public WeatherForecastController(IOptions options, IConfiguration config) +// // { +// // this._config = config; +// // var appSetting = new AppSetting(); +// // config.Bind(appSetting); +// // this._appSetting = options.Value; +// // } +// +// //public WeatherForecastController(ILogger logger) +// //{ +// // _logger = logger; +// //} +// +// [HttpGet] +// public IEnumerable Get() +// { +// var rng = new Random(); +// return Enumerable.Range(1, 5).Select(index => new WeatherForecast +// { +// Date = DateTime.Now.AddDays(index), +// TemperatureC = rng.Next(-20, 55), +// Summary = Summaries[rng.Next(Summaries.Length)] +// }) +// .ToArray(); +// } +// } +// } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs index c14cd603..cd938f30 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs @@ -43,9 +43,9 @@ public void ConfigureServices(IServiceCollection services) //`J IConfiguration services.Configure(this.Configuration); - services.Configure("Player", this.Configuration.GetSection("Player")); - services.Configure("Player1", this.Configuration.GetSection("Player1")); + services.Configure("Player1", this.Configuration.GetSection("Player1")); services.Configure("Player2", this.Configuration.GetSection("Player2")); + services.Configure("Player3", this.Configuration.GetSection("Player3")); } } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json b/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json index 8ee8b905..b00bf6b0 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json @@ -11,17 +11,17 @@ "ConnectionStrings": { "DefaultConnectionString": "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" }, - "Player": { - "AppId": "test", - "Key": "12345678990" - }, - "Player1": { "AppId": "player1", - "Key": "player1_123456" + "Key": "12345678990" }, + "Player2": { "AppId": "player2", "Key": "player2_123456" + }, + "Player3": { + "AppId": "player3", + "Key": "player3_123456" } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppServiceWithOptionsMonitor.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppServiceWithOptionsMonitor.cs new file mode 100644 index 00000000..90a837ff --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/AppServiceWithOptionsMonitor.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.Extensions.Options; + +namespace NetFx48 +{ + public class AppServiceWithOptionsMonitor : IAppService + { + private readonly AppSetting1 _appSetting; + private readonly Player1 _player; + + public AppServiceWithOptionsMonitor(IOptionsMonitor appSettingOption, + IOptionsMonitor playerOption) + { + this._player = playerOption.Get("Player"); + this._appSetting = appSettingOption?.CurrentValue; + + Console.WriteLine($"AppSetting.Player.AppId = {this._appSetting.Player.AppId}"); + Console.WriteLine($"Player.AppId = {this._player.AppId}"); + } + + public string GetPlayerId() + { + return this._player.AppId; + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppServiceWithOptionsSnapshot.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppServiceWithOptionsSnapshot.cs new file mode 100644 index 00000000..821b47da --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/AppServiceWithOptionsSnapshot.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.Extensions.Options; + +namespace NetFx48 +{ + public class AppServiceWithOptionsSnapshot : IAppService + { + private readonly AppSetting1 _appSetting; + private readonly Player1 _player; + + public AppServiceWithOptionsSnapshot(IOptionsSnapshot appSettingOption, + IOptionsSnapshot playerOption) + { + this._player = playerOption?.Value; + this._appSetting = appSettingOption?.Value; + + Console.WriteLine($"AppSetting.Player.AppId = {this._appSetting.Player.AppId}"); + Console.WriteLine($"Player.AppId = {this._player.AppId}"); + } + + public string GetPlayerId() + { + return this._player.AppId; + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/Models/Player.cs b/Configuration/NetCore/Lab.Config/NetFx48/Models/Player.cs index 95bb9f28..7b778d7d 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/Models/Player.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/Models/Player.cs @@ -6,4 +6,11 @@ public struct Player public string Key { get; set; } } + + public class Player1 + { + public string AppId { get; set; } + + public string Key { get; set; } + } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs index 8d0c27c2..32f1b6b1 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs @@ -1,9 +1,7 @@ using System; -using System.IO; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Options; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace NetFx48 @@ -18,17 +16,21 @@ public class SurveyOptionTests .ConfigureAppConfiguration((hosting, configBuilder) => { // 1.ŪպA - var environmentName = hosting.Configuration["ENVIRONMENT2"]; - configBuilder.AddJsonFile("appsettings.json",false,true); - configBuilder.AddJsonFile($"appsettings.{environmentName}.json",true,true); + var environmentName = + hosting.Configuration["ENVIRONMENT2"]; + configBuilder.AddJsonFile("appsettings.json", false, true); + configBuilder + .AddJsonFile($"appsettings.{environmentName}.json", + true, true); }) .ConfigureServices((hosting, services) => { // 2.`J Options services.AddOptions(); + // 3. `J IConfiguration services.Configure(hosting.Configuration); - + //`JLA services.AddSingleton(); }) @@ -36,7 +38,72 @@ public class SurveyOptionTests var host = builder.Build(); var service = host.Services.GetService(); var playerId = service.GetPlayerId(); - Console.WriteLine($"PlayerId={playerId}"); + Console.WriteLine($"PlayerId = {playerId}"); + } + + [TestMethod] + public void `JOptionMonitor() + { + var builder = Host.CreateDefaultBuilder() + .ConfigureAppConfiguration((hosting, configBuilder) => + { + // 1.ŪպA + var environmentName = + hosting.Configuration["ENVIRONMENT2"]; + configBuilder.AddJsonFile("appsettings.json", false, true); + configBuilder + .AddJsonFile($"appsettings.{environmentName}.json", + true, true); + }) + .ConfigureServices((hosting, services) => + { + // `J Option M Configuration + services.Configure(hosting.Configuration); + + // `J Option MSw Configuration Section Name + services.Configure("Player", + hosting.Configuration.GetSection("Player")); + + //`JLA + services.AddScoped(); + }) + ; + var host = builder.Build(); + var service = host.Services.GetService(); + var playerId = service.GetPlayerId(); + Console.WriteLine($"PlayerId = {playerId}"); + } + + [TestMethod] + public void `JOptionSnapshot() + { + var builder = Host.CreateDefaultBuilder() + .ConfigureAppConfiguration((hosting, configBuilder) => + { + var environmentName = + hosting.Configuration["ENVIRONMENT2"]; + configBuilder.AddJsonFile("appsettings.json", false, true); + configBuilder + .AddJsonFile($"appsettings.{environmentName}.json", + true, true); + }) + .ConfigureServices((hosting, services) => + { + // `J Option by պA + services.Configure(hosting.Configuration); + + // `J Option by SwպA + services.Configure(hosting.Configuration + .GetSection("Player")); + + //`JLA + services.AddScoped(); + }) + ; + var host = builder.Build(); + var service = host.Services.GetService(); + var playerId = service.GetPlayerId(); + Console.WriteLine($"PlayerId = {playerId}"); } } } \ No newline at end of file From b1510ca564d8c23eb28a8398f3da7b9995f8e544 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 1 Apr 2021 14:14:56 +0800 Subject: [PATCH 017/424] refactor --- .../AspNetCore3/StartupInjectionIOptionsMonitor.cs | 5 +---- .../Lab.Config/AspNetCore3/StartupInjectionOptions.cs | 5 +---- .../AspNetCore3/StartupInjectionOptionsSnapshot.cs | 11 +++-------- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs index cd938f30..9eaa24d2 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs @@ -38,10 +38,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - //`J IOptions - services.AddOptions(); - - //`J IConfiguration + //`J Options M IConfiguration services.Configure(this.Configuration); services.Configure("Player1", this.Configuration.GetSection("Player1")); services.Configure("Player2", this.Configuration.GetSection("Player2")); diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs index 70ec5f2d..e43e24eb 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs @@ -38,10 +38,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - //`J IOptions - services.AddOptions(); - - //`J IConfiguration + //`J Options M IConfiguration services.Configure(this.Configuration); } } diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs index 3b5933bd..c7ffc0ba 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs @@ -38,28 +38,23 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - //`J IOptions - //services.AddOptions(); + // AppSetting services.AddOptions() .ValidateDataAnnotations() .Validate(p => { - if (p.AllowedHosts ==null) + if (p.AllowedHosts == null) { return false; } return true; }, "AllowedHosts must be value"); // Failure message. - ; - - //`J IConfiguration + //`J Options M IConfiguration services.Configure(this.Configuration); services.Configure("Player1", this.Configuration.GetSection("Player1")); services.Configure("Player2", this.Configuration.GetSection("Player2")); - - //services.AddSingleton(Configuration); } } } \ No newline at end of file From 3de04e8891d007b24eb87134dec304ad154f2af7 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 1 Apr 2021 14:15:01 +0800 Subject: [PATCH 018/424] refactor --- .../NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs index 32f1b6b1..d4b51348 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs @@ -25,10 +25,7 @@ public class SurveyOptionTests }) .ConfigureServices((hosting, services) => { - // 2.`J Options - services.AddOptions(); - - // 3. `J IConfiguration + // 2. `J Option M Configuration services.Configure(hosting.Configuration); //`JLA From 1ee3b2a1ae1f7db568d6e98b265a4c9686d8bb69 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 1 Apr 2021 15:01:06 +0800 Subject: [PATCH 019/424] refactor --- .../Controllers/DefaultController.cs | 16 ++++++++++---- .../NetCore/Lab.Config/AspNetCore3/Program.cs | 4 ++-- .../NetCore/Lab.Config/AspNetCore3/Startup.cs | 22 +++++++++++++++++++ .../AspNetCore3/StartupInjectionAppSetting.cs | 14 +++++++----- .../StartupInjectionIOptionsMonitor.cs | 5 ++++- .../AspNetCore3/StartupInjectionOptions.cs | 2 +- .../StartupInjectionOptionsSnapshot.cs | 6 ++++- .../Lab.Config/AspNetCore3/appsettings.json | 1 + 8 files changed, 55 insertions(+), 15 deletions(-) diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs index 48fe7d64..c2653148 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs @@ -10,8 +10,7 @@ namespace AspNetCore3.Controllers [Route("[controller]")] public class DefaultController : ControllerBase { - // [Route("controller/appsettings")] - [Route("appsettings")] + [Route("options/appsettings")] public IActionResult Get() { // var setting = this.GetAppSetting(); @@ -19,8 +18,17 @@ public IActionResult Get() return this.Ok(setting); } - [Route("players/{id}")] - public IActionResult GetPlayer(int id) + [Route("monitor/players/{id}")] + public IActionResult GetMonitorPlayer(int id) + { + var serviceProvider = this.HttpContext.RequestServices; + var playerOption = serviceProvider.GetService>(); + var player = playerOption.Get($"Player{id}"); + return this.Ok(player); + } + + [Route("snapshot/players/{id}")] + public IActionResult GetSnapshotPlayer(int id) { var serviceProvider = this.HttpContext.RequestServices; var playerOption = serviceProvider.GetService>(); diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs index a78569e1..be0f1227 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs @@ -20,10 +20,10 @@ public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { - // webBuilder.UseStartup(); + webBuilder.UseStartup(); // webBuilder.UseStartup(); // webBuilder.UseStartup(); - webBuilder.UseStartup(); + // webBuilder.UseStartup(); // webBuilder.UseStartup(); }); } diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Startup.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Startup.cs index d4ba7974..edcdc609 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Startup.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Startup.cs @@ -37,6 +37,28 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) public void ConfigureServices(IServiceCollection services) { services.AddControllers(); + + // AppSetting + services.AddOptions() + .ValidateDataAnnotations() + .Validate(p => + { + if (p.AllowedHosts == null) + { + return false; + } + + return true; + }, "AllowedHosts must be value"); // Failure message. + + //`J Options M IConfiguration + services.Configure(this.Configuration); + + //`J Options M Configuration Section Name + services.Configure("Player1", this.Configuration.GetSection("Player1")); + services.Configure("Player2", this.Configuration.GetSection("Player2")); + services.Configure("Player3", this.Configuration.GetSection("Player3")); + services.Configure("ConnectionStrings", this.Configuration.GetSection("ConnectionStrings")); } } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionAppSetting.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionAppSetting.cs index 877559e9..9c6d3481 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionAppSetting.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionAppSetting.cs @@ -38,12 +38,14 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - //var appSetting = new AppSetting(); - //this.Configuration.Bind(appSetting); - - ////`J AppSetting - //services.AddSingleton(appSetting); - services.Configure(this.Configuration); + //`J AppSetting + services.AddSingleton(provider => + { + //lazy load + var appSetting = new AppSetting(); + this.Configuration.Bind(appSetting); + return appSetting; + }); } } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs index 9eaa24d2..97d60096 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs @@ -38,7 +38,10 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - //`J Options M IConfiguration + //`J Options M IConfiguration + services.Configure(this.Configuration); + + //`J Options M Configuration Section Name services.Configure(this.Configuration); services.Configure("Player1", this.Configuration.GetSection("Player1")); services.Configure("Player2", this.Configuration.GetSection("Player2")); diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs index e43e24eb..178ddab3 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs @@ -38,7 +38,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - //`J Options M IConfiguration + //`J Options M IConfiguration services.Configure(this.Configuration); } } diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs index c7ffc0ba..44f45b23 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs @@ -51,10 +51,14 @@ public void ConfigureServices(IServiceCollection services) return true; }, "AllowedHosts must be value"); // Failure message. - //`J Options M IConfiguration + //`J Options M IConfiguration + services.Configure(this.Configuration); + + //`J Options M Configuration Section Name services.Configure(this.Configuration); services.Configure("Player1", this.Configuration.GetSection("Player1")); services.Configure("Player2", this.Configuration.GetSection("Player2")); + services.Configure("Player3", this.Configuration.GetSection("Player3")); } } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json b/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json index b00bf6b0..d157c06f 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json @@ -10,6 +10,7 @@ "ConnectionStrings": { "DefaultConnectionString": "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" + "AuthenticationConnectionString": "" }, "Player1": { "AppId": "player1", From 59c970428a1c9c7160d3668a65882cc20f9b9d64 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 1 Apr 2021 16:18:54 +0800 Subject: [PATCH 020/424] refactor --- .../Controllers/WeatherForecastController.cs | 192 +++++++++--------- .../NetCore/Lab.Config/AspNetCore3/Program.cs | 6 +- .../Properties/launchSettings.json | 4 +- .../NetCore/Lab.Config/AspNetCore3/Startup.cs | 8 +- .../Lab.Config/AspNetCore3/appsettings.json | 6 +- .../Lab.Config/AspNetCore5/AspNetCore5.csproj | 18 ++ .../Controllers/DefaultController.cs | 73 +++++++ .../Controllers/WeatherForecastController.cs | 39 ++++ .../NetCore/Lab.Config/AspNetCore5/Program.cs | 23 +++ .../Properties/launchSettings.json | 31 +++ .../NetCore/Lab.Config/AspNetCore5/Startup.cs | 71 +++++++ .../Lab.Config/AspNetCore5/WeatherForecast.cs | 15 ++ .../AspNetCore5/appsettings.Development.json | 9 + .../Lab.Config/AspNetCore5/appsettings.json | 28 +++ .../NetCore/Lab.Config/Lab.Config.sln | 8 + 15 files changed, 428 insertions(+), 103 deletions(-) create mode 100644 Configuration/NetCore/Lab.Config/AspNetCore5/AspNetCore5.csproj create mode 100644 Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs create mode 100644 Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/WeatherForecastController.cs create mode 100644 Configuration/NetCore/Lab.Config/AspNetCore5/Program.cs create mode 100644 Configuration/NetCore/Lab.Config/AspNetCore5/Properties/launchSettings.json create mode 100644 Configuration/NetCore/Lab.Config/AspNetCore5/Startup.cs create mode 100644 Configuration/NetCore/Lab.Config/AspNetCore5/WeatherForecast.cs create mode 100644 Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.Development.json create mode 100644 Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.json diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs index f50947bf..5b4654df 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs @@ -1,95 +1,97 @@ -// using System; -// using System.Collections.Generic; -// using System.Linq; -// using Lab.Infra; -// using Microsoft.AspNetCore.Mvc; -// using Microsoft.Extensions.Configuration; -// using Microsoft.Extensions.Logging; -// using Microsoft.Extensions.Options; -// -// namespace AspNetCore3.Controllers -// { -// [ApiController] -// [Route("[controller]")] -// public class WeatherForecastController : ControllerBase -// { -// private static readonly string[] Summaries = -// { -// "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" -// }; -// -// private readonly ILogger _logger; -// private AppSetting _appSetting; -// private IConfiguration _config; -// private Player _player1; -// private Player _player2; -// -// // TODO:依賴 AppSetting -// // public WeatherForecastController(AppSetting appSetting) -// // { -// // this._appSetting = appSetting; -// // } -// -// // TODO:依賴 IOptions -// public WeatherForecastController(IOptions options) -// { -// try -// { -// this._appSetting = options.Value; -// } -// catch (OptionsValidationException ex) -// { -// foreach (var failure in ex.Failures) -// { -// Console.WriteLine(failure); -// } -// } -// } -// -// // TODO:依賴 IOptionsSnapshot -// //public WeatherForecastController(IOptionsSnapshot options) -// //{ -// // this._player1 = options.Get("Player1"); -// // this._player2 = options.Get("Player2"); -// //} -// -// //// TODO:依賴 IOptionsMonitor -// //public WeatherForecastController(IOptionsMonitor options) -// //{ -// // this._player1 = options.Get("Player1"); -// // this._player2 = options.Get("Player2"); -// //} -// -// // TODO:依賴 IConfiguration -// //public WeatherForecastController(IConfiguration config) -// //{ -// // this._config = config; -// //} -// -// // public WeatherForecastController(IOptions options, IConfiguration config) -// // { -// // this._config = config; -// // var appSetting = new AppSetting(); -// // config.Bind(appSetting); -// // this._appSetting = options.Value; -// // } -// -// //public WeatherForecastController(ILogger logger) -// //{ -// // _logger = logger; -// //} -// -// [HttpGet] -// public IEnumerable Get() -// { -// var rng = new Random(); -// return Enumerable.Range(1, 5).Select(index => new WeatherForecast -// { -// Date = DateTime.Now.AddDays(index), -// TemperatureC = rng.Next(-20, 55), -// Summary = Summaries[rng.Next(Summaries.Length)] -// }) -// .ToArray(); -// } -// } -// } \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using Lab.Infra; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace AspNetCore3.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + private AppSetting _appSetting; + private IConfiguration _config; + private Player _player1; + private Player _player2; + + // TODO:依賴 AppSetting + // public WeatherForecastController(AppSetting appSetting) + // { + // this._appSetting = appSetting; + // } + + // TODO:依賴 IOptions + public WeatherForecastController(IOptions options) + { + + try + { + this._appSetting = options.Value; + } + catch (OptionsValidationException ex) + { + foreach (var failure in ex.Failures) + { + Console.WriteLine(failure); + } + } + } + + // TODO:依賴 IOptionsSnapshot + //public WeatherForecastController(IOptionsSnapshot options) + //{ + // this._player1 = options.Get("Player1"); + // this._player2 = options.Get("Player2"); + //} + + //// TODO:依賴 IOptionsMonitor + //public WeatherForecastController(IOptionsMonitor options) + //{ + // this._player1 = options.Get("Player1"); + // this._player2 = options.Get("Player2"); + //} + + // TODO:依賴 IConfiguration + //public WeatherForecastController(IConfiguration config) + //{ + // this._config = config; + //} + + // public WeatherForecastController(IOptions options, IConfiguration config) + // { + // this._config = config; + // var appSetting = new AppSetting(); + // config.Bind(appSetting); + // this._appSetting = options.Value; + // } + + //public WeatherForecastController(ILogger logger) + //{ + // _logger = logger; + //} + + [HttpGet] + public IEnumerable Get() + { + var rng = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = rng.Next(-20, 55), + Summary = Summaries[rng.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs index be0f1227..83e04777 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs @@ -19,7 +19,11 @@ public static void Main(string[] args) public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => - { + { + webBuilder.ConfigureAppConfiguration(p => + { + p.AddJsonFile("appsettings.json", false, false); + }); webBuilder.UseStartup(); // webBuilder.UseStartup(); // webBuilder.UseStartup(); diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Properties/launchSettings.json b/Configuration/NetCore/Lab.Config/AspNetCore3/Properties/launchSettings.json index 649d735f..546f5d39 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Properties/launchSettings.json +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Properties/launchSettings.json @@ -12,8 +12,8 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "//launchUrl": "weatherforecast", - "launchUrl": "default", + "launchUrl": "weatherforecast", + "//launchUrl": "default", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Startup.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Startup.cs index edcdc609..5faec907 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Startup.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Startup.cs @@ -55,10 +55,10 @@ public void ConfigureServices(IServiceCollection services) services.Configure(this.Configuration); //`J Options M Configuration Section Name - services.Configure("Player1", this.Configuration.GetSection("Player1")); - services.Configure("Player2", this.Configuration.GetSection("Player2")); - services.Configure("Player3", this.Configuration.GetSection("Player3")); - services.Configure("ConnectionStrings", this.Configuration.GetSection("ConnectionStrings")); + // services.Configure("Player1", this.Configuration.GetSection("Player1")); + // services.Configure("Player2", this.Configuration.GetSection("Player2")); + // services.Configure("Player3", this.Configuration.GetSection("Player3")); + // services.Configure("ConnectionStrings", this.Configuration.GetSection("ConnectionStrings")); } } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json b/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json index d157c06f..829fc2f8 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json @@ -9,9 +9,13 @@ "AllowedHosts": "*", "ConnectionStrings": { - "DefaultConnectionString": "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" + "DefaultConnectionString": "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;", "AuthenticationConnectionString": "" }, + "Player": { + "AppId": "player", + "Key": "1234567890" + }, "Player1": { "AppId": "player1", "Key": "12345678990" diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/AspNetCore5.csproj b/Configuration/NetCore/Lab.Config/AspNetCore5/AspNetCore5.csproj new file mode 100644 index 00000000..eae77f5a --- /dev/null +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/AspNetCore5.csproj @@ -0,0 +1,18 @@ + + + + net5.0 + bin + bin\AspNetCore5.xml + + + + + + + + + + + + diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs b/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs new file mode 100644 index 00000000..ee675c6b --- /dev/null +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs @@ -0,0 +1,73 @@ +using System; +using Lab.Infra; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace AspNetCore5.Controllers +{ + [ApiController] + [Route("[controller]")] + public class DefaultController : ControllerBase + { + [HttpGet] + [Route("options/appsettings")] + public IActionResult Get() + { + var serviceProvider = this.HttpContext.RequestServices; + var content = serviceProvider.GetService>()?.Value; + return this.Ok(content); + } + + [HttpGet] + [Route("monitor/players/{id}")] + public IActionResult GetMonitorPlayer(int id) + { + var serviceProvider = this.HttpContext.RequestServices; + var appSettingOptions = serviceProvider.GetService>(); + var playerOptions = serviceProvider.GetService>(); + var content = new + { + App = appSettingOptions?.CurrentValue, + Player = playerOptions?.Get($"Player{id}") + }; + return this.Ok(content); + } + + [HttpGet] + [Route("snapshot/players/{id}")] + public IActionResult GetSnapshotPlayer(int id) + { + var serviceProvider = this.HttpContext.RequestServices; + var appSettingOptions = serviceProvider.GetService>(); + var playerOptions = serviceProvider.GetService>(); + var content = new + { + App = appSettingOptions?.Value, + Player = playerOptions?.Get($"Player{id}") + }; + return this.Ok(content); + } + + private AppSetting GetAppSetting() + { + var serviceProvider = this.HttpContext.RequestServices; + var options = serviceProvider.GetService>(); + return options?.Value; + } + + private AppSetting GetAppSettingMonitor() + { + var serviceProvider = this.HttpContext.RequestServices; + var appsSettingOptions = serviceProvider.GetService>(); + var playerOption = serviceProvider.GetService>(); + var player1 = playerOption.Get("Player1"); + var player2 = playerOption.Get("Player2"); + Console.WriteLine($"player1={player1.AppId}"); + Console.WriteLine($"player2={player2.AppId}"); + + // var appSetting = options.Get("Player1"); + return appsSettingOptions?.CurrentValue; + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/WeatherForecastController.cs b/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..932d39a7 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/WeatherForecastController.cs @@ -0,0 +1,39 @@ +// using System; +// using System.Collections.Generic; +// using System.Linq; +// using System.Threading.Tasks; +// using Microsoft.AspNetCore.Mvc; +// using Microsoft.Extensions.Logging; +// +// namespace AspNetCore5.Controllers +// { +// [ApiController] +// [Route("[controller]")] +// public class WeatherForecastController : ControllerBase +// { +// private static readonly string[] Summaries = new[] +// { +// "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +// }; +// +// private readonly ILogger _logger; +// +// public WeatherForecastController(ILogger logger) +// { +// _logger = logger; +// } +// +// [HttpGet] +// public IEnumerable Get() +// { +// var rng = new Random(); +// return Enumerable.Range(1, 5).Select(index => new WeatherForecast +// { +// Date = DateTime.Now.AddDays(index), +// TemperatureC = rng.Next(-20, 55), +// Summary = Summaries[rng.Next(Summaries.Length)] +// }) +// .ToArray(); +// } +// } +// } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/Program.cs b/Configuration/NetCore/Lab.Config/AspNetCore5/Program.cs new file mode 100644 index 00000000..2c140db5 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/Program.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace AspNetCore5 +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/Properties/launchSettings.json b/Configuration/NetCore/Lab.Config/AspNetCore5/Properties/launchSettings.json new file mode 100644 index 00000000..b38cbf0c --- /dev/null +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:32162", + "sslPort": 44347 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "AspNetCore5": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/Startup.cs b/Configuration/NetCore/Lab.Config/AspNetCore5/Startup.cs new file mode 100644 index 00000000..6fbe80e2 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/Startup.cs @@ -0,0 +1,71 @@ +using Lab.Infra; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.OpenApi.Models; + +namespace AspNetCore5 +{ + public class Startup + { + public IConfiguration Configuration { get; } + + public Startup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "AspNetCore5 v1")); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo {Title = "AspNetCore5", Version = "v1"}); + }); + + // AppSetting + services.AddOptions() + .ValidateDataAnnotations() + .Validate(p => + { + if (p.AllowedHosts == null) + { + return false; + } + + return true; + }, "AllowedHosts must be value"); // Failure message. + + //`J Options M IConfiguration + services.Configure(this.Configuration); + + //`J Options M Configuration Section Name + services.Configure("Player1", this.Configuration.GetSection("Player1")); + services.Configure("Player2", this.Configuration.GetSection("Player2")); + services.Configure("Player3", this.Configuration.GetSection("Player3")); + services.Configure("ConnectionStrings", this.Configuration.GetSection("ConnectionStrings")); + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/WeatherForecast.cs b/Configuration/NetCore/Lab.Config/AspNetCore5/WeatherForecast.cs new file mode 100644 index 00000000..393f1d76 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/WeatherForecast.cs @@ -0,0 +1,15 @@ +using System; + +namespace AspNetCore5 +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int) (TemperatureC / 0.5556); + + public string Summary { get; set; } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.Development.json b/Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.Development.json new file mode 100644 index 00000000..8983e0fc --- /dev/null +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.json b/Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.json new file mode 100644 index 00000000..559f064e --- /dev/null +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.json @@ -0,0 +1,28 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + + "ConnectionStrings": { + "DefaultConnectionString": "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;", + "AuthenticationConnectionString": "" + }, + "Player1": { + "AppId": "player1", + "Key": "12345678990" + }, + + "Player2": { + "AppId": "player2", + "Key": "player2_123456" + }, + "Player3": { + "AppId": "player3", + "Key": "player3_123456" + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/Lab.Config.sln b/Configuration/NetCore/Lab.Config/Lab.Config.sln index b490ce4e..11296a46 100644 --- a/Configuration/NetCore/Lab.Config/Lab.Config.sln +++ b/Configuration/NetCore/Lab.Config/Lab.Config.sln @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore3", "AspNetCore3\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetFx48", "NetFx48\NetFx48.csproj", "{D0B23D3B-F2E0-4B38-8864-F3FF60EE115B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCore5", "AspNetCore5\AspNetCore5.csproj", "{5D04A967-5E69-4CD7-AE41-F8BAD30D7DC2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,6 +44,12 @@ Global {D0B23D3B-F2E0-4B38-8864-F3FF60EE115B}.Release|Any CPU.Build.0 = Release|Any CPU {D0B23D3B-F2E0-4B38-8864-F3FF60EE115B}.QA|Any CPU.ActiveCfg = QA|Any CPU {D0B23D3B-F2E0-4B38-8864-F3FF60EE115B}.QA|Any CPU.Build.0 = QA|Any CPU + {5D04A967-5E69-4CD7-AE41-F8BAD30D7DC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D04A967-5E69-4CD7-AE41-F8BAD30D7DC2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D04A967-5E69-4CD7-AE41-F8BAD30D7DC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D04A967-5E69-4CD7-AE41-F8BAD30D7DC2}.Release|Any CPU.Build.0 = Release|Any CPU + {5D04A967-5E69-4CD7-AE41-F8BAD30D7DC2}.QA|Any CPU.ActiveCfg = Debug|Any CPU + {5D04A967-5E69-4CD7-AE41-F8BAD30D7DC2}.QA|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From b271db9d07ad6c49bbfafabafc98d1674577e5e5 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 1 Apr 2021 17:20:21 +0800 Subject: [PATCH 021/424] refactor --- .../Lab.Config/AspNetCore5/AspNetCore5.csproj | 1 + .../Controllers/DefaultController.cs | 12 ++++++++++ .../NetCore/Lab.Config/AspNetCore5/Program.cs | 24 +++++++++++-------- .../NetCore/Lab.Config/AspNetCore5/Startup.cs | 8 +++++++ .../Lab.Config/AspNetCore5/appsettings.json | 4 ++++ 5 files changed, 39 insertions(+), 10 deletions(-) diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/AspNetCore5.csproj b/Configuration/NetCore/Lab.Config/AspNetCore5/AspNetCore5.csproj index eae77f5a..60048b36 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore5/AspNetCore5.csproj +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/AspNetCore5.csproj @@ -8,6 +8,7 @@ + diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs b/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs index ee675c6b..db606bcc 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs @@ -1,6 +1,7 @@ using System; using Lab.Infra; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -19,6 +20,17 @@ public IActionResult Get() return this.Ok(content); } + [HttpGet] + [Route("config/appsettings")] + public IActionResult GetConfig() + { + var serviceProvider = this.HttpContext.RequestServices; + var config = serviceProvider.GetService(); + var content = new AppSetting(); + config.Bind(content); + return this.Ok(content); + } + [HttpGet] [Route("monitor/players/{id}")] public IActionResult GetMonitorPlayer(int id) diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/Program.cs b/Configuration/NetCore/Lab.Config/AspNetCore5/Program.cs index 2c140db5..861aa8ce 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore5/Program.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/Program.cs @@ -1,23 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; namespace AspNetCore5 { public class Program { + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.ConfigureAppConfiguration(p => + { + // sJպA + //p.AddJsonFile("appsettings.json", false, false); + }); + webBuilder.UseStartup(); + }); + } + public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/Startup.cs b/Configuration/NetCore/Lab.Config/AspNetCore5/Startup.cs index 6fbe80e2..1a7bd75c 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore5/Startup.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/Startup.cs @@ -66,6 +66,14 @@ public void ConfigureServices(IServiceCollection services) services.Configure("Player2", this.Configuration.GetSection("Player2")); services.Configure("Player3", this.Configuration.GetSection("Player3")); services.Configure("ConnectionStrings", this.Configuration.GetSection("ConnectionStrings")); + // services.PostConfigure("Player1", config => + // { + // config.AppId = "post_configured_option1_value"; + // }); + // services.PostConfigureAll(config => + // { + // config.Player.AppId = "post_configured_option1_value"; + // }); } } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.json b/Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.json index 559f064e..ee3bc5a4 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.json +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.json @@ -12,6 +12,10 @@ "DefaultConnectionString": "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;", "AuthenticationConnectionString": "" }, + "Player": { + "AppId": "player23", + "Key": "1234567890" + }, "Player1": { "AppId": "player1", "Key": "12345678990" From 300ce4a91c1170365b7cebde302ad2d9dd92928d Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 2 Apr 2021 12:41:51 +0800 Subject: [PATCH 022/424] refactor --- .../NetCore/Lab.Config/NetFx48/AppService.cs | 40 ------------------- .../NetCore/Lab.Config/NetFx48/AppWorkFlow.cs | 19 +++++++++ .../NetFx48/AppWorkFlowWithOption.cs | 19 +++++++++ ...or.cs => AppWorkFlowWithOptionsMonitor.cs} | 4 +- ...t.cs => AppWorkFlowWithOptionsSnapshot.cs} | 4 +- .../Lab.Config/NetFx48/IAppWorkFlow.cs | 7 ++++ .../SurveyCommandConfigurationTests.cs | 2 +- .../NetFx48/SurveyJsonConfigurationTests.cs | 4 +- .../Lab.Config/NetFx48/SurveyOptionTests.cs | 12 +++--- 9 files changed, 58 insertions(+), 53 deletions(-) delete mode 100644 Configuration/NetCore/Lab.Config/NetFx48/AppService.cs create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlow.cs create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs rename Configuration/NetCore/Lab.Config/NetFx48/{AppServiceWithOptionsMonitor.cs => AppWorkFlowWithOptionsMonitor.cs} (81%) rename Configuration/NetCore/Lab.Config/NetFx48/{AppServiceWithOptionsSnapshot.cs => AppWorkFlowWithOptionsSnapshot.cs} (80%) create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/IAppWorkFlow.cs diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs deleted file mode 100644 index 7bf16e6f..00000000 --- a/Configuration/NetCore/Lab.Config/NetFx48/AppService.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Options; - -namespace NetFx48 -{ - public interface IAppService - { - string GetPlayerId(); - } - - public class AppService : IAppService - { - private readonly IConfiguration _config; - - public AppService(IConfiguration config) - { - this._config = config; - } - - public string GetPlayerId() - { - return this._config.GetSection("Player:AppId").Value; - } - } - - public class AppServiceWithOption : IAppService - { - private readonly AppSetting1 _appSetting; - - public AppServiceWithOption(IOptions options) - { - this._appSetting = options.Value; - } - - public string GetPlayerId() - { - return this._appSetting.Player.AppId; - } - } -} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlow.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlow.cs new file mode 100644 index 00000000..85244965 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlow.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Configuration; + +namespace NetFx48 +{ + public class AppWorkFlow : IAppWorkFlow + { + private readonly IConfiguration _config; + + public AppWorkFlow(IConfiguration config) + { + this._config = config; + } + + public string GetPlayerId() + { + return this._config.GetSection("Player:AppId").Value; + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs new file mode 100644 index 00000000..d6e6328b --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Options; + +namespace NetFx48 +{ + public class AppWorkFlowWithOption : IAppWorkFlow + { + private readonly AppSetting1 _appSetting; + + public AppWorkFlowWithOption(IOptions options) + { + this._appSetting = options.Value; + } + + public string GetPlayerId() + { + return this._appSetting.Player.AppId; + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppServiceWithOptionsMonitor.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOptionsMonitor.cs similarity index 81% rename from Configuration/NetCore/Lab.Config/NetFx48/AppServiceWithOptionsMonitor.cs rename to Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOptionsMonitor.cs index 90a837ff..97fe82cb 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/AppServiceWithOptionsMonitor.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOptionsMonitor.cs @@ -3,12 +3,12 @@ namespace NetFx48 { - public class AppServiceWithOptionsMonitor : IAppService + public class AppWorkFlowWithOptionsMonitor : IAppWorkFlow { private readonly AppSetting1 _appSetting; private readonly Player1 _player; - public AppServiceWithOptionsMonitor(IOptionsMonitor appSettingOption, + public AppWorkFlowWithOptionsMonitor(IOptionsMonitor appSettingOption, IOptionsMonitor playerOption) { this._player = playerOption.Get("Player"); diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppServiceWithOptionsSnapshot.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOptionsSnapshot.cs similarity index 80% rename from Configuration/NetCore/Lab.Config/NetFx48/AppServiceWithOptionsSnapshot.cs rename to Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOptionsSnapshot.cs index 821b47da..72a1f7c6 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/AppServiceWithOptionsSnapshot.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOptionsSnapshot.cs @@ -3,12 +3,12 @@ namespace NetFx48 { - public class AppServiceWithOptionsSnapshot : IAppService + public class AppWorkFlowWithOptionsSnapshot : IAppWorkFlow { private readonly AppSetting1 _appSetting; private readonly Player1 _player; - public AppServiceWithOptionsSnapshot(IOptionsSnapshot appSettingOption, + public AppWorkFlowWithOptionsSnapshot(IOptionsSnapshot appSettingOption, IOptionsSnapshot playerOption) { this._player = playerOption?.Value; diff --git a/Configuration/NetCore/Lab.Config/NetFx48/IAppWorkFlow.cs b/Configuration/NetCore/Lab.Config/NetFx48/IAppWorkFlow.cs new file mode 100644 index 00000000..3c7de088 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/IAppWorkFlow.cs @@ -0,0 +1,7 @@ +namespace NetFx48 +{ + public interface IAppWorkFlow + { + string GetPlayerId(); + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyCommandConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyCommandConfigurationTests.cs index 7c00b012..6e1eaddb 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyCommandConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyCommandConfigurationTests.cs @@ -58,7 +58,7 @@ public void 命令對應_Host(string[] args) .ConfigureServices(service => { //DI - service.AddScoped(typeof(AppService)); + service.AddScoped(typeof(AppWorkFlow)); }) ; var host = builder.Build(); diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs index 537cba32..05f034b6 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs @@ -67,11 +67,11 @@ public void 注入Configuration() .ConfigureServices(service => { //DI - service.AddScoped(typeof(AppService)); + service.AddScoped(typeof(AppWorkFlow)); }); var host = builder.Build(); - var appService = host.Services.GetService(); + var appService = host.Services.GetService(); var playerId = appService.GetPlayerId(); Console.WriteLine($"AppId = {playerId}"); } diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs index d4b51348..8a9edb35 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs @@ -29,11 +29,11 @@ public class SurveyOptionTests services.Configure(hosting.Configuration); //`JLA - services.AddSingleton(); + services.AddSingleton(); }) ; var host = builder.Build(); - var service = host.Services.GetService(); + var service = host.Services.GetService(); var playerId = service.GetPlayerId(); Console.WriteLine($"PlayerId = {playerId}"); } @@ -62,11 +62,11 @@ public class SurveyOptionTests hosting.Configuration.GetSection("Player")); //`JLA - services.AddScoped(); + services.AddScoped(); }) ; var host = builder.Build(); - var service = host.Services.GetService(); + var service = host.Services.GetService(); var playerId = service.GetPlayerId(); Console.WriteLine($"PlayerId = {playerId}"); } @@ -94,11 +94,11 @@ public class SurveyOptionTests .GetSection("Player")); //`JLA - services.AddScoped(); + services.AddScoped(); }) ; var host = builder.Build(); - var service = host.Services.GetService(); + var service = host.Services.GetService(); var playerId = service.GetPlayerId(); Console.WriteLine($"PlayerId = {playerId}"); } From 9e574c0cde4afef3cfebe1ad9867e698847d7f01 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 2 Apr 2021 13:06:16 +0800 Subject: [PATCH 023/424] refactor --- .../Lab.Config/AspNetCore5/Controllers/DefaultController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs b/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs index db606bcc..1c9c318c 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs @@ -43,6 +43,10 @@ public IActionResult GetMonitorPlayer(int id) App = appSettingOptions?.CurrentValue, Player = playerOptions?.Get($"Player{id}") }; + appSettingOptions.OnChange(p => + { + Console.WriteLine("`Iwܧ"); + }); return this.Ok(content); } From d71018660f52cb1809bddbd673aced078fd91a3d Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 2 Apr 2021 13:41:27 +0800 Subject: [PATCH 024/424] refactor --- .../NetCore/Lab.Config/NetFx48/NetFx48.csproj | 1 + .../Lab.Config/NetFx48/SurveyOptionTests.cs | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj index 679a98ac..954e0d74 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj +++ b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj @@ -28,6 +28,7 @@ + diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs index 8a9edb35..7106c0b3 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs @@ -102,5 +102,49 @@ public class SurveyOptionTests var playerId = service.GetPlayerId(); Console.WriteLine($"PlayerId = {playerId}"); } + + [TestMethod] + public void () + { + var builder = Host.CreateDefaultBuilder() + .ConfigureAppConfiguration((hosting, configBuilder) => + { + // 1.ŪպA + var environmentName = + hosting.Configuration["ENVIRONMENT2"]; + configBuilder.AddJsonFile("appsettings.json", false, true); + configBuilder + .AddJsonFile($"appsettings.{environmentName}.json", + true, true); + }) + .ConfigureServices((hosting, services) => + { + // 2. `J Option M Configuration + services.Configure(hosting.Configuration); + // + services.AddOptions() + .ValidateDataAnnotations() + .Validate(p => + { + if (p.ConnectionStrings + .DefaultConnectionString == null) + { + return false; + } + + return true; + }, + "DefaultConnectionString must be value"); // Failure message. + ; + + //`JLA + services.AddSingleton(); + }) + ; + var host = builder.Build(); + var service = host.Services.GetService(); + var playerId = service.GetPlayerId(); + Console.WriteLine($"PlayerId = {playerId}"); + } } } \ No newline at end of file From 5f5bfa3776d2c63fd0a3af4798bfdb2fe7258fc9 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 2 Apr 2021 13:59:30 +0800 Subject: [PATCH 025/424] refactor --- .../Lab.Config/NetFx48/AppWorkFlowWithOption.cs | 15 +++++++++++++-- .../Lab.Config/NetFx48/SurveyOptionTests.cs | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs index d6e6328b..654999c7 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Options; +using System; +using Microsoft.Extensions.Options; namespace NetFx48 { @@ -8,7 +9,17 @@ public class AppWorkFlowWithOption : IAppWorkFlow public AppWorkFlowWithOption(IOptions options) { - this._appSetting = options.Value; + try + { + this._appSetting = options.Value; + } + catch (OptionsValidationException ex) + { + foreach (var failure in ex.Failures) + { + Console.WriteLine(failure); + } + } } public string GetPlayerId() diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs index 7106c0b3..985fc309 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs @@ -126,8 +126,8 @@ public class SurveyOptionTests .ValidateDataAnnotations() .Validate(p => { - if (p.ConnectionStrings - .DefaultConnectionString == null) + var hasContent = string.IsNullOrWhiteSpace(p.ConnectionStrings.DefaultConnectionString); + if (hasContent == false) { return false; } From 8bfee5b5db2d34885324084e005c26a8777562fc Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 2 Apr 2021 14:17:59 +0800 Subject: [PATCH 026/424] refactor --- .../Controllers/DefaultController.cs | 28 ++----------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs index c2653148..0ad61e02 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs @@ -1,4 +1,3 @@ -using System; using Lab.Infra; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; @@ -13,9 +12,9 @@ public class DefaultController : ControllerBase [Route("options/appsettings")] public IActionResult Get() { - // var setting = this.GetAppSetting(); - var setting = this.GetAppSettingMonitor(); - return this.Ok(setting); + var serviceProvider = this.HttpContext.RequestServices; + var options = serviceProvider.GetService>(); + return this.Ok(options?.Value); } [Route("monitor/players/{id}")] @@ -35,26 +34,5 @@ public IActionResult GetSnapshotPlayer(int id) var player = playerOption.Get($"Player{id}"); return this.Ok(player); } - - private AppSetting GetAppSetting() - { - var serviceProvider = this.HttpContext.RequestServices; - var options = serviceProvider.GetService>(); - return options?.Value; - } - - private AppSetting GetAppSettingMonitor() - { - var serviceProvider = this.HttpContext.RequestServices; - var appsSettingOptions = serviceProvider.GetService>(); - var playerOption = serviceProvider.GetService>(); - var player1 = playerOption.Get("Player1"); - var player2 = playerOption.Get("Player2"); - Console.WriteLine($"player1={player1.AppId}"); - Console.WriteLine($"player2={player2.AppId}"); - - // var appSetting = options.Get("Player1"); - return appsSettingOptions?.CurrentValue; - } } } \ No newline at end of file From d6439753721f08b63e11bc4777f6aaaccdd54e42 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 2 Apr 2021 14:39:42 +0800 Subject: [PATCH 027/424] refactor --- .../Lab.Config/NetFx48/AppWorkFlow1.cs | 16 ++++++++++ .../NetFx48/AppWorkFlowWithOption.cs | 2 +- .../Lab.Config/NetFx48/SurveyOptionTests.cs | 32 ++++++++++++++++++- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlow1.cs diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlow1.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlow1.cs new file mode 100644 index 00000000..dfaf1482 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlow1.cs @@ -0,0 +1,16 @@ +namespace NetFx48 +{ + public class AppWorkFlow1 : IAppWorkFlow + { + private AppSetting _appSetting; + + public AppWorkFlow1(AppSetting appSetting) + { + this._appSetting = appSetting; + } + public string GetPlayerId() + { + return this._appSetting.Player.AppId; + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs index 654999c7..6dd2db39 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs @@ -21,7 +21,7 @@ public AppWorkFlowWithOption(IOptions options) } } } - + public string GetPlayerId() { return this._appSetting.Player.AppId; diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs index 985fc309..6845a811 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs @@ -103,7 +103,7 @@ public class SurveyOptionTests Console.WriteLine($"PlayerId = {playerId}"); } - [TestMethod] +[TestMethod] public void () { var builder = Host.CreateDefaultBuilder() @@ -146,5 +146,35 @@ public class SurveyOptionTests var playerId = service.GetPlayerId(); Console.WriteLine($"PlayerId = {playerId}"); } + + [TestMethod] + public void `JպA() + { + var builder = Host.CreateDefaultBuilder() + .ConfigureAppConfiguration((hosting, configBuilder) => + { + // 1.ŪպA + var environmentName = + hosting.Configuration["ENVIRONMENT2"]; + configBuilder.AddJsonFile("appsettings.json", false, true); + configBuilder + .AddJsonFile($"appsettings.{environmentName}.json", + true, true); + }) + .ConfigureServices((hosting, services) => + { + var appSetting = hosting.Configuration.Get(); + services.AddSingleton(typeof(AppSetting), appSetting); + + //`JLA + services.AddSingleton(); + }) + ; + var host = builder.Build(); + var service = host.Services.GetService(); + var playerId = service.GetPlayerId(); + Console.WriteLine($"PlayerId = {playerId}"); + } + } } \ No newline at end of file From 2039ba9681ea8a21296a95cfc806f8726f452aaf Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Sun, 4 Apr 2021 12:25:47 +0800 Subject: [PATCH 028/424] refactor --- .../Controllers/DefaultController.cs | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs b/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs index 1c9c318c..3f0d4c4b 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs @@ -64,26 +64,5 @@ public IActionResult GetSnapshotPlayer(int id) }; return this.Ok(content); } - - private AppSetting GetAppSetting() - { - var serviceProvider = this.HttpContext.RequestServices; - var options = serviceProvider.GetService>(); - return options?.Value; - } - - private AppSetting GetAppSettingMonitor() - { - var serviceProvider = this.HttpContext.RequestServices; - var appsSettingOptions = serviceProvider.GetService>(); - var playerOption = serviceProvider.GetService>(); - var player1 = playerOption.Get("Player1"); - var player2 = playerOption.Get("Player2"); - Console.WriteLine($"player1={player1.AppId}"); - Console.WriteLine($"player2={player2.AppId}"); - - // var appSetting = options.Get("Player1"); - return appsSettingOptions?.CurrentValue; - } } } \ No newline at end of file From 5ce55a5ca040a9343bf8841c25780045d2fa373e Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Sun, 4 Apr 2021 16:45:55 +0800 Subject: [PATCH 029/424] refactor --- Host/ConsoleAppNetFx48/AppHost.cs | 35 +++++++++++++ .../ConsoleAppNetFx48.csproj | 11 ++++ .../ConsoleAppNetFx48/LabBackgroundService.cs | 27 ++++++++++ Host/ConsoleAppNetFx48/LabHostedService.cs | 51 +++++++++++++++++++ Host/ConsoleAppNetFx48/Program.cs | 12 +++++ Host/Lab.MsHost.sln | 16 ++++-- Host/NetFx48/AppHost.cs | 35 +++++++++++++ Host/NetFx48/LabBackgroundService.cs | 27 ++++++++++ Host/NetFx48/LabHostedService.cs | 51 +++++++++++++++++++ Host/NetFx48/NetFx48.csproj | 11 ++++ Host/NetFx48/Program.cs | 12 +++++ 11 files changed, 283 insertions(+), 5 deletions(-) create mode 100644 Host/ConsoleAppNetFx48/AppHost.cs create mode 100644 Host/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj create mode 100644 Host/ConsoleAppNetFx48/LabBackgroundService.cs create mode 100644 Host/ConsoleAppNetFx48/LabHostedService.cs create mode 100644 Host/ConsoleAppNetFx48/Program.cs create mode 100644 Host/NetFx48/AppHost.cs create mode 100644 Host/NetFx48/LabBackgroundService.cs create mode 100644 Host/NetFx48/LabHostedService.cs create mode 100644 Host/NetFx48/NetFx48.csproj create mode 100644 Host/NetFx48/Program.cs diff --git a/Host/ConsoleAppNetFx48/AppHost.cs b/Host/ConsoleAppNetFx48/AppHost.cs new file mode 100644 index 00000000..23d90627 --- /dev/null +++ b/Host/ConsoleAppNetFx48/AppHost.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace ConsoleAppNetFx48 +{ + public class AppHost : IHostedService + { + private readonly ILogger logger; + private readonly IHostApplicationLifetime appLifetime; + + public AppHost(ILogger logger, IHostApplicationLifetime appLifetime) + { + this.logger = logger; + this.appLifetime = appLifetime; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + this.logger.LogWarning("App running at: {time}", DateTimeOffset.Now); + + await Task.Yield(); + + this.appLifetime.StopApplication(); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + this.logger.LogWarning("App stopped at: {time}", DateTimeOffset.Now); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Host/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj b/Host/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj new file mode 100644 index 00000000..bd4231b7 --- /dev/null +++ b/Host/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj @@ -0,0 +1,11 @@ + + + + Exe + net5.0 + + + + + + diff --git a/Host/ConsoleAppNetFx48/LabBackgroundService.cs b/Host/ConsoleAppNetFx48/LabBackgroundService.cs new file mode 100644 index 00000000..6436c681 --- /dev/null +++ b/Host/ConsoleAppNetFx48/LabBackgroundService.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace ConsoleAppNetFx48 +{ + public class LabBackgroundService : BackgroundService + { + private readonly ILogger _logger; + + public LabBackgroundService(ILogger logger) + { + this._logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + this._logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); + await Task.Delay(1000, stoppingToken); + } + } + } +} \ No newline at end of file diff --git a/Host/ConsoleAppNetFx48/LabHostedService.cs b/Host/ConsoleAppNetFx48/LabHostedService.cs new file mode 100644 index 00000000..f97f2467 --- /dev/null +++ b/Host/ConsoleAppNetFx48/LabHostedService.cs @@ -0,0 +1,51 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace ConsoleAppNetFx48 +{ + public class LabHostedService : IHostedService + { + private readonly ILogger _logger; + + public LabHostedService(ILogger logger, + IHostApplicationLifetime lifetime) + { + this._logger = logger; + + lifetime.ApplicationStarted.Register(this.OnStarted); + lifetime.ApplicationStopping.Register(this.OnStopping); + lifetime.ApplicationStopped.Register(this.OnStopped); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + this._logger.LogInformation("1. StartAsync has been called."); + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + this._logger.LogInformation("4. StopAsync has been called."); + + return Task.CompletedTask; + } + + private void OnStarted() + { + this._logger.LogInformation("2. OnStarted has been called."); + } + + private void OnStopped() + { + this._logger.LogInformation("5. OnStopped has been called."); + } + + private void OnStopping() + { + this._logger.LogInformation("3. OnStopping has been called."); + } + } +} \ No newline at end of file diff --git a/Host/ConsoleAppNetFx48/Program.cs b/Host/ConsoleAppNetFx48/Program.cs new file mode 100644 index 00000000..1c21da12 --- /dev/null +++ b/Host/ConsoleAppNetFx48/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace ConsoleAppNetFx48 +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} \ No newline at end of file diff --git a/Host/Lab.MsHost.sln b/Host/Lab.MsHost.sln index ce7c2c7d..ea780bc4 100644 --- a/Host/Lab.MsHost.sln +++ b/Host/Lab.MsHost.sln @@ -2,7 +2,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleAppNet5", "ConsoleAppNet5\ConsoleAppNet5.csproj", "{1CE278B8-A6DB-422C-B3CD-151A1C5D3B4E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleAppNet48", "ConsoleAppNet48\ConsoleAppNet48.csproj", "{069BA841-E538-4E51-8D1D-F175107B1312}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetFx48", "NetFx48\NetFx48.csproj", "{B565ABD2-BC7D-44B4-8202-EDD30B2FB260}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleAppNetFx48", "ConsoleAppNetFx48\ConsoleAppNetFx48.csproj", "{45E650BE-BBBF-4060-B2CB-049C45B6830D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -14,9 +16,13 @@ Global {1CE278B8-A6DB-422C-B3CD-151A1C5D3B4E}.Debug|Any CPU.Build.0 = Debug|Any CPU {1CE278B8-A6DB-422C-B3CD-151A1C5D3B4E}.Release|Any CPU.ActiveCfg = Release|Any CPU {1CE278B8-A6DB-422C-B3CD-151A1C5D3B4E}.Release|Any CPU.Build.0 = Release|Any CPU - {069BA841-E538-4E51-8D1D-F175107B1312}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {069BA841-E538-4E51-8D1D-F175107B1312}.Debug|Any CPU.Build.0 = Debug|Any CPU - {069BA841-E538-4E51-8D1D-F175107B1312}.Release|Any CPU.ActiveCfg = Release|Any CPU - {069BA841-E538-4E51-8D1D-F175107B1312}.Release|Any CPU.Build.0 = Release|Any CPU + {B565ABD2-BC7D-44B4-8202-EDD30B2FB260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B565ABD2-BC7D-44B4-8202-EDD30B2FB260}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B565ABD2-BC7D-44B4-8202-EDD30B2FB260}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B565ABD2-BC7D-44B4-8202-EDD30B2FB260}.Release|Any CPU.Build.0 = Release|Any CPU + {45E650BE-BBBF-4060-B2CB-049C45B6830D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45E650BE-BBBF-4060-B2CB-049C45B6830D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45E650BE-BBBF-4060-B2CB-049C45B6830D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45E650BE-BBBF-4060-B2CB-049C45B6830D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Host/NetFx48/AppHost.cs b/Host/NetFx48/AppHost.cs new file mode 100644 index 00000000..04545531 --- /dev/null +++ b/Host/NetFx48/AppHost.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace NetFx48 +{ + public class AppHost : IHostedService + { + private readonly ILogger logger; + private readonly IHostApplicationLifetime appLifetime; + + public AppHost(ILogger logger, IHostApplicationLifetime appLifetime) + { + this.logger = logger; + this.appLifetime = appLifetime; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + this.logger.LogWarning("App running at: {time}", DateTimeOffset.Now); + + await Task.Yield(); + + this.appLifetime.StopApplication(); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + this.logger.LogWarning("App stopped at: {time}", DateTimeOffset.Now); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Host/NetFx48/LabBackgroundService.cs b/Host/NetFx48/LabBackgroundService.cs new file mode 100644 index 00000000..69048bdd --- /dev/null +++ b/Host/NetFx48/LabBackgroundService.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace NetFx48 +{ + public class LabBackgroundService : BackgroundService + { + private readonly ILogger _logger; + + public LabBackgroundService(ILogger logger) + { + this._logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + this._logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); + await Task.Delay(1000, stoppingToken); + } + } + } +} \ No newline at end of file diff --git a/Host/NetFx48/LabHostedService.cs b/Host/NetFx48/LabHostedService.cs new file mode 100644 index 00000000..a573516d --- /dev/null +++ b/Host/NetFx48/LabHostedService.cs @@ -0,0 +1,51 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace NetFx48 +{ + public class LabHostedService : IHostedService + { + private readonly ILogger _logger; + + public LabHostedService(ILogger logger, + IHostApplicationLifetime lifetime) + { + this._logger = logger; + + lifetime.ApplicationStarted.Register(this.OnStarted); + lifetime.ApplicationStopping.Register(this.OnStopping); + lifetime.ApplicationStopped.Register(this.OnStopped); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + this._logger.LogInformation("1. StartAsync has been called."); + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + this._logger.LogInformation("4. StopAsync has been called."); + + return Task.CompletedTask; + } + + private void OnStarted() + { + this._logger.LogInformation("2. OnStarted has been called."); + } + + private void OnStopped() + { + this._logger.LogInformation("5. OnStopped has been called."); + } + + private void OnStopping() + { + this._logger.LogInformation("3. OnStopping has been called."); + } + } +} \ No newline at end of file diff --git a/Host/NetFx48/NetFx48.csproj b/Host/NetFx48/NetFx48.csproj new file mode 100644 index 00000000..bd4231b7 --- /dev/null +++ b/Host/NetFx48/NetFx48.csproj @@ -0,0 +1,11 @@ + + + + Exe + net5.0 + + + + + + diff --git a/Host/NetFx48/Program.cs b/Host/NetFx48/Program.cs new file mode 100644 index 00000000..b484980b --- /dev/null +++ b/Host/NetFx48/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace NetFx48 +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} \ No newline at end of file From 644970e7eb98d116ee4ddc7e5ff10f92e50078a8 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Tue, 6 Apr 2021 13:28:13 +0800 Subject: [PATCH 030/424] refactor --- ...yEnvironmentVariablesConfigurationTests.cs | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs index 53043e3f..f5bdd59a 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs @@ -1,6 +1,7 @@ using System; using System.IO; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -16,7 +17,8 @@ public void Host實例化ConfigurationBuilder() .ConfigureAppConfiguration((hosting, configBuilder) => { // config.Sources.Clear(); - + var hostingEnvironmentEnvironmentName = + hosting.HostingEnvironment.EnvironmentName; configBuilder.AddEnvironmentVariables("Custom_"); var configRoot = configBuilder.Build(); @@ -24,14 +26,16 @@ public void Host實例化ConfigurationBuilder() Console .WriteLine($"ASPNETCORE_ENVIRONMENT = {configRoot["ASPNETCORE_ENVIRONMENT"]}"); Console - .WriteLine($"DOTNET_ENVIRONMENT2 = {configRoot["DOTNET_ENVIRONMENT2"]}"); + .WriteLine($"DOTNET_ENVIRONMENT = {configRoot["DOTNET_ENVIRONMENT"]}"); Console - .WriteLine($"CUSTOM_ENVIRONMENT1 = {configRoot["CUSTOM_ENVIRONMENT1"]}"); + .WriteLine($"CUSTOM_ENVIRONMENT = {configRoot["CUSTOM_ENVIRONMENT"]}"); Console .WriteLine($"ENVIRONMENT1 = {configRoot["ENVIRONMENT1"]}"); }) ; - builder.Build(); + var host = builder.Build(); + var environment = host.Services.GetRequiredService(); + Console.WriteLine($"EnvironmentName={environment.EnvironmentName}"); } [TestMethod] @@ -41,16 +45,20 @@ public void 切換組態設定() .ConfigureAppConfiguration((hosting, configBuilder) => { // config.Sources.Clear(); - var environmentName = hosting.Configuration["ENVIRONMENT2"]; - configBuilder.AddJsonFile("appsettings.json",false,true); - configBuilder.AddJsonFile($"appsettings.{environmentName}.json",true,true); + var environmentName = + hosting.Configuration["ENVIRONMENT2"]; + configBuilder.AddJsonFile("appsettings.json", false, true); + configBuilder + .AddJsonFile($"appsettings.{environmentName}.json", + true, true); var configRoot = configBuilder.Build(); - + //讀取組態 Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); Console.WriteLine($"Key = {configRoot["Player:Key"]}"); - Console.WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); + Console + .WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); }) ; builder.Build(); From 0100dc409c9cb51ab371347e109bc9a09fce518f Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Tue, 6 Apr 2021 15:38:51 +0800 Subject: [PATCH 031/424] refactor --- ...urveyEnvironmentVariablesConfigurationTests.cs | 15 +++++++++++++++ .../NetCore/Lab.Config/NetFx48/appsettings.json | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs index f5bdd59a..bf710645 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs @@ -77,5 +77,20 @@ public void 手動實例化ConfigurationBuilder() //讀取組態 Console.WriteLine($"ENVIRONMENT = {configRoot["ENVIRONMENT"]}"); } + + [TestMethod] + public void 設定主機組態() + { + var builder = Host.CreateDefaultBuilder() + .ConfigureHostConfiguration(config => + { + config.AddJsonFile("appsettings.json", false, true); + }) + ; + + var host = builder.Build(); + var environment = host.Services.GetRequiredService(); + Console.WriteLine($"EnvironmentName={environment.EnvironmentName}"); + } } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.json b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.json index da93d1bc..db5d9362 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.json +++ b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.json @@ -2,9 +2,9 @@ "ConnectionStrings": { "DefaultConnectionString": "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" }, - "Player": { "AppId": "player1", "Key": "1234567890" - } + }, + "Environment": "Development" } \ No newline at end of file From 494af98cece0d27a03a58f3ff568b3e8ebb83f2e Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Tue, 6 Apr 2021 18:00:48 +0800 Subject: [PATCH 032/424] refactor --- Host/ConsoleAppNet5/Program.cs | 4 +- Host/ConsoleAppNetFx48/AppHost.cs | 2 +- .../ConsoleAppNetFx48/LabBackgroundService.cs | 41 +++++++++++++++-- Host/ConsoleAppNetFx48/LabHostedService.cs | 44 ++++++++++--------- Host/ConsoleAppNetFx48/Program.cs | 42 ++++++++++++++++-- Host/NetFx48/AppHost.cs | 2 +- Host/NetFx48/LabHostedService.cs | 2 +- Host/NetFx48/Program.cs | 4 +- 8 files changed, 107 insertions(+), 34 deletions(-) diff --git a/Host/ConsoleAppNet5/Program.cs b/Host/ConsoleAppNet5/Program.cs index c6aa3ecd..5c83e5b0 100644 --- a/Host/ConsoleAppNet5/Program.cs +++ b/Host/ConsoleAppNet5/Program.cs @@ -2,9 +2,9 @@ namespace ConsoleAppNet5 { - class Program + internal class Program { - static void Main(string[] args) + private static void Main(string[] args) { Console.WriteLine("Hello World!"); } diff --git a/Host/ConsoleAppNetFx48/AppHost.cs b/Host/ConsoleAppNetFx48/AppHost.cs index 23d90627..aa205ec7 100644 --- a/Host/ConsoleAppNetFx48/AppHost.cs +++ b/Host/ConsoleAppNetFx48/AppHost.cs @@ -8,8 +8,8 @@ namespace ConsoleAppNetFx48 { public class AppHost : IHostedService { - private readonly ILogger logger; private readonly IHostApplicationLifetime appLifetime; + private readonly ILogger logger; public AppHost(ILogger logger, IHostApplicationLifetime appLifetime) { diff --git a/Host/ConsoleAppNetFx48/LabBackgroundService.cs b/Host/ConsoleAppNetFx48/LabBackgroundService.cs index 6436c681..56ea7423 100644 --- a/Host/ConsoleAppNetFx48/LabBackgroundService.cs +++ b/Host/ConsoleAppNetFx48/LabBackgroundService.cs @@ -9,12 +9,22 @@ namespace ConsoleAppNetFx48 public class LabBackgroundService : BackgroundService { private readonly ILogger _logger; - - public LabBackgroundService(ILogger logger) + + public LabBackgroundService(ILogger logger, + IHostApplicationLifetime appLifetime, + IHostLifetime hostLifetime, + IHostEnvironment hostEnvironment) { this._logger = logger; + appLifetime.ApplicationStarted.Register(this.OnStarted); + appLifetime.ApplicationStopping.Register(this.OnStopping); + appLifetime.ApplicationStopped.Register(this.OnStopped); + this._logger.LogInformation($"主機環境:" + + $"ApplicationName = {hostEnvironment.ApplicationName}\r\n" + + $"EnvironmentName = {hostEnvironment.EnvironmentName}\r\n" + + $"RootPath = {hostEnvironment.ContentRootPath}\r\n" + + $"Root File Provider = {hostEnvironment.ContentRootFileProvider}\r\n"); } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) @@ -23,5 +33,30 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) await Task.Delay(1000, stoppingToken); } } + + public Task StartAsync(CancellationToken cancellationToken) + { + this._logger.LogInformation("1. 調用 Host.StartAsync "); + return Task.CompletedTask; + } + private void OnStarted() + { + this._logger.LogInformation("2. 調用 OnStarted"); + } + private void OnStopping() + { + this._logger.LogInformation("3. 調用 OnStopping"); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + this._logger.LogInformation("4. 調用 Host.StopAsync"); + return Task.CompletedTask; + } + + private void OnStopped() + { + this._logger.LogInformation("5. 調用 OnStopped"); + } } } \ No newline at end of file diff --git a/Host/ConsoleAppNetFx48/LabHostedService.cs b/Host/ConsoleAppNetFx48/LabHostedService.cs index f97f2467..4ad14be1 100644 --- a/Host/ConsoleAppNetFx48/LabHostedService.cs +++ b/Host/ConsoleAppNetFx48/LabHostedService.cs @@ -10,42 +10,44 @@ public class LabHostedService : IHostedService private readonly ILogger _logger; public LabHostedService(ILogger logger, - IHostApplicationLifetime lifetime) + IHostApplicationLifetime appLifetime, + IHostLifetime hostLifetime, + IHostEnvironment hostEnvironment) { this._logger = logger; - - lifetime.ApplicationStarted.Register(this.OnStarted); - lifetime.ApplicationStopping.Register(this.OnStopping); - lifetime.ApplicationStopped.Register(this.OnStopped); + appLifetime.ApplicationStarted.Register(this.OnStarted); + appLifetime.ApplicationStopping.Register(this.OnStopping); + appLifetime.ApplicationStopped.Register(this.OnStopped); + this._logger.LogInformation($"主機環境:" + + $"ApplicationName = {hostEnvironment.ApplicationName}\r\n" + + $"EnvironmentName = {hostEnvironment.EnvironmentName}\r\n" + + $"RootPath = {hostEnvironment.ContentRootPath}\r\n" + + $"Root File Provider = {hostEnvironment.ContentRootFileProvider}\r\n"); } - + public Task StartAsync(CancellationToken cancellationToken) { - this._logger.LogInformation("1. StartAsync has been called."); - + this._logger.LogInformation("1. 調用 Host.StartAsync "); return Task.CompletedTask; } - - public Task StopAsync(CancellationToken cancellationToken) + private void OnStarted() { - this._logger.LogInformation("4. StopAsync has been called."); - - return Task.CompletedTask; + this._logger.LogInformation("2. 調用 OnStarted"); } - - private void OnStarted() + private void OnStopping() { - this._logger.LogInformation("2. OnStarted has been called."); + this._logger.LogInformation("3. 調用 OnStopping"); } - private void OnStopped() + public Task StopAsync(CancellationToken cancellationToken) { - this._logger.LogInformation("5. OnStopped has been called."); + this._logger.LogInformation("4. 調用 Host.StopAsync"); + return Task.CompletedTask; } - - private void OnStopping() + + private void OnStopped() { - this._logger.LogInformation("3. OnStopping has been called."); + this._logger.LogInformation("5. 調用 OnStopped"); } } } \ No newline at end of file diff --git a/Host/ConsoleAppNetFx48/Program.cs b/Host/ConsoleAppNetFx48/Program.cs index 1c21da12..0bb61d7a 100644 --- a/Host/ConsoleAppNetFx48/Program.cs +++ b/Host/ConsoleAppNetFx48/Program.cs @@ -1,12 +1,48 @@ using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; namespace ConsoleAppNetFx48 { - class Program + // internal class Program1 + // { + // private static void Main(string[] args) + // { + // var hostBuilder = Host.CreateDefaultBuilder(args) + // .ConfigureServices((hostBuilder, services) => + // { + // services.AddHostedService(); + // Console.WriteLine($"注入 {nameof(LabHostedService)}"); + // }); + // var host = hostBuilder.Build(); + // host.RunAsync(); + // Console.WriteLine($"{nameof(LabHostedService)} 應用程式已啟動"); + // Console.ReadLine(); + // } + // } + + internal class Program { - static void Main(string[] args) + private static Task Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + var task = host.RunAsync(); + Console.WriteLine($"{nameof(LabHostedService)} 應用程式已啟動"); + + return task; + } + + private static IHostBuilder CreateHostBuilder(string[] args) { - Console.WriteLine("Hello World!"); + return Host.CreateDefaultBuilder(args) + .ConfigureServices((hostBuilder, services) => + { + services.AddHostedService(); + services.AddHostedService(); + Console.WriteLine("注入HostService"); + }) + ; } } } \ No newline at end of file diff --git a/Host/NetFx48/AppHost.cs b/Host/NetFx48/AppHost.cs index 04545531..898c3be4 100644 --- a/Host/NetFx48/AppHost.cs +++ b/Host/NetFx48/AppHost.cs @@ -8,8 +8,8 @@ namespace NetFx48 { public class AppHost : IHostedService { - private readonly ILogger logger; private readonly IHostApplicationLifetime appLifetime; + private readonly ILogger logger; public AppHost(ILogger logger, IHostApplicationLifetime appLifetime) { diff --git a/Host/NetFx48/LabHostedService.cs b/Host/NetFx48/LabHostedService.cs index a573516d..92cec54d 100644 --- a/Host/NetFx48/LabHostedService.cs +++ b/Host/NetFx48/LabHostedService.cs @@ -13,7 +13,7 @@ public LabHostedService(ILogger logger, IHostApplicationLifetime lifetime) { this._logger = logger; - + lifetime.ApplicationStarted.Register(this.OnStarted); lifetime.ApplicationStopping.Register(this.OnStopping); lifetime.ApplicationStopped.Register(this.OnStopped); diff --git a/Host/NetFx48/Program.cs b/Host/NetFx48/Program.cs index b484980b..9aeab0ca 100644 --- a/Host/NetFx48/Program.cs +++ b/Host/NetFx48/Program.cs @@ -2,9 +2,9 @@ namespace NetFx48 { - class Program + internal class Program { - static void Main(string[] args) + private static void Main(string[] args) { Console.WriteLine("Hello World!"); } From 209d3e53135e49dc42e5f2fc46feab74bac83f8b Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 7 Apr 2021 10:55:16 +0800 Subject: [PATCH 033/424] refactor --- Host/ConsoleAppNetFx48/LabHostedService.cs | 1 + Host/ConsoleAppNetFx48/Program.cs | 1 + Host/Lab.MsHost.sln | 12 ------------ 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Host/ConsoleAppNetFx48/LabHostedService.cs b/Host/ConsoleAppNetFx48/LabHostedService.cs index 4ad14be1..78670489 100644 --- a/Host/ConsoleAppNetFx48/LabHostedService.cs +++ b/Host/ConsoleAppNetFx48/LabHostedService.cs @@ -23,6 +23,7 @@ public LabHostedService(ILogger logger, $"EnvironmentName = {hostEnvironment.EnvironmentName}\r\n" + $"RootPath = {hostEnvironment.ContentRootPath}\r\n" + $"Root File Provider = {hostEnvironment.ContentRootFileProvider}\r\n"); + } public Task StartAsync(CancellationToken cancellationToken) diff --git a/Host/ConsoleAppNetFx48/Program.cs b/Host/ConsoleAppNetFx48/Program.cs index 0bb61d7a..50d78f7d 100644 --- a/Host/ConsoleAppNetFx48/Program.cs +++ b/Host/ConsoleAppNetFx48/Program.cs @@ -28,6 +28,7 @@ private static Task Main(string[] args) { var host = CreateHostBuilder(args).Build(); var task = host.RunAsync(); + host.WaitForShutdownAsync(); Console.WriteLine($"{nameof(LabHostedService)} 應用程式已啟動"); return task; diff --git a/Host/Lab.MsHost.sln b/Host/Lab.MsHost.sln index ea780bc4..e2e6abce 100644 --- a/Host/Lab.MsHost.sln +++ b/Host/Lab.MsHost.sln @@ -1,9 +1,5 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleAppNet5", "ConsoleAppNet5\ConsoleAppNet5.csproj", "{1CE278B8-A6DB-422C-B3CD-151A1C5D3B4E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetFx48", "NetFx48\NetFx48.csproj", "{B565ABD2-BC7D-44B4-8202-EDD30B2FB260}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleAppNetFx48", "ConsoleAppNetFx48\ConsoleAppNetFx48.csproj", "{45E650BE-BBBF-4060-B2CB-049C45B6830D}" EndProject Global @@ -12,14 +8,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1CE278B8-A6DB-422C-B3CD-151A1C5D3B4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1CE278B8-A6DB-422C-B3CD-151A1C5D3B4E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1CE278B8-A6DB-422C-B3CD-151A1C5D3B4E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1CE278B8-A6DB-422C-B3CD-151A1C5D3B4E}.Release|Any CPU.Build.0 = Release|Any CPU - {B565ABD2-BC7D-44B4-8202-EDD30B2FB260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B565ABD2-BC7D-44B4-8202-EDD30B2FB260}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B565ABD2-BC7D-44B4-8202-EDD30B2FB260}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B565ABD2-BC7D-44B4-8202-EDD30B2FB260}.Release|Any CPU.Build.0 = Release|Any CPU {45E650BE-BBBF-4060-B2CB-049C45B6830D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {45E650BE-BBBF-4060-B2CB-049C45B6830D}.Debug|Any CPU.Build.0 = Debug|Any CPU {45E650BE-BBBF-4060-B2CB-049C45B6830D}.Release|Any CPU.ActiveCfg = Release|Any CPU From 6797d1610fc740b928e4ce485a5d49ec65976630 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 7 Apr 2021 11:01:28 +0800 Subject: [PATCH 034/424] refactor --- Host/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Host/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj b/Host/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj index bd4231b7..aded3afa 100644 --- a/Host/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj +++ b/Host/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj @@ -2,7 +2,7 @@ Exe - net5.0 + net48 From 7d10a3f7659545b26f81509be049caf4d517e107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E5=B0=8F=E7=AB=A0?= Date: Wed, 7 Apr 2021 11:03:01 +0800 Subject: [PATCH 035/424] delete --- Host/ConsoleAppNet48/AppHost.cs | 35 -------------- Host/ConsoleAppNet48/ConsoleAppNet48.csproj | 12 ----- Host/ConsoleAppNet48/LabBackgroundService.cs | 27 ----------- Host/ConsoleAppNet48/LabHostedService.cs | 51 -------------------- Host/ConsoleAppNet48/Program.cs | 22 --------- 5 files changed, 147 deletions(-) delete mode 100644 Host/ConsoleAppNet48/AppHost.cs delete mode 100644 Host/ConsoleAppNet48/ConsoleAppNet48.csproj delete mode 100644 Host/ConsoleAppNet48/LabBackgroundService.cs delete mode 100644 Host/ConsoleAppNet48/LabHostedService.cs delete mode 100644 Host/ConsoleAppNet48/Program.cs diff --git a/Host/ConsoleAppNet48/AppHost.cs b/Host/ConsoleAppNet48/AppHost.cs deleted file mode 100644 index 8e713810..00000000 --- a/Host/ConsoleAppNet48/AppHost.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace ConsoleAppNet48 -{ - public class AppHost : IHostedService - { - private readonly ILogger logger; - private readonly IHostApplicationLifetime appLifetime; - - public AppHost(ILogger logger, IHostApplicationLifetime appLifetime) - { - this.logger = logger; - this.appLifetime = appLifetime; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - this.logger.LogWarning("App running at: {time}", DateTimeOffset.Now); - - await Task.Yield(); - - this.appLifetime.StopApplication(); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - this.logger.LogWarning("App stopped at: {time}", DateTimeOffset.Now); - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/Host/ConsoleAppNet48/ConsoleAppNet48.csproj b/Host/ConsoleAppNet48/ConsoleAppNet48.csproj deleted file mode 100644 index 50724ce9..00000000 --- a/Host/ConsoleAppNet48/ConsoleAppNet48.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - net48 - - - - - - - diff --git a/Host/ConsoleAppNet48/LabBackgroundService.cs b/Host/ConsoleAppNet48/LabBackgroundService.cs deleted file mode 100644 index ccca500a..00000000 --- a/Host/ConsoleAppNet48/LabBackgroundService.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace ConsoleAppNet48 -{ - public class LabBackgroundService : BackgroundService - { - private readonly ILogger _logger; - - public LabBackgroundService(ILogger logger) - { - this._logger = logger; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - while (!stoppingToken.IsCancellationRequested) - { - this._logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); - await Task.Delay(1000, stoppingToken); - } - } - } -} \ No newline at end of file diff --git a/Host/ConsoleAppNet48/LabHostedService.cs b/Host/ConsoleAppNet48/LabHostedService.cs deleted file mode 100644 index b580eb10..00000000 --- a/Host/ConsoleAppNet48/LabHostedService.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace ConsoleAppNet48 -{ - public class LabHostedService : IHostedService - { - private readonly ILogger _logger; - - public LabHostedService(ILogger logger, - IHostApplicationLifetime lifetime) - { - this._logger = logger; - - lifetime.ApplicationStarted.Register(this.OnStarted); - lifetime.ApplicationStopping.Register(this.OnStopping); - lifetime.ApplicationStopped.Register(this.OnStopped); - } - - public Task StartAsync(CancellationToken cancellationToken) - { - this._logger.LogInformation("1. StartAsync has been called."); - - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - this._logger.LogInformation("4. StopAsync has been called."); - - return Task.CompletedTask; - } - - private void OnStarted() - { - this._logger.LogInformation("2. OnStarted has been called."); - } - - private void OnStopped() - { - this._logger.LogInformation("5. OnStopped has been called."); - } - - private void OnStopping() - { - this._logger.LogInformation("3. OnStopping has been called."); - } - } -} \ No newline at end of file diff --git a/Host/ConsoleAppNet48/Program.cs b/Host/ConsoleAppNet48/Program.cs deleted file mode 100644 index ee76ac29..00000000 --- a/Host/ConsoleAppNet48/Program.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace ConsoleAppNet48 -{ - public class Program - { - public static IHostBuilder CreateHostBuilder(string[] args) - { - return Host.CreateDefaultBuilder(args) - .ConfigureServices((hostContext, services) => - { - services.AddHostedService(); - }); - } - - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - } -} \ No newline at end of file From 49060369d5d508bf46c485eb2429f2595e7894be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E5=B0=8F=E7=AB=A0?= Date: Wed, 7 Apr 2021 11:03:15 +0800 Subject: [PATCH 036/424] delete --- Host/ConsoleAppNet5/ConsoleAppNet5.csproj | 8 -------- Host/ConsoleAppNet5/Program.cs | 12 ------------ 2 files changed, 20 deletions(-) delete mode 100644 Host/ConsoleAppNet5/ConsoleAppNet5.csproj delete mode 100644 Host/ConsoleAppNet5/Program.cs diff --git a/Host/ConsoleAppNet5/ConsoleAppNet5.csproj b/Host/ConsoleAppNet5/ConsoleAppNet5.csproj deleted file mode 100644 index 9590466a..00000000 --- a/Host/ConsoleAppNet5/ConsoleAppNet5.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - Exe - net5.0 - - - diff --git a/Host/ConsoleAppNet5/Program.cs b/Host/ConsoleAppNet5/Program.cs deleted file mode 100644 index 5c83e5b0..00000000 --- a/Host/ConsoleAppNet5/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace ConsoleAppNet5 -{ - internal class Program - { - private static void Main(string[] args) - { - Console.WriteLine("Hello World!"); - } - } -} \ No newline at end of file From b2b4931047b86baad63bd360abd5d6bfb795807a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E5=B0=8F=E7=AB=A0?= Date: Wed, 7 Apr 2021 11:03:26 +0800 Subject: [PATCH 037/424] delete --- Host/NetFx48/AppHost.cs | 35 ------------------- Host/NetFx48/LabBackgroundService.cs | 27 --------------- Host/NetFx48/LabHostedService.cs | 51 ---------------------------- Host/NetFx48/NetFx48.csproj | 11 ------ Host/NetFx48/Program.cs | 12 ------- 5 files changed, 136 deletions(-) delete mode 100644 Host/NetFx48/AppHost.cs delete mode 100644 Host/NetFx48/LabBackgroundService.cs delete mode 100644 Host/NetFx48/LabHostedService.cs delete mode 100644 Host/NetFx48/NetFx48.csproj delete mode 100644 Host/NetFx48/Program.cs diff --git a/Host/NetFx48/AppHost.cs b/Host/NetFx48/AppHost.cs deleted file mode 100644 index 898c3be4..00000000 --- a/Host/NetFx48/AppHost.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace NetFx48 -{ - public class AppHost : IHostedService - { - private readonly IHostApplicationLifetime appLifetime; - private readonly ILogger logger; - - public AppHost(ILogger logger, IHostApplicationLifetime appLifetime) - { - this.logger = logger; - this.appLifetime = appLifetime; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - this.logger.LogWarning("App running at: {time}", DateTimeOffset.Now); - - await Task.Yield(); - - this.appLifetime.StopApplication(); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - this.logger.LogWarning("App stopped at: {time}", DateTimeOffset.Now); - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/Host/NetFx48/LabBackgroundService.cs b/Host/NetFx48/LabBackgroundService.cs deleted file mode 100644 index 69048bdd..00000000 --- a/Host/NetFx48/LabBackgroundService.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace NetFx48 -{ - public class LabBackgroundService : BackgroundService - { - private readonly ILogger _logger; - - public LabBackgroundService(ILogger logger) - { - this._logger = logger; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - while (!stoppingToken.IsCancellationRequested) - { - this._logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); - await Task.Delay(1000, stoppingToken); - } - } - } -} \ No newline at end of file diff --git a/Host/NetFx48/LabHostedService.cs b/Host/NetFx48/LabHostedService.cs deleted file mode 100644 index 92cec54d..00000000 --- a/Host/NetFx48/LabHostedService.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace NetFx48 -{ - public class LabHostedService : IHostedService - { - private readonly ILogger _logger; - - public LabHostedService(ILogger logger, - IHostApplicationLifetime lifetime) - { - this._logger = logger; - - lifetime.ApplicationStarted.Register(this.OnStarted); - lifetime.ApplicationStopping.Register(this.OnStopping); - lifetime.ApplicationStopped.Register(this.OnStopped); - } - - public Task StartAsync(CancellationToken cancellationToken) - { - this._logger.LogInformation("1. StartAsync has been called."); - - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - this._logger.LogInformation("4. StopAsync has been called."); - - return Task.CompletedTask; - } - - private void OnStarted() - { - this._logger.LogInformation("2. OnStarted has been called."); - } - - private void OnStopped() - { - this._logger.LogInformation("5. OnStopped has been called."); - } - - private void OnStopping() - { - this._logger.LogInformation("3. OnStopping has been called."); - } - } -} \ No newline at end of file diff --git a/Host/NetFx48/NetFx48.csproj b/Host/NetFx48/NetFx48.csproj deleted file mode 100644 index bd4231b7..00000000 --- a/Host/NetFx48/NetFx48.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - Exe - net5.0 - - - - - - diff --git a/Host/NetFx48/Program.cs b/Host/NetFx48/Program.cs deleted file mode 100644 index 9aeab0ca..00000000 --- a/Host/NetFx48/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace NetFx48 -{ - internal class Program - { - private static void Main(string[] args) - { - Console.WriteLine("Hello World!"); - } - } -} \ No newline at end of file From 6365f1979bad6b0e90bc00667483f918c56eec0d Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 7 Apr 2021 13:57:35 +0800 Subject: [PATCH 038/424] add Lab.WorkerService --- .../2021-03-26_16-34-47_CoverageHistory.xml | 12 + .../report/ClassLibrary1_Calculation.htm | 82 ++ .../Lab.OpenCoverDemo/report/Cobertura.xml | 105 ++ .../report/UnitTestProject1_UnitTest1.htm | 88 ++ .../report/UnitTestProject2_UnitTest1.htm | 75 + .../report/badge_linecoverage.png | Bin 0 -> 2925 bytes .../report/badge_linecoverage.svg | 88 ++ .../Lab.OpenCoverDemo/report/class.js | 207 +++ .../Lab.OpenCoverDemo/report/coverage.xml | 1270 +++++++++++++++++ .../Lab.OpenCoverDemo/report/icon_cube.svg | 2 + .../report/icon_down-dir_active.svg | 2 + .../Lab.OpenCoverDemo/report/icon_fork.svg | 2 + .../report/icon_info-circled.svg | 2 + .../Lab.OpenCoverDemo/report/icon_minus.svg | 2 + .../Lab.OpenCoverDemo/report/icon_plus.svg | 2 + .../report/icon_search-minus.svg | 2 + .../report/icon_search-plus.svg | 2 + .../Lab.OpenCoverDemo/report/icon_up-dir.svg | 2 + .../report/icon_up-dir_active.svg | 2 + .../Lab.OpenCoverDemo/report/icon_wrench.svg | 2 + .../Lab.OpenCoverDemo/report/index.htm | 71 + .../Lab.OpenCoverDemo/report/main.js | 274 ++++ .../Lab.OpenCoverDemo/report/report.css | 357 +++++ .../Lab.Config/ConsoleApp1/ConsoleApp1.csproj | 0 .../Lab.Config/ConsoleApp1}/Program.cs | 6 +- .../Properties/launchSettings.json | 8 + .../NetFx48/Properties/launchSettings.json | 8 + .../ConsoleApp1/ConsoleApp1.csproj | 3 - .../ConsoleApp1}/Program.cs | 6 +- .../WebApi.NetFx48.csproj.DotSettings | 2 + Host/ConsoleAppNet48/AppHost.cs | 35 - Host/ConsoleAppNet48/ConsoleAppNet48.csproj | 12 - Host/ConsoleAppNet48/LabBackgroundService.cs | 27 - Host/ConsoleAppNet48/LabHostedService.cs | 51 - Host/ConsoleAppNet48/Program.cs | 22 - .../ConsoleAppNetFx48/AppHost.cs | 0 .../ConsoleAppNetFx48.csproj | 0 .../ConsoleAppNetFx48/LabBackgroundService.cs | 0 .../ConsoleAppNetFx48/LabHostedService.cs | 0 .../ConsoleAppNetFx48/Program.cs | 0 Host/{ => Lab.MsHost}/Lab.MsHost.sln | 0 .../ConsoleAppNetFx48.csproj | 17 + .../ConsoleAppNetFx48/DoThing.cs | 38 + .../ConsoleAppNetFx48/Program.cs | 38 + .../Properties/launchSettings.json | 11 + .../ConsoleAppNetFx48/Worker.cs} | 10 +- .../appsettings.Development.json | 9 + .../ConsoleAppNetFx48/appsettings.json | 9 + Host/Lab.WorkerService/Lab.WorkerService.sln | 16 + Host/NetFx48/AppHost.cs | 35 - Host/NetFx48/LabHostedService.cs | 51 - .../EntityModel/CopyMe.SqlServer.generated.cs | 216 +++ 52 files changed, 3034 insertions(+), 247 deletions(-) create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/history/2021-03-26_16-34-47_CoverageHistory.xml create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/ClassLibrary1_Calculation.htm create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/Cobertura.xml create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject1_UnitTest1.htm create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject2_UnitTest1.htm create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.png create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.svg create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/class.js create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/coverage.xml create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_cube.svg create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_down-dir_active.svg create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_fork.svg create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_info-circled.svg create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_minus.svg create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_plus.svg create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-minus.svg create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-plus.svg create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir.svg create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir_active.svg create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_wrench.svg create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/index.htm create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/main.js create mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/report.css rename Host/ConsoleAppNet5/ConsoleAppNet5.csproj => Configuration/NetCore/Lab.Config/ConsoleApp1/ConsoleApp1.csproj (100%) rename {Host/NetFx48 => Configuration/NetCore/Lab.Config/ConsoleApp1}/Program.cs (51%) create mode 100644 Configuration/NetCore/Lab.Config/ConsoleApp1/Properties/launchSettings.json create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/Properties/launchSettings.json rename Host/NetFx48/NetFx48.csproj => Coravel/Lab.CoravelScheduler/ConsoleApp1/ConsoleApp1.csproj (59%) rename {Host/ConsoleAppNet5 => Coravel/Lab.CoravelScheduler/ConsoleApp1}/Program.cs (50%) create mode 100644 Coravel/Lab.CoravelScheduler/WebApi.NetFx48/WebApi.NetFx48.csproj.DotSettings delete mode 100644 Host/ConsoleAppNet48/AppHost.cs delete mode 100644 Host/ConsoleAppNet48/ConsoleAppNet48.csproj delete mode 100644 Host/ConsoleAppNet48/LabBackgroundService.cs delete mode 100644 Host/ConsoleAppNet48/LabHostedService.cs delete mode 100644 Host/ConsoleAppNet48/Program.cs rename Host/{ => Lab.MsHost}/ConsoleAppNetFx48/AppHost.cs (100%) rename Host/{ => Lab.MsHost}/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj (100%) rename Host/{ => Lab.MsHost}/ConsoleAppNetFx48/LabBackgroundService.cs (100%) rename Host/{ => Lab.MsHost}/ConsoleAppNetFx48/LabHostedService.cs (100%) rename Host/{ => Lab.MsHost}/ConsoleAppNetFx48/Program.cs (100%) rename Host/{ => Lab.MsHost}/Lab.MsHost.sln (100%) create mode 100644 Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj create mode 100644 Host/Lab.WorkerService/ConsoleAppNetFx48/DoThing.cs create mode 100644 Host/Lab.WorkerService/ConsoleAppNetFx48/Program.cs create mode 100644 Host/Lab.WorkerService/ConsoleAppNetFx48/Properties/launchSettings.json rename Host/{NetFx48/LabBackgroundService.cs => Lab.WorkerService/ConsoleAppNetFx48/Worker.cs} (70%) create mode 100644 Host/Lab.WorkerService/ConsoleAppNetFx48/appsettings.Development.json create mode 100644 Host/Lab.WorkerService/ConsoleAppNetFx48/appsettings.json create mode 100644 Host/Lab.WorkerService/Lab.WorkerService.sln delete mode 100644 Host/NetFx48/AppHost.cs delete mode 100644 Host/NetFx48/LabHostedService.cs create mode 100644 ORM/Linq2Db/Lab.Linq2Db/Lab.UnitTest/EntityModel/CopyMe.SqlServer.generated.cs diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/history/2021-03-26_16-34-47_CoverageHistory.xml b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/history/2021-03-26_16-34-47_CoverageHistory.xml new file mode 100644 index 00000000..7531ba24 --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/history/2021-03-26_16-34-47_CoverageHistory.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/ClassLibrary1_Calculation.htm b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/ClassLibrary1_Calculation.htm new file mode 100644 index 00000000..6976b753 --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/ClassLibrary1_Calculation.htm @@ -0,0 +1,82 @@ + + + + + + +ClassLibrary1.Calculation - Coverage Report + +
+

Summary

+ ++++ + + + + + + + + + + + + + +
Class:ClassLibrary1.Calculation
Assembly:ClassLibrary1
File(s):E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\ClassLibrary1\Calculation.cs
Covered lines:3
Uncovered lines:6
Coverable lines:9
Total lines:18
Line coverage:33.3% (3 of 9)
Covered branches:0
Total branches:0
Tag:Build.2019.11.11
+

Coverage History

+
+ +

Metrics

+ + + + + + + +
MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage Crap Score
Add(...)10100%100%1
Sub(...)100%0%2
Sub1(...)100%0%2
+

File(s)

+

E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\ClassLibrary1\Calculation.cs

+ + + + + + + + + + + + + + + + + + + + + + +
#LineLine coverage
 1namespace ClassLibrary1
 2{
 3    public class Calculation
 4    {
 5        public int Add(int firstNumber, int secondNumber)
 26        {
 27            return firstNumber + secondNumber;
 28        }
 9        public int Sub(int firstNumber, int secondNumber)
 010        {
 011            return firstNumber + secondNumber;
 012        }
 13        public int Sub1(int firstNumber, int secondNumber)
 014        {
 015            return firstNumber + secondNumber;
 016        }
 17    }
 18}
+
+
+ + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/Cobertura.xml b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/Cobertura.xml new file mode 100644 index 00000000..105f9442 --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/Cobertura.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject1_UnitTest1.htm b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject1_UnitTest1.htm new file mode 100644 index 00000000..1ec7556c --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject1_UnitTest1.htm @@ -0,0 +1,88 @@ + + + + + + +UnitTestProject1.UnitTest1 - Coverage Report + +
+

Summary

+ ++++ + + + + + + + + + + + + + +
Class:UnitTestProject1.UnitTest1
Assembly:UnitTestProject1
File(s):E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\UnitTest1.cs
Covered lines:9
Uncovered lines:1
Coverable lines:10
Total lines:26
Line coverage:90% (9 of 10)
Covered branches:0
Total branches:0
Tag:Build.2019.11.11
+

Coverage History

+
+ +

Metrics

+ + + + + + +
MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage Crap Score
TestMethod1()10100%100%1
TestMethod2()1080%100%1.01
+

File(s)

+

E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\UnitTest1.cs

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#LineLine coverage
 1using System;
 2using ClassLibrary1;
 3using Microsoft.VisualStudio.TestTools.UnitTesting;
 4
 5namespace UnitTestProject1
 6{
 7    [TestClass]
 8    public class UnitTest1
 9    {
 10        [TestMethod]
 11        public void TestMethod1()
 112        {
 113            var calculation = new Calculation();
 114            var actual = calculation.Add(1, 1);
 115            Assert.AreEqual(2,actual);
 116        }
 17
 18        [TestMethod]
 19        public void TestMethod2()
 120        {
 121            var calculation = new Calculation();
 122            var actual = calculation.Add(1, 1);
 123            Assert.AreEqual(1,actual);
 024        }
 25    }
 26}
+
+
+
+

Methods/Properties

+TestMethod1()
+TestMethod2()
+
+
+ + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject2_UnitTest1.htm b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject2_UnitTest1.htm new file mode 100644 index 00000000..7d13ebe2 --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject2_UnitTest1.htm @@ -0,0 +1,75 @@ + + + + + + +UnitTestProject2.UnitTest1 - Coverage Report + +
+

Summary

+ ++++ + + + + + + + + + + + + + +
Class:UnitTestProject2.UnitTest1
Assembly:UnitTestProject2
File(s):E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject2\UnitTest1.cs
Covered lines:3
Uncovered lines:0
Coverable lines:3
Total lines:15
Line coverage:100% (3 of 3)
Covered branches:0
Total branches:0
Tag:Build.2019.11.11
+

Coverage History

+
+ +

Metrics

+ + + + + +
MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage Crap Score
TestMethod1()10100%100%1
+

File(s)

+

E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject2\UnitTest1.cs

+ + + + + + + + + + + + + + + + + + + +
#LineLine coverage
 1using System;
 2using Microsoft.VisualStudio.TestTools.UnitTesting;
 3
 4namespace UnitTestProject2
 5{
 6    [TestClass]
 7    public class UnitTest1
 8    {
 9        [TestMethod]
 10        public void TestMethod1()
 111        {
 112            Assert.AreEqual(1,1);
 113        }
 14    }
 15}
+
+
+
+

Methods/Properties

+TestMethod1()
+
+
+ + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.png b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.png new file mode 100644 index 0000000000000000000000000000000000000000..1d4585190f78c440e6cda1f35623d1780d05fbd8 GIT binary patch literal 2925 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 literal 0 HcmV?d00001 diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.svg new file mode 100644 index 00000000..f3f4a54c --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.svg @@ -0,0 +1,88 @@ + + + Code coverage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Generated by: ReportGenerator 4.1.2.0 + + + + Coverage + Coverage + 68.1%68.1% + + + + + Line coverage + + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/class.js b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/class.js new file mode 100644 index 00000000..b82bca96 --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/class.js @@ -0,0 +1,207 @@ +/* Chartist.js 0.11.0 + * Copyright © 2017 Gion Kunz + * Free to use under either the WTFPL license or the MIT license. + * https://raw.githubusercontent.com/gionkunz/chartist-js/master/LICENSE-WTFPL + * https://raw.githubusercontent.com/gionkunz/chartist-js/master/LICENSE-MIT + */ + +!function (a, b) { "function" == typeof define && define.amd ? define("Chartist", [], function () { return a.Chartist = b() }) : "object" == typeof module && module.exports ? module.exports = b() : a.Chartist = b() }(this, function () { + var a = { version: "0.11.0" }; return function (a, b, c) { "use strict"; c.namespaces = { svg: "http://www.w3.org/2000/svg", xmlns: "http://www.w3.org/2000/xmlns/", xhtml: "http://www.w3.org/1999/xhtml", xlink: "http://www.w3.org/1999/xlink", ct: "http://gionkunz.github.com/chartist-js/ct" }, c.noop = function (a) { return a }, c.alphaNumerate = function (a) { return String.fromCharCode(97 + a % 26) }, c.extend = function (a) { var b, d, e; for (a = a || {}, b = 1; b < arguments.length; b++) { d = arguments[b]; for (var f in d) e = d[f], "object" != typeof e || null === e || e instanceof Array ? a[f] = e : a[f] = c.extend(a[f], e) } return a }, c.replaceAll = function (a, b, c) { return a.replace(new RegExp(b, "g"), c) }, c.ensureUnit = function (a, b) { return "number" == typeof a && (a += b), a }, c.quantity = function (a) { if ("string" == typeof a) { var b = /^(\d+)\s*(.*)$/g.exec(a); return { value: +b[1], unit: b[2] || void 0 } } return { value: a } }, c.querySelector = function (a) { return a instanceof Node ? a : b.querySelector(a) }, c.times = function (a) { return Array.apply(null, new Array(a)) }, c.sum = function (a, b) { return a + (b ? b : 0) }, c.mapMultiply = function (a) { return function (b) { return b * a } }, c.mapAdd = function (a) { return function (b) { return b + a } }, c.serialMap = function (a, b) { var d = [], e = Math.max.apply(null, a.map(function (a) { return a.length })); return c.times(e).forEach(function (c, e) { var f = a.map(function (a) { return a[e] }); d[e] = b.apply(null, f) }), d }, c.roundWithPrecision = function (a, b) { var d = Math.pow(10, b || c.precision); return Math.round(a * d) / d }, c.precision = 8, c.escapingMap = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }, c.serialize = function (a) { return null === a || void 0 === a ? a : ("number" == typeof a ? a = "" + a : "object" == typeof a && (a = JSON.stringify({ data: a })), Object.keys(c.escapingMap).reduce(function (a, b) { return c.replaceAll(a, b, c.escapingMap[b]) }, a)) }, c.deserialize = function (a) { if ("string" != typeof a) return a; a = Object.keys(c.escapingMap).reduce(function (a, b) { return c.replaceAll(a, c.escapingMap[b], b) }, a); try { a = JSON.parse(a), a = void 0 !== a.data ? a.data : a } catch (b) { } return a }, c.createSvg = function (a, b, d, e) { var f; return b = b || "100%", d = d || "100%", Array.prototype.slice.call(a.querySelectorAll("svg")).filter(function (a) { return a.getAttributeNS(c.namespaces.xmlns, "ct") }).forEach(function (b) { a.removeChild(b) }), f = new c.Svg("svg").attr({ width: b, height: d }).addClass(e), f._node.style.width = b, f._node.style.height = d, a.appendChild(f._node), f }, c.normalizeData = function (a, b, d) { var e, f = { raw: a, normalized: {} }; return f.normalized.series = c.getDataArray({ series: a.series || [] }, b, d), e = f.normalized.series.every(function (a) { return a instanceof Array }) ? Math.max.apply(null, f.normalized.series.map(function (a) { return a.length })) : f.normalized.series.length, f.normalized.labels = (a.labels || []).slice(), Array.prototype.push.apply(f.normalized.labels, c.times(Math.max(0, e - f.normalized.labels.length)).map(function () { return "" })), b && c.reverseData(f.normalized), f }, c.safeHasProperty = function (a, b) { return null !== a && "object" == typeof a && a.hasOwnProperty(b) }, c.isDataHoleValue = function (a) { return null === a || void 0 === a || "number" == typeof a && isNaN(a) }, c.reverseData = function (a) { a.labels.reverse(), a.series.reverse(); for (var b = 0; b < a.series.length; b++)"object" == typeof a.series[b] && void 0 !== a.series[b].data ? a.series[b].data.reverse() : a.series[b] instanceof Array && a.series[b].reverse() }, c.getDataArray = function (a, b, d) { function e(a) { if (c.safeHasProperty(a, "value")) return e(a.value); if (c.safeHasProperty(a, "data")) return e(a.data); if (a instanceof Array) return a.map(e); if (!c.isDataHoleValue(a)) { if (d) { var b = {}; return "string" == typeof d ? b[d] = c.getNumberOrUndefined(a) : b.y = c.getNumberOrUndefined(a), b.x = a.hasOwnProperty("x") ? c.getNumberOrUndefined(a.x) : b.x, b.y = a.hasOwnProperty("y") ? c.getNumberOrUndefined(a.y) : b.y, b } return c.getNumberOrUndefined(a) } } return a.series.map(e) }, c.normalizePadding = function (a, b) { return b = b || 0, "number" == typeof a ? { top: a, right: a, bottom: a, left: a } : { top: "number" == typeof a.top ? a.top : b, right: "number" == typeof a.right ? a.right : b, bottom: "number" == typeof a.bottom ? a.bottom : b, left: "number" == typeof a.left ? a.left : b } }, c.getMetaData = function (a, b) { var c = a.data ? a.data[b] : a[b]; return c ? c.meta : void 0 }, c.orderOfMagnitude = function (a) { return Math.floor(Math.log(Math.abs(a)) / Math.LN10) }, c.projectLength = function (a, b, c) { return b / c.range * a }, c.getAvailableHeight = function (a, b) { return Math.max((c.quantity(b.height).value || a.height()) - (b.chartPadding.top + b.chartPadding.bottom) - b.axisX.offset, 0) }, c.getHighLow = function (a, b, d) { function e(a) { if (void 0 !== a) if (a instanceof Array) for (var b = 0; b < a.length; b++)e(a[b]); else { var c = d ? +a[d] : +a; g && c > f.high && (f.high = c), h && c < f.low && (f.low = c) } } b = c.extend({}, b, d ? b["axis" + d.toUpperCase()] : {}); var f = { high: void 0 === b.high ? -Number.MAX_VALUE : +b.high, low: void 0 === b.low ? Number.MAX_VALUE : +b.low }, g = void 0 === b.high, h = void 0 === b.low; return (g || h) && e(a), (b.referenceValue || 0 === b.referenceValue) && (f.high = Math.max(b.referenceValue, f.high), f.low = Math.min(b.referenceValue, f.low)), f.high <= f.low && (0 === f.low ? f.high = 1 : f.low < 0 ? f.high = 0 : f.high > 0 ? f.low = 0 : (f.high = 1, f.low = 0)), f }, c.isNumeric = function (a) { return null !== a && isFinite(a) }, c.isFalseyButZero = function (a) { return !a && 0 !== a }, c.getNumberOrUndefined = function (a) { return c.isNumeric(a) ? +a : void 0 }, c.isMultiValue = function (a) { return "object" == typeof a && ("x" in a || "y" in a) }, c.getMultiValue = function (a, b) { return c.isMultiValue(a) ? c.getNumberOrUndefined(a[b || "y"]) : c.getNumberOrUndefined(a) }, c.rho = function (a) { function b(a, c) { return a % c === 0 ? c : b(c, a % c) } function c(a) { return a * a + 1 } if (1 === a) return a; var d, e = 2, f = 2; if (a % 2 === 0) return 2; do e = c(e) % a, f = c(c(f)) % a, d = b(Math.abs(e - f), a); while (1 === d); return d }, c.getBounds = function (a, b, d, e) { function f(a, b) { return a === (a += b) && (a *= 1 + (b > 0 ? o : -o)), a } var g, h, i, j = 0, k = { high: b.high, low: b.low }; k.valueRange = k.high - k.low, k.oom = c.orderOfMagnitude(k.valueRange), k.step = Math.pow(10, k.oom), k.min = Math.floor(k.low / k.step) * k.step, k.max = Math.ceil(k.high / k.step) * k.step, k.range = k.max - k.min, k.numberOfSteps = Math.round(k.range / k.step); var l = c.projectLength(a, k.step, k), m = l < d, n = e ? c.rho(k.range) : 0; if (e && c.projectLength(a, 1, k) >= d) k.step = 1; else if (e && n < k.step && c.projectLength(a, n, k) >= d) k.step = n; else for (; ;) { if (m && c.projectLength(a, k.step, k) <= d) k.step *= 2; else { if (m || !(c.projectLength(a, k.step / 2, k) >= d)) break; if (k.step /= 2, e && k.step % 1 !== 0) { k.step *= 2; break } } if (j++ > 1e3) throw new Error("Exceeded maximum number of iterations while optimizing scale step!") } var o = 2.221e-16; for (k.step = Math.max(k.step, o), h = k.min, i = k.max; h + k.step <= k.low;)h = f(h, k.step); for (; i - k.step >= k.high;)i = f(i, -k.step); k.min = h, k.max = i, k.range = k.max - k.min; var p = []; for (g = k.min; g <= k.max; g = f(g, k.step)) { var q = c.roundWithPrecision(g); q !== p[p.length - 1] && p.push(q) } return k.values = p, k }, c.polarToCartesian = function (a, b, c, d) { var e = (d - 90) * Math.PI / 180; return { x: a + c * Math.cos(e), y: b + c * Math.sin(e) } }, c.createChartRect = function (a, b, d) { var e = !(!b.axisX && !b.axisY), f = e ? b.axisY.offset : 0, g = e ? b.axisX.offset : 0, h = a.width() || c.quantity(b.width).value || 0, i = a.height() || c.quantity(b.height).value || 0, j = c.normalizePadding(b.chartPadding, d); h = Math.max(h, f + j.left + j.right), i = Math.max(i, g + j.top + j.bottom); var k = { padding: j, width: function () { return this.x2 - this.x1 }, height: function () { return this.y1 - this.y2 } }; return e ? ("start" === b.axisX.position ? (k.y2 = j.top + g, k.y1 = Math.max(i - j.bottom, k.y2 + 1)) : (k.y2 = j.top, k.y1 = Math.max(i - j.bottom - g, k.y2 + 1)), "start" === b.axisY.position ? (k.x1 = j.left + f, k.x2 = Math.max(h - j.right, k.x1 + 1)) : (k.x1 = j.left, k.x2 = Math.max(h - j.right - f, k.x1 + 1))) : (k.x1 = j.left, k.x2 = Math.max(h - j.right, k.x1 + 1), k.y2 = j.top, k.y1 = Math.max(i - j.bottom, k.y2 + 1)), k }, c.createGrid = function (a, b, d, e, f, g, h, i) { var j = {}; j[d.units.pos + "1"] = a, j[d.units.pos + "2"] = a, j[d.counterUnits.pos + "1"] = e, j[d.counterUnits.pos + "2"] = e + f; var k = g.elem("line", j, h.join(" ")); i.emit("draw", c.extend({ type: "grid", axis: d, index: b, group: g, element: k }, j)) }, c.createGridBackground = function (a, b, c, d) { var e = a.elem("rect", { x: b.x1, y: b.y2, width: b.width(), height: b.height() }, c, !0); d.emit("draw", { type: "gridBackground", group: a, element: e }) }, c.createLabel = function (a, d, e, f, g, h, i, j, k, l, m) { var n, o = {}; if (o[g.units.pos] = a + i[g.units.pos], o[g.counterUnits.pos] = i[g.counterUnits.pos], o[g.units.len] = d, o[g.counterUnits.len] = Math.max(0, h - 10), l) { var p = b.createElement("span"); p.className = k.join(" "), p.setAttribute("xmlns", c.namespaces.xhtml), p.innerText = f[e], p.style[g.units.len] = Math.round(o[g.units.len]) + "px", p.style[g.counterUnits.len] = Math.round(o[g.counterUnits.len]) + "px", n = j.foreignObject(p, c.extend({ style: "overflow: visible;" }, o)) } else n = j.elem("text", o, k.join(" ")).text(f[e]); m.emit("draw", c.extend({ type: "label", axis: g, index: e, group: j, element: n, text: f[e] }, o)) }, c.getSeriesOption = function (a, b, c) { if (a.name && b.series && b.series[a.name]) { var d = b.series[a.name]; return d.hasOwnProperty(c) ? d[c] : b[c] } return b[c] }, c.optionsProvider = function (b, d, e) { function f(b) { var f = h; if (h = c.extend({}, j), d) for (i = 0; i < d.length; i++) { var g = a.matchMedia(d[i][0]); g.matches && (h = c.extend(h, d[i][1])) } e && b && e.emit("optionsChanged", { previousOptions: f, currentOptions: h }) } function g() { k.forEach(function (a) { a.removeListener(f) }) } var h, i, j = c.extend({}, b), k = []; if (!a.matchMedia) throw "window.matchMedia not found! Make sure you're using a polyfill."; if (d) for (i = 0; i < d.length; i++) { var l = a.matchMedia(d[i][0]); l.addListener(f), k.push(l) } return f(), { removeMediaQueryListeners: g, getCurrentOptions: function () { return c.extend({}, h) } } }, c.splitIntoSegments = function (a, b, d) { var e = { increasingX: !1, fillHoles: !1 }; d = c.extend({}, e, d); for (var f = [], g = !0, h = 0; h < a.length; h += 2)void 0 === c.getMultiValue(b[h / 2].value) ? d.fillHoles || (g = !0) : (d.increasingX && h >= 2 && a[h] <= a[h - 2] && (g = !0), g && (f.push({ pathCoordinates: [], valueData: [] }), g = !1), f[f.length - 1].pathCoordinates.push(a[h], a[h + 1]), f[f.length - 1].valueData.push(b[h / 2])); return f } }(window, document, a), function (a, b, c) { "use strict"; c.Interpolation = {}, c.Interpolation.none = function (a) { var b = { fillHoles: !1 }; return a = c.extend({}, b, a), function (b, d) { for (var e = new c.Svg.Path, f = !0, g = 0; g < b.length; g += 2) { var h = b[g], i = b[g + 1], j = d[g / 2]; void 0 !== c.getMultiValue(j.value) ? (f ? e.move(h, i, !1, j) : e.line(h, i, !1, j), f = !1) : a.fillHoles || (f = !0) } return e } }, c.Interpolation.simple = function (a) { var b = { divisor: 2, fillHoles: !1 }; a = c.extend({}, b, a); var d = 1 / Math.max(1, a.divisor); return function (b, e) { for (var f, g, h, i = new c.Svg.Path, j = 0; j < b.length; j += 2) { var k = b[j], l = b[j + 1], m = (k - f) * d, n = e[j / 2]; void 0 !== n.value ? (void 0 === h ? i.move(k, l, !1, n) : i.curve(f + m, g, k - m, l, k, l, !1, n), f = k, g = l, h = n) : a.fillHoles || (f = k = h = void 0) } return i } }, c.Interpolation.cardinal = function (a) { var b = { tension: 1, fillHoles: !1 }; a = c.extend({}, b, a); var d = Math.min(1, Math.max(0, a.tension)), e = 1 - d; return function f(b, g) { var h = c.splitIntoSegments(b, g, { fillHoles: a.fillHoles }); if (h.length) { if (h.length > 1) { var i = []; return h.forEach(function (a) { i.push(f(a.pathCoordinates, a.valueData)) }), c.Svg.Path.join(i) } if (b = h[0].pathCoordinates, g = h[0].valueData, b.length <= 4) return c.Interpolation.none()(b, g); for (var j, k = (new c.Svg.Path).move(b[0], b[1], !1, g[0]), l = 0, m = b.length; m - 2 * !j > l; l += 2) { var n = [{ x: +b[l - 2], y: +b[l - 1] }, { x: +b[l], y: +b[l + 1] }, { x: +b[l + 2], y: +b[l + 3] }, { x: +b[l + 4], y: +b[l + 5] }]; j ? l ? m - 4 === l ? n[3] = { x: +b[0], y: +b[1] } : m - 2 === l && (n[2] = { x: +b[0], y: +b[1] }, n[3] = { x: +b[2], y: +b[3] }) : n[0] = { x: +b[m - 2], y: +b[m - 1] } : m - 4 === l ? n[3] = n[2] : l || (n[0] = { x: +b[l], y: +b[l + 1] }), k.curve(d * (-n[0].x + 6 * n[1].x + n[2].x) / 6 + e * n[2].x, d * (-n[0].y + 6 * n[1].y + n[2].y) / 6 + e * n[2].y, d * (n[1].x + 6 * n[2].x - n[3].x) / 6 + e * n[2].x, d * (n[1].y + 6 * n[2].y - n[3].y) / 6 + e * n[2].y, n[2].x, n[2].y, !1, g[(l + 2) / 2]) } return k } return c.Interpolation.none()([]) } }, c.Interpolation.monotoneCubic = function (a) { var b = { fillHoles: !1 }; return a = c.extend({}, b, a), function d(b, e) { var f = c.splitIntoSegments(b, e, { fillHoles: a.fillHoles, increasingX: !0 }); if (f.length) { if (f.length > 1) { var g = []; return f.forEach(function (a) { g.push(d(a.pathCoordinates, a.valueData)) }), c.Svg.Path.join(g) } if (b = f[0].pathCoordinates, e = f[0].valueData, b.length <= 4) return c.Interpolation.none()(b, e); var h, i, j = [], k = [], l = b.length / 2, m = [], n = [], o = [], p = []; for (h = 0; h < l; h++)j[h] = b[2 * h], k[h] = b[2 * h + 1]; for (h = 0; h < l - 1; h++)o[h] = k[h + 1] - k[h], p[h] = j[h + 1] - j[h], n[h] = o[h] / p[h]; for (m[0] = n[0], m[l - 1] = n[l - 2], h = 1; h < l - 1; h++)0 === n[h] || 0 === n[h - 1] || n[h - 1] > 0 != n[h] > 0 ? m[h] = 0 : (m[h] = 3 * (p[h - 1] + p[h]) / ((2 * p[h] + p[h - 1]) / n[h - 1] + (p[h] + 2 * p[h - 1]) / n[h]), isFinite(m[h]) || (m[h] = 0)); for (i = (new c.Svg.Path).move(j[0], k[0], !1, e[0]), h = 0; h < l - 1; h++)i.curve(j[h] + p[h] / 3, k[h] + m[h] * p[h] / 3, j[h + 1] - p[h] / 3, k[h + 1] - m[h + 1] * p[h] / 3, j[h + 1], k[h + 1], !1, e[h + 1]); return i } return c.Interpolation.none()([]) } }, c.Interpolation.step = function (a) { var b = { postpone: !0, fillHoles: !1 }; return a = c.extend({}, b, a), function (b, d) { for (var e, f, g, h = new c.Svg.Path, i = 0; i < b.length; i += 2) { var j = b[i], k = b[i + 1], l = d[i / 2]; void 0 !== l.value ? (void 0 === g ? h.move(j, k, !1, l) : (a.postpone ? h.line(j, f, !1, g) : h.line(e, k, !1, l), h.line(j, k, !1, l)), e = j, f = k, g = l) : a.fillHoles || (e = f = g = void 0) } return h } } }(window, document, a), function (a, b, c) { "use strict"; c.EventEmitter = function () { function a(a, b) { d[a] = d[a] || [], d[a].push(b) } function b(a, b) { d[a] && (b ? (d[a].splice(d[a].indexOf(b), 1), 0 === d[a].length && delete d[a]) : delete d[a]) } function c(a, b) { d[a] && d[a].forEach(function (a) { a(b) }), d["*"] && d["*"].forEach(function (c) { c(a, b) }) } var d = []; return { addEventHandler: a, removeEventHandler: b, emit: c } } }(window, document, a), function (a, b, c) { "use strict"; function d(a) { var b = []; if (a.length) for (var c = 0; c < a.length; c++)b.push(a[c]); return b } function e(a, b) { var d = b || this.prototype || c.Class, e = Object.create(d); c.Class.cloneDefinitions(e, a); var f = function () { var a, b = e.constructor || function () { }; return a = this === c ? Object.create(e) : this, b.apply(a, Array.prototype.slice.call(arguments, 0)), a }; return f.prototype = e, f["super"] = d, f.extend = this.extend, f } function f() { var a = d(arguments), b = a[0]; return a.splice(1, a.length - 1).forEach(function (a) { Object.getOwnPropertyNames(a).forEach(function (c) { delete b[c], Object.defineProperty(b, c, Object.getOwnPropertyDescriptor(a, c)) }) }), b } c.Class = { extend: e, cloneDefinitions: f } }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d) { return a && (this.data = a || {}, this.data.labels = this.data.labels || [], this.data.series = this.data.series || [], this.eventEmitter.emit("data", { type: "update", data: this.data })), b && (this.options = c.extend({}, d ? this.options : this.defaultOptions, b), this.initializeTimeoutId || (this.optionsProvider.removeMediaQueryListeners(), this.optionsProvider = c.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter))), this.initializeTimeoutId || this.createChart(this.optionsProvider.getCurrentOptions()), this } function e() { return this.initializeTimeoutId ? a.clearTimeout(this.initializeTimeoutId) : (a.removeEventListener("resize", this.resizeListener), this.optionsProvider.removeMediaQueryListeners()), this } function f(a, b) { return this.eventEmitter.addEventHandler(a, b), this } function g(a, b) { return this.eventEmitter.removeEventHandler(a, b), this } function h() { a.addEventListener("resize", this.resizeListener), this.optionsProvider = c.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter), this.eventEmitter.addEventHandler("optionsChanged", function () { this.update() }.bind(this)), this.options.plugins && this.options.plugins.forEach(function (a) { a instanceof Array ? a[0](this, a[1]) : a(this) }.bind(this)), this.eventEmitter.emit("data", { type: "initial", data: this.data }), this.createChart(this.optionsProvider.getCurrentOptions()), this.initializeTimeoutId = void 0 } function i(a, b, d, e, f) { this.container = c.querySelector(a), this.data = b || {}, this.data.labels = this.data.labels || [], this.data.series = this.data.series || [], this.defaultOptions = d, this.options = e, this.responsiveOptions = f, this.eventEmitter = c.EventEmitter(), this.supportsForeignObject = c.Svg.isSupported("Extensibility"), this.supportsAnimations = c.Svg.isSupported("AnimationEventsAttribute"), this.resizeListener = function () { this.update() }.bind(this), this.container && (this.container.__chartist__ && this.container.__chartist__.detach(), this.container.__chartist__ = this), this.initializeTimeoutId = setTimeout(h.bind(this), 0) } c.Base = c.Class.extend({ constructor: i, optionsProvider: void 0, container: void 0, svg: void 0, eventEmitter: void 0, createChart: function () { throw new Error("Base chart type can't be instantiated!") }, update: d, detach: e, on: f, off: g, version: c.version, supportsForeignObject: !1 }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, d, e, f, g) { a instanceof Element ? this._node = a : (this._node = b.createElementNS(c.namespaces.svg, a), "svg" === a && this.attr({ "xmlns:ct": c.namespaces.ct })), d && this.attr(d), e && this.addClass(e), f && (g && f._node.firstChild ? f._node.insertBefore(this._node, f._node.firstChild) : f._node.appendChild(this._node)) } function e(a, b) { return "string" == typeof a ? b ? this._node.getAttributeNS(b, a) : this._node.getAttribute(a) : (Object.keys(a).forEach(function (b) { if (void 0 !== a[b]) if (b.indexOf(":") !== -1) { var d = b.split(":"); this._node.setAttributeNS(c.namespaces[d[0]], b, a[b]) } else this._node.setAttribute(b, a[b]) }.bind(this)), this) } function f(a, b, d, e) { return new c.Svg(a, b, d, this, e) } function g() { return this._node.parentNode instanceof SVGElement ? new c.Svg(this._node.parentNode) : null } function h() { for (var a = this._node; "svg" !== a.nodeName;)a = a.parentNode; return new c.Svg(a) } function i(a) { var b = this._node.querySelector(a); return b ? new c.Svg(b) : null } function j(a) { var b = this._node.querySelectorAll(a); return b.length ? new c.Svg.List(b) : null } function k() { return this._node } function l(a, d, e, f) { if ("string" == typeof a) { var g = b.createElement("div"); g.innerHTML = a, a = g.firstChild } a.setAttribute("xmlns", c.namespaces.xmlns); var h = this.elem("foreignObject", d, e, f); return h._node.appendChild(a), h } function m(a) { return this._node.appendChild(b.createTextNode(a)), this } function n() { for (; this._node.firstChild;)this._node.removeChild(this._node.firstChild); return this } function o() { return this._node.parentNode.removeChild(this._node), this.parent() } function p(a) { return this._node.parentNode.replaceChild(a._node, this._node), a } function q(a, b) { return b && this._node.firstChild ? this._node.insertBefore(a._node, this._node.firstChild) : this._node.appendChild(a._node), this } function r() { return this._node.getAttribute("class") ? this._node.getAttribute("class").trim().split(/\s+/) : [] } function s(a) { return this._node.setAttribute("class", this.classes(this._node).concat(a.trim().split(/\s+/)).filter(function (a, b, c) { return c.indexOf(a) === b }).join(" ")), this } function t(a) { var b = a.trim().split(/\s+/); return this._node.setAttribute("class", this.classes(this._node).filter(function (a) { return b.indexOf(a) === -1 }).join(" ")), this } function u() { return this._node.setAttribute("class", ""), this } function v() { return this._node.getBoundingClientRect().height } function w() { return this._node.getBoundingClientRect().width } function x(a, b, d) { return void 0 === b && (b = !0), Object.keys(a).forEach(function (e) { function f(a, b) { var f, g, h, i = {}; a.easing && (h = a.easing instanceof Array ? a.easing : c.Svg.Easing[a.easing], delete a.easing), a.begin = c.ensureUnit(a.begin, "ms"), a.dur = c.ensureUnit(a.dur, "ms"), h && (a.calcMode = "spline", a.keySplines = h.join(" "), a.keyTimes = "0;1"), b && (a.fill = "freeze", i[e] = a.from, this.attr(i), g = c.quantity(a.begin || 0).value, a.begin = "indefinite"), f = this.elem("animate", c.extend({ attributeName: e }, a)), b && setTimeout(function () { try { f._node.beginElement() } catch (b) { i[e] = a.to, this.attr(i), f.remove() } }.bind(this), g), d && f._node.addEventListener("beginEvent", function () { d.emit("animationBegin", { element: this, animate: f._node, params: a }) }.bind(this)), f._node.addEventListener("endEvent", function () { d && d.emit("animationEnd", { element: this, animate: f._node, params: a }), b && (i[e] = a.to, this.attr(i), f.remove()) }.bind(this)) } a[e] instanceof Array ? a[e].forEach(function (a) { f.bind(this)(a, !1) }.bind(this)) : f.bind(this)(a[e], b) }.bind(this)), this } function y(a) { var b = this; this.svgElements = []; for (var d = 0; d < a.length; d++)this.svgElements.push(new c.Svg(a[d])); Object.keys(c.Svg.prototype).filter(function (a) { return ["constructor", "parent", "querySelector", "querySelectorAll", "replace", "append", "classes", "height", "width"].indexOf(a) === -1 }).forEach(function (a) { b[a] = function () { var d = Array.prototype.slice.call(arguments, 0); return b.svgElements.forEach(function (b) { c.Svg.prototype[a].apply(b, d) }), b } }) } c.Svg = c.Class.extend({ constructor: d, attr: e, elem: f, parent: g, root: h, querySelector: i, querySelectorAll: j, getNode: k, foreignObject: l, text: m, empty: n, remove: o, replace: p, append: q, classes: r, addClass: s, removeClass: t, removeAllClasses: u, height: v, width: w, animate: x }), c.Svg.isSupported = function (a) { return b.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#" + a, "1.1") }; var z = { easeInSine: [.47, 0, .745, .715], easeOutSine: [.39, .575, .565, 1], easeInOutSine: [.445, .05, .55, .95], easeInQuad: [.55, .085, .68, .53], easeOutQuad: [.25, .46, .45, .94], easeInOutQuad: [.455, .03, .515, .955], easeInCubic: [.55, .055, .675, .19], easeOutCubic: [.215, .61, .355, 1], easeInOutCubic: [.645, .045, .355, 1], easeInQuart: [.895, .03, .685, .22], easeOutQuart: [.165, .84, .44, 1], easeInOutQuart: [.77, 0, .175, 1], easeInQuint: [.755, .05, .855, .06], easeOutQuint: [.23, 1, .32, 1], easeInOutQuint: [.86, 0, .07, 1], easeInExpo: [.95, .05, .795, .035], easeOutExpo: [.19, 1, .22, 1], easeInOutExpo: [1, 0, 0, 1], easeInCirc: [.6, .04, .98, .335], easeOutCirc: [.075, .82, .165, 1], easeInOutCirc: [.785, .135, .15, .86], easeInBack: [.6, -.28, .735, .045], easeOutBack: [.175, .885, .32, 1.275], easeInOutBack: [.68, -.55, .265, 1.55] }; c.Svg.Easing = z, c.Svg.List = c.Class.extend({ constructor: y }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e, f, g) { var h = c.extend({ command: f ? a.toLowerCase() : a.toUpperCase() }, b, g ? { data: g } : {}); d.splice(e, 0, h) } function e(a, b) { a.forEach(function (c, d) { u[c.command.toLowerCase()].forEach(function (e, f) { b(c, e, d, f, a) }) }) } function f(a, b) { this.pathElements = [], this.pos = 0, this.close = a, this.options = c.extend({}, v, b) } function g(a) { return void 0 !== a ? (this.pos = Math.max(0, Math.min(this.pathElements.length, a)), this) : this.pos } function h(a) { return this.pathElements.splice(this.pos, a), this } function i(a, b, c, e) { return d("M", { x: +a, y: +b }, this.pathElements, this.pos++, c, e), this } function j(a, b, c, e) { return d("L", { x: +a, y: +b }, this.pathElements, this.pos++, c, e), this } function k(a, b, c, e, f, g, h, i) { return d("C", { x1: +a, y1: +b, x2: +c, y2: +e, x: +f, y: +g }, this.pathElements, this.pos++, h, i), this } function l(a, b, c, e, f, g, h, i, j) { return d("A", { rx: +a, ry: +b, xAr: +c, lAf: +e, sf: +f, x: +g, y: +h }, this.pathElements, this.pos++, i, j), this } function m(a) { var b = a.replace(/([A-Za-z])([0-9])/g, "$1 $2").replace(/([0-9])([A-Za-z])/g, "$1 $2").split(/[\s,]+/).reduce(function (a, b) { return b.match(/[A-Za-z]/) && a.push([]), a[a.length - 1].push(b), a }, []); "Z" === b[b.length - 1][0].toUpperCase() && b.pop(); var d = b.map(function (a) { var b = a.shift(), d = u[b.toLowerCase()]; return c.extend({ command: b }, d.reduce(function (b, c, d) { return b[c] = +a[d], b }, {})) }), e = [this.pos, 0]; return Array.prototype.push.apply(e, d), Array.prototype.splice.apply(this.pathElements, e), this.pos += d.length, this } function n() { var a = Math.pow(10, this.options.accuracy); return this.pathElements.reduce(function (b, c) { var d = u[c.command.toLowerCase()].map(function (b) { return this.options.accuracy ? Math.round(c[b] * a) / a : c[b] }.bind(this)); return b + c.command + d.join(",") }.bind(this), "") + (this.close ? "Z" : "") } function o(a, b) { return e(this.pathElements, function (c, d) { c[d] *= "x" === d[0] ? a : b }), this } function p(a, b) { return e(this.pathElements, function (c, d) { c[d] += "x" === d[0] ? a : b }), this } function q(a) { return e(this.pathElements, function (b, c, d, e, f) { var g = a(b, c, d, e, f); (g || 0 === g) && (b[c] = g) }), this } function r(a) { var b = new c.Svg.Path(a || this.close); return b.pos = this.pos, b.pathElements = this.pathElements.slice().map(function (a) { return c.extend({}, a) }), b.options = c.extend({}, this.options), b } function s(a) { var b = [new c.Svg.Path]; return this.pathElements.forEach(function (d) { d.command === a.toUpperCase() && 0 !== b[b.length - 1].pathElements.length && b.push(new c.Svg.Path), b[b.length - 1].pathElements.push(d) }), b } function t(a, b, d) { for (var e = new c.Svg.Path(b, d), f = 0; f < a.length; f++)for (var g = a[f], h = 0; h < g.pathElements.length; h++)e.pathElements.push(g.pathElements[h]); return e } var u = { m: ["x", "y"], l: ["x", "y"], c: ["x1", "y1", "x2", "y2", "x", "y"], a: ["rx", "ry", "xAr", "lAf", "sf", "x", "y"] }, v = { accuracy: 3 }; c.Svg.Path = c.Class.extend({ constructor: f, position: g, remove: h, move: i, line: j, curve: k, arc: l, scale: o, translate: p, transform: q, parse: m, stringify: n, clone: r, splitByCommand: s }), c.Svg.Path.elementDescriptions = u, c.Svg.Path.join = t }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, c, d) { this.units = a, this.counterUnits = a === f.x ? f.y : f.x, this.chartRect = b, this.axisLength = b[a.rectEnd] - b[a.rectStart], this.gridOffset = b[a.rectOffset], this.ticks = c, this.options = d } function e(a, b, d, e, f) { var g = e["axis" + this.units.pos.toUpperCase()], h = this.ticks.map(this.projectValue.bind(this)), i = this.ticks.map(g.labelInterpolationFnc); h.forEach(function (j, k) { var l, m = { x: 0, y: 0 }; l = h[k + 1] ? h[k + 1] - j : Math.max(this.axisLength - j, 30), c.isFalseyButZero(i[k]) && "" !== i[k] || ("x" === this.units.pos ? (j = this.chartRect.x1 + j, m.x = e.axisX.labelOffset.x, "start" === e.axisX.position ? m.y = this.chartRect.padding.top + e.axisX.labelOffset.y + (d ? 5 : 20) : m.y = this.chartRect.y1 + e.axisX.labelOffset.y + (d ? 5 : 20)) : (j = this.chartRect.y1 - j, m.y = e.axisY.labelOffset.y - (d ? l : 0), "start" === e.axisY.position ? m.x = d ? this.chartRect.padding.left + e.axisY.labelOffset.x : this.chartRect.x1 - 10 : m.x = this.chartRect.x2 + e.axisY.labelOffset.x + 10), g.showGrid && c.createGrid(j, k, this, this.gridOffset, this.chartRect[this.counterUnits.len](), a, [e.classNames.grid, e.classNames[this.units.dir]], f), g.showLabel && c.createLabel(j, l, k, i, this, g.offset, m, b, [e.classNames.label, e.classNames[this.units.dir], "start" === g.position ? e.classNames[g.position] : e.classNames.end], d, f)) }.bind(this)) } var f = { x: { pos: "x", len: "width", dir: "horizontal", rectStart: "x1", rectEnd: "x2", rectOffset: "y2" }, y: { pos: "y", len: "height", dir: "vertical", rectStart: "y2", rectEnd: "y1", rectOffset: "x1" } }; c.Axis = c.Class.extend({ constructor: d, createGridAndLabels: e, projectValue: function (a, b, c) { throw new Error("Base axis can't be instantiated!") } }), c.Axis.units = f }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { var f = e.highLow || c.getHighLow(b, e, a.pos); this.bounds = c.getBounds(d[a.rectEnd] - d[a.rectStart], f, e.scaleMinSpace || 20, e.onlyInteger), this.range = { min: this.bounds.min, max: this.bounds.max }, c.AutoScaleAxis["super"].constructor.call(this, a, d, this.bounds.values, e) } function e(a) { return this.axisLength * (+c.getMultiValue(a, this.units.pos) - this.bounds.min) / this.bounds.range } c.AutoScaleAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { var f = e.highLow || c.getHighLow(b, e, a.pos); this.divisor = e.divisor || 1, this.ticks = e.ticks || c.times(this.divisor).map(function (a, b) { return f.low + (f.high - f.low) / this.divisor * b }.bind(this)), this.ticks.sort(function (a, b) { return a - b }), this.range = { min: f.low, max: f.high }, c.FixedScaleAxis["super"].constructor.call(this, a, d, this.ticks, e), this.stepLength = this.axisLength / this.divisor } function e(a) { return this.axisLength * (+c.getMultiValue(a, this.units.pos) - this.range.min) / (this.range.max - this.range.min) } c.FixedScaleAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { c.StepAxis["super"].constructor.call(this, a, d, e.ticks, e); var f = Math.max(1, e.ticks.length - (e.stretch ? 1 : 0)); this.stepLength = this.axisLength / f } function e(a, b) { return this.stepLength * b } c.StepAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a) { var b = c.normalizeData(this.data, a.reverseData, !0); this.svg = c.createSvg(this.container, a.width, a.height, a.classNames.chart); var d, e, g = this.svg.elem("g").addClass(a.classNames.gridGroup), h = this.svg.elem("g"), i = this.svg.elem("g").addClass(a.classNames.labelGroup), j = c.createChartRect(this.svg, a, f.padding); d = void 0 === a.axisX.type ? new c.StepAxis(c.Axis.units.x, b.normalized.series, j, c.extend({}, a.axisX, { ticks: b.normalized.labels, stretch: a.fullWidth })) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, j, a.axisX), e = void 0 === a.axisY.type ? new c.AutoScaleAxis(c.Axis.units.y, b.normalized.series, j, c.extend({}, a.axisY, { high: c.isNumeric(a.high) ? a.high : a.axisY.high, low: c.isNumeric(a.low) ? a.low : a.axisY.low })) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, j, a.axisY), d.createGridAndLabels(g, i, this.supportsForeignObject, a, this.eventEmitter), e.createGridAndLabels(g, i, this.supportsForeignObject, a, this.eventEmitter), a.showGridBackground && c.createGridBackground(g, j, a.classNames.gridBackground, this.eventEmitter), b.raw.series.forEach(function (f, g) { var i = h.elem("g"); i.attr({ "ct:series-name": f.name, "ct:meta": c.serialize(f.meta) }), i.addClass([a.classNames.series, f.className || a.classNames.series + "-" + c.alphaNumerate(g)].join(" ")); var k = [], l = []; b.normalized.series[g].forEach(function (a, h) { var i = { x: j.x1 + d.projectValue(a, h, b.normalized.series[g]), y: j.y1 - e.projectValue(a, h, b.normalized.series[g]) }; k.push(i.x, i.y), l.push({ value: a, valueIndex: h, meta: c.getMetaData(f, h) }) }.bind(this)); var m = { lineSmooth: c.getSeriesOption(f, a, "lineSmooth"), showPoint: c.getSeriesOption(f, a, "showPoint"), showLine: c.getSeriesOption(f, a, "showLine"), showArea: c.getSeriesOption(f, a, "showArea"), areaBase: c.getSeriesOption(f, a, "areaBase") }, n = "function" == typeof m.lineSmooth ? m.lineSmooth : m.lineSmooth ? c.Interpolation.monotoneCubic() : c.Interpolation.none(), o = n(k, l); if (m.showPoint && o.pathElements.forEach(function (b) { var h = i.elem("line", { x1: b.x, y1: b.y, x2: b.x + .01, y2: b.y }, a.classNames.point).attr({ "ct:value": [b.data.value.x, b.data.value.y].filter(c.isNumeric).join(","), "ct:meta": c.serialize(b.data.meta) }); this.eventEmitter.emit("draw", { type: "point", value: b.data.value, index: b.data.valueIndex, meta: b.data.meta, series: f, seriesIndex: g, axisX: d, axisY: e, group: i, element: h, x: b.x, y: b.y }) }.bind(this)), m.showLine) { var p = i.elem("path", { d: o.stringify() }, a.classNames.line, !0); this.eventEmitter.emit("draw", { type: "line", values: b.normalized.series[g], path: o.clone(), chartRect: j, index: g, series: f, seriesIndex: g, seriesMeta: f.meta, axisX: d, axisY: e, group: i, element: p }) } if (m.showArea && e.range) { var q = Math.max(Math.min(m.areaBase, e.range.max), e.range.min), r = j.y1 - e.projectValue(q); o.splitByCommand("M").filter(function (a) { return a.pathElements.length > 1 }).map(function (a) { var b = a.pathElements[0], c = a.pathElements[a.pathElements.length - 1]; return a.clone(!0).position(0).remove(1).move(b.x, r).line(b.x, b.y).position(a.pathElements.length + 1).line(c.x, r) }).forEach(function (c) { var h = i.elem("path", { d: c.stringify() }, a.classNames.area, !0); this.eventEmitter.emit("draw", { type: "area", values: b.normalized.series[g], path: c.clone(), series: f, seriesIndex: g, axisX: d, axisY: e, chartRect: j, index: g, group: i, element: h }) }.bind(this)) } }.bind(this)), this.eventEmitter.emit("created", { bounds: e.bounds, chartRect: j, axisX: d, axisY: e, svg: this.svg, options: a }) } function e(a, b, d, e) { c.Line["super"].constructor.call(this, a, b, f, c.extend({}, f, d), e) } var f = { axisX: { offset: 30, position: "end", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, type: void 0 }, axisY: { offset: 40, position: "start", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, type: void 0, scaleMinSpace: 20, onlyInteger: !1 }, width: void 0, height: void 0, showLine: !0, showPoint: !0, showArea: !1, areaBase: 0, lineSmooth: !0, showGridBackground: !1, low: void 0, high: void 0, chartPadding: { top: 15, right: 15, bottom: 5, left: 10 }, fullWidth: !1, reverseData: !1, classNames: { chart: "ct-chart-line", label: "ct-label", labelGroup: "ct-labels", series: "ct-series", line: "ct-line", point: "ct-point", area: "ct-area", grid: "ct-grid", gridGroup: "ct-grids", gridBackground: "ct-grid-background", vertical: "ct-vertical", horizontal: "ct-horizontal", start: "ct-start", end: "ct-end" } }; c.Line = c.Base.extend({ constructor: e, createChart: d }) }(window, document, a), function (a, b, c) { + "use strict"; function d(a) { + var b, d; a.distributeSeries ? (b = c.normalizeData(this.data, a.reverseData, a.horizontalBars ? "x" : "y"), b.normalized.series = b.normalized.series.map(function (a) { return [a] })) : b = c.normalizeData(this.data, a.reverseData, a.horizontalBars ? "x" : "y"), this.svg = c.createSvg(this.container, a.width, a.height, a.classNames.chart + (a.horizontalBars ? " " + a.classNames.horizontalBars : "")); var e = this.svg.elem("g").addClass(a.classNames.gridGroup), g = this.svg.elem("g"), h = this.svg.elem("g").addClass(a.classNames.labelGroup); if (a.stackBars && 0 !== b.normalized.series.length) { + var i = c.serialMap(b.normalized.series, function () { + return Array.prototype.slice.call(arguments).map(function (a) { return a }).reduce(function (a, b) { return { x: a.x + (b && b.x) || 0, y: a.y + (b && b.y) || 0 } }, { x: 0, y: 0 }) + }); d = c.getHighLow([i], a, a.horizontalBars ? "x" : "y") + } else d = c.getHighLow(b.normalized.series, a, a.horizontalBars ? "x" : "y"); d.high = +a.high || (0 === a.high ? 0 : d.high), d.low = +a.low || (0 === a.low ? 0 : d.low); var j, k, l, m, n, o = c.createChartRect(this.svg, a, f.padding); k = a.distributeSeries && a.stackBars ? b.normalized.labels.slice(0, 1) : b.normalized.labels, a.horizontalBars ? (j = m = void 0 === a.axisX.type ? new c.AutoScaleAxis(c.Axis.units.x, b.normalized.series, o, c.extend({}, a.axisX, { highLow: d, referenceValue: 0 })) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, o, c.extend({}, a.axisX, { highLow: d, referenceValue: 0 })), l = n = void 0 === a.axisY.type ? new c.StepAxis(c.Axis.units.y, b.normalized.series, o, { ticks: k }) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, o, a.axisY)) : (l = m = void 0 === a.axisX.type ? new c.StepAxis(c.Axis.units.x, b.normalized.series, o, { ticks: k }) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, o, a.axisX), j = n = void 0 === a.axisY.type ? new c.AutoScaleAxis(c.Axis.units.y, b.normalized.series, o, c.extend({}, a.axisY, { highLow: d, referenceValue: 0 })) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, o, c.extend({}, a.axisY, { highLow: d, referenceValue: 0 }))); var p = a.horizontalBars ? o.x1 + j.projectValue(0) : o.y1 - j.projectValue(0), q = []; l.createGridAndLabels(e, h, this.supportsForeignObject, a, this.eventEmitter), j.createGridAndLabels(e, h, this.supportsForeignObject, a, this.eventEmitter), a.showGridBackground && c.createGridBackground(e, o, a.classNames.gridBackground, this.eventEmitter), b.raw.series.forEach(function (d, e) { var f, h, i = e - (b.raw.series.length - 1) / 2; f = a.distributeSeries && !a.stackBars ? l.axisLength / b.normalized.series.length / 2 : a.distributeSeries && a.stackBars ? l.axisLength / 2 : l.axisLength / b.normalized.series[e].length / 2, h = g.elem("g"), h.attr({ "ct:series-name": d.name, "ct:meta": c.serialize(d.meta) }), h.addClass([a.classNames.series, d.className || a.classNames.series + "-" + c.alphaNumerate(e)].join(" ")), b.normalized.series[e].forEach(function (g, k) { var r, s, t, u; if (u = a.distributeSeries && !a.stackBars ? e : a.distributeSeries && a.stackBars ? 0 : k, r = a.horizontalBars ? { x: o.x1 + j.projectValue(g && g.x ? g.x : 0, k, b.normalized.series[e]), y: o.y1 - l.projectValue(g && g.y ? g.y : 0, u, b.normalized.series[e]) } : { x: o.x1 + l.projectValue(g && g.x ? g.x : 0, u, b.normalized.series[e]), y: o.y1 - j.projectValue(g && g.y ? g.y : 0, k, b.normalized.series[e]) }, l instanceof c.StepAxis && (l.options.stretch || (r[l.units.pos] += f * (a.horizontalBars ? -1 : 1)), r[l.units.pos] += a.stackBars || a.distributeSeries ? 0 : i * a.seriesBarDistance * (a.horizontalBars ? -1 : 1)), t = q[k] || p, q[k] = t - (p - r[l.counterUnits.pos]), void 0 !== g) { var v = {}; v[l.units.pos + "1"] = r[l.units.pos], v[l.units.pos + "2"] = r[l.units.pos], !a.stackBars || "accumulate" !== a.stackMode && a.stackMode ? (v[l.counterUnits.pos + "1"] = p, v[l.counterUnits.pos + "2"] = r[l.counterUnits.pos]) : (v[l.counterUnits.pos + "1"] = t, v[l.counterUnits.pos + "2"] = q[k]), v.x1 = Math.min(Math.max(v.x1, o.x1), o.x2), v.x2 = Math.min(Math.max(v.x2, o.x1), o.x2), v.y1 = Math.min(Math.max(v.y1, o.y2), o.y1), v.y2 = Math.min(Math.max(v.y2, o.y2), o.y1); var w = c.getMetaData(d, k); s = h.elem("line", v, a.classNames.bar).attr({ "ct:value": [g.x, g.y].filter(c.isNumeric).join(","), "ct:meta": c.serialize(w) }), this.eventEmitter.emit("draw", c.extend({ type: "bar", value: g, index: k, meta: w, series: d, seriesIndex: e, axisX: m, axisY: n, chartRect: o, group: h, element: s }, v)) } }.bind(this)) }.bind(this)), this.eventEmitter.emit("created", { bounds: j.bounds, chartRect: o, axisX: m, axisY: n, svg: this.svg, options: a }) + } function e(a, b, d, e) { c.Bar["super"].constructor.call(this, a, b, f, c.extend({}, f, d), e) } var f = { axisX: { offset: 30, position: "end", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, scaleMinSpace: 30, onlyInteger: !1 }, axisY: { offset: 40, position: "start", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, scaleMinSpace: 20, onlyInteger: !1 }, width: void 0, height: void 0, high: void 0, low: void 0, referenceValue: 0, chartPadding: { top: 15, right: 15, bottom: 5, left: 10 }, seriesBarDistance: 15, stackBars: !1, stackMode: "accumulate", horizontalBars: !1, distributeSeries: !1, reverseData: !1, showGridBackground: !1, classNames: { chart: "ct-chart-bar", horizontalBars: "ct-horizontal-bars", label: "ct-label", labelGroup: "ct-labels", series: "ct-series", bar: "ct-bar", grid: "ct-grid", gridGroup: "ct-grids", gridBackground: "ct-grid-background", vertical: "ct-vertical", horizontal: "ct-horizontal", start: "ct-start", end: "ct-end" } }; c.Bar = c.Base.extend({ constructor: e, createChart: d }) + }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, c) { var d = b.x > a.x; return d && "explode" === c || !d && "implode" === c ? "start" : d && "implode" === c || !d && "explode" === c ? "end" : "middle" } function e(a) { var b, e, f, h, i, j = c.normalizeData(this.data), k = [], l = a.startAngle; this.svg = c.createSvg(this.container, a.width, a.height, a.donut ? a.classNames.chartDonut : a.classNames.chartPie), e = c.createChartRect(this.svg, a, g.padding), f = Math.min(e.width() / 2, e.height() / 2), i = a.total || j.normalized.series.reduce(function (a, b) { return a + b }, 0); var m = c.quantity(a.donutWidth); "%" === m.unit && (m.value *= f / 100), f -= a.donut && !a.donutSolid ? m.value / 2 : 0, h = "outside" === a.labelPosition || a.donut && !a.donutSolid ? f : "center" === a.labelPosition ? 0 : a.donutSolid ? f - m.value / 2 : f / 2, h += a.labelOffset; var n = { x: e.x1 + e.width() / 2, y: e.y2 + e.height() / 2 }, o = 1 === j.raw.series.filter(function (a) { return a.hasOwnProperty("value") ? 0 !== a.value : 0 !== a }).length; j.raw.series.forEach(function (a, b) { k[b] = this.svg.elem("g", null, null) }.bind(this)), a.showLabel && (b = this.svg.elem("g", null, null)), j.raw.series.forEach(function (e, g) { if (0 !== j.normalized.series[g] || !a.ignoreEmptyValues) { k[g].attr({ "ct:series-name": e.name }), k[g].addClass([a.classNames.series, e.className || a.classNames.series + "-" + c.alphaNumerate(g)].join(" ")); var p = i > 0 ? l + j.normalized.series[g] / i * 360 : 0, q = Math.max(0, l - (0 === g || o ? 0 : .2)); p - q >= 359.99 && (p = q + 359.99); var r, s, t, u = c.polarToCartesian(n.x, n.y, f, q), v = c.polarToCartesian(n.x, n.y, f, p), w = new c.Svg.Path(!a.donut || a.donutSolid).move(v.x, v.y).arc(f, f, 0, p - l > 180, 0, u.x, u.y); a.donut ? a.donutSolid && (t = f - m.value, r = c.polarToCartesian(n.x, n.y, t, l - (0 === g || o ? 0 : .2)), s = c.polarToCartesian(n.x, n.y, t, p), w.line(r.x, r.y), w.arc(t, t, 0, p - l > 180, 1, s.x, s.y)) : w.line(n.x, n.y); var x = a.classNames.slicePie; a.donut && (x = a.classNames.sliceDonut, a.donutSolid && (x = a.classNames.sliceDonutSolid)); var y = k[g].elem("path", { d: w.stringify() }, x); if (y.attr({ "ct:value": j.normalized.series[g], "ct:meta": c.serialize(e.meta) }), a.donut && !a.donutSolid && (y._node.style.strokeWidth = m.value + "px"), this.eventEmitter.emit("draw", { type: "slice", value: j.normalized.series[g], totalDataSum: i, index: g, meta: e.meta, series: e, group: k[g], element: y, path: w.clone(), center: n, radius: f, startAngle: l, endAngle: p }), a.showLabel) { var z; z = 1 === j.raw.series.length ? { x: n.x, y: n.y } : c.polarToCartesian(n.x, n.y, h, l + (p - l) / 2); var A; A = j.normalized.labels && !c.isFalseyButZero(j.normalized.labels[g]) ? j.normalized.labels[g] : j.normalized.series[g]; var B = a.labelInterpolationFnc(A, g); if (B || 0 === B) { var C = b.elem("text", { dx: z.x, dy: z.y, "text-anchor": d(n, z, a.labelDirection) }, a.classNames.label).text("" + B); this.eventEmitter.emit("draw", { type: "label", index: g, group: b, element: C, text: "" + B, x: z.x, y: z.y }) } } l = p } }.bind(this)), this.eventEmitter.emit("created", { chartRect: e, svg: this.svg, options: a }) } function f(a, b, d, e) { c.Pie["super"].constructor.call(this, a, b, g, c.extend({}, g, d), e) } var g = { width: void 0, height: void 0, chartPadding: 5, classNames: { chartPie: "ct-chart-pie", chartDonut: "ct-chart-donut", series: "ct-series", slicePie: "ct-slice-pie", sliceDonut: "ct-slice-donut", sliceDonutSolid: "ct-slice-donut-solid", label: "ct-label" }, startAngle: 0, total: void 0, donut: !1, donutSolid: !1, donutWidth: 60, showLabel: !0, labelOffset: 0, labelPosition: "inside", labelInterpolationFnc: c.noop, labelDirection: "neutral", reverseData: !1, ignoreEmptyValues: !1 }; c.Pie = c.Base.extend({ constructor: f, createChart: e, determineAnchorPosition: d }) }(window, document, a), a +}); + +var i, l, selectedLine = null; + +/* Navigate to hash without browser history entry */ +var navigateToHash = function () { + if (window.history !== undefined && window.history.replaceState !== undefined) { + window.history.replaceState(undefined, undefined, this.getAttribute("href")); + } +}; + +var hashLinks = document.getElementsByClassName('navigatetohash'); +for (i = 0, l = hashLinks.length; i < l; i++) { + hashLinks[i].addEventListener('click', navigateToHash); +} + +/* Switch test method */ +var switchTestMethod = function () { + var method = this.getAttribute("value"); + console.log("Selected test method: " + method); + + var lines, i, l, coverageData, lineAnalysis, cells; + + lines = document.querySelectorAll('.lineAnalysis tr'); + + for (i = 1, l = lines.length; i < l; i++) { + coverageData = JSON.parse(lines[i].getAttribute('data-coverage').replace(/'/g, '"')); + lineAnalysis = coverageData[method]; + cells = lines[i].querySelectorAll('td'); + if (lineAnalysis === undefined) { + lineAnalysis = coverageData.AllTestMethods; + if (lineAnalysis.LVS !== 'gray') { + cells[0].setAttribute('class', 'red'); + cells[1].innerText = cells[1].textContent = '0'; + cells[4].setAttribute('class', 'lightred'); + } + } else { + cells[0].setAttribute('class', lineAnalysis.LVS); + cells[1].innerText = cells[1].textContent = lineAnalysis.VC; + cells[4].setAttribute('class', 'light' + lineAnalysis.LVS); + } + } +}; + +var testMethods = document.getElementsByClassName('switchtestmethod'); +for (i = 0, l = testMethods.length; i < l; i++) { + testMethods[i].addEventListener('change', switchTestMethod); +} + +/* Highlight test method by line */ +var toggleLine = function () { + if (selectedLine === this) { + selectedLine = null; + } else { + selectedLine = null; + unhighlightTestMethods(); + highlightTestMethods.call(this); + selectedLine = this; + } + +}; +var highlightTestMethods = function () { + if (selectedLine !== null) { + return; + } + + var lineAnalysis; + var coverageData = JSON.parse(this.getAttribute('data-coverage').replace(/'/g, '"')); + var testMethods = document.getElementsByClassName('testmethod'); + + for (i = 0, l = testMethods.length; i < l; i++) { + lineAnalysis = coverageData[testMethods[i].id]; + if (lineAnalysis === undefined) { + testMethods[i].className = testMethods[i].className.replace(/\s*light.+/g, ""); + } else { + testMethods[i].className += ' light' + lineAnalysis.LVS; + } + } +}; +var unhighlightTestMethods = function () { + if (selectedLine !== null) { + return; + } + + var testMethods = document.getElementsByClassName('testmethod'); + for (i = 0, l = testMethods.length; i < l; i++) { + testMethods[i].className = testMethods[i].className.replace(/\s*light.+/g, ""); + } +}; +var coverableLines = document.getElementsByClassName('coverableline'); +for (i = 0, l = coverableLines.length; i < l; i++) { + coverableLines[i].addEventListener('click', toggleLine); + coverableLines[i].addEventListener('mouseenter', highlightTestMethods); + coverableLines[i].addEventListener('mouseleave', unhighlightTestMethods); +} + +/* History charts */ +var renderChart = function (chart) { + // Remove current children (e.g. PNG placeholder) + while (chart.firstChild) { + chart.firstChild.remove(); + } + + var chartData = window[chart.getAttribute('data-data')]; + var options = { + axisY: { + type: undefined, + onlyInteger: true + }, + lineSmooth: false, + low: 0, + high: 100, + scaleMinSpace: 20, + onlyInteger: true, + fullWidth: true + }; + var lineChart = new Chartist.Line(chart, { + labels: [], + series: chartData.series + }, options); + + /* Zoom */ + var zoomButtonDiv = document.createElement("div"); + zoomButtonDiv.className = "toggleZoom"; + var zoomButtonLink = document.createElement("a"); + zoomButtonLink.setAttribute("href", ""); + var zoomButtonText = document.createElement("i"); + zoomButtonText.className = "icon-search-plus"; + + zoomButtonLink.appendChild(zoomButtonText); + zoomButtonDiv.appendChild(zoomButtonLink); + + chart.appendChild(zoomButtonDiv); + + zoomButtonDiv.addEventListener('click', function (event) { + event.preventDefault(); + + if (options.axisY.type === undefined) { + options.axisY.type = Chartist.AutoScaleAxis; + zoomButtonText.className = "icon-search-minus"; + } else { + options.axisY.type = undefined; + zoomButtonText.className = "icon-search-plus"; + } + + lineChart.update(null, options); + }); + + var tooltip = document.createElement("div"); + tooltip.className = "tooltip"; + + chart.appendChild(tooltip); + + /* Tooltips */ + var showToolTip = function () { + var point = this; + var index = [].slice.call(chart.getElementsByClassName('ct-point')).indexOf(point); + + tooltip.innerHTML = chartData.tooltips[index % chartData.tooltips.length]; + tooltip.style.display = 'block'; + }; + + var moveToolTip = function (event) { + var box = chart.getBoundingClientRect(); + var left = event.pageX - box.left - window.pageXOffset; + var top = event.pageY - box.top - window.pageYOffset; + + tooltip.style.left = left - tooltip.offsetWidth / 2 - 5 + 'px'; + tooltip.style.top = top - tooltip.offsetHeight - 40 + 'px'; + }; + + var hideToolTip = function () { + tooltip.style.display = 'none'; + }; + + chart.addEventListener('mousemove', moveToolTip); + + lineChart.on('created', function () { + var chartPoints = chart.getElementsByClassName('ct-point'); + for (i = 0, l = chartPoints.length; i < l; i++) { + chartPoints[i].addEventListener('mousemove', showToolTip); + chartPoints[i].addEventListener('mouseout', hideToolTip); + } + }); +}; + +var charts = document.getElementsByClassName('historychart'); +for (i = 0, l = charts.length; i < l; i++) { + renderChart(charts[i]); +} \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/coverage.xml b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/coverage.xml new file mode 100644 index 00000000..dbfc7f84 --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/coverage.xml @@ -0,0 +1,1270 @@ + + + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll + 2021-02-09T23:13:42Z + mscorlib + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\vstest.console.exe + 2021-03-10T01:18:39.6988681Z + vstest.console + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll + 2021-03-10T01:18:39.1758657Z + Microsoft.TestPlatform.CoreUtilities + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll + 2020-10-08T02:21:33.8194762Z + System + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + 2021-03-10T01:18:39.3688664Z + Microsoft.VisualStudio.TestPlatform.ObjectModel + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.Client.dll + 2021-03-10T01:18:39.3608699Z + Microsoft.VisualStudio.TestPlatform.Client + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.Common.dll + 2021-03-10T01:18:39.3648679Z + Microsoft.VisualStudio.TestPlatform.Common + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll + 2021-02-09T23:13:40.5754059Z + System.Core + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll + 2021-03-10T01:18:39.1818661Z + Microsoft.TestPlatform.PlatformAbstractions + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll + 2019-12-07T09:10:37.7737543Z + System.Xml + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.Utilities.dll + 2021-03-10T01:18:39.1838668Z + Microsoft.TestPlatform.Utilities + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\NuGet.Frameworks.dll + 2021-03-10T01:18:39.455872Z + NuGet.Frameworks + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll + 2020-06-05T05:04:05.335002Z + System.Configuration + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.Extensions.FileSystemGlobbing.dll + 2021-03-10T01:18:39.1398684Z + Microsoft.Extensions.FileSystemGlobbing + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CrossPlatEngine.dll + 2021-03-10T01:18:39.1798685Z + Microsoft.TestPlatform.CrossPlatEngine + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.Cci.dll + 2021-03-10T01:18:40.1568669Z + Microsoft.Cci + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll + 2019-12-07T09:10:34.4923358Z + System.Runtime + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.InteropServices\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.InteropServices.dll + 2019-12-07T09:10:35.9300981Z + System.Runtime.InteropServices + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll + 2019-12-07T09:10:34.5705678Z + System.Collections + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.TestPlatform.Extensions.BlameDataCollector.dll + 2021-03-10T01:18:38.7708669Z + Microsoft.TestPlatform.Extensions.BlameDataCollector + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.TestPlatform.Extensions.EventLogCollector.dll + 2021-03-10T01:18:38.7728666Z + Microsoft.TestPlatform.Extensions.EventLogCollector + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.TestPlatform.TestHostRuntimeProvider.dll + 2021-03-10T01:18:38.7748692Z + Microsoft.TestPlatform.TestHostRuntimeProvider + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.ArchitectureTools.PEReader.dll + 2021-03-10T01:18:39.2018677Z + Microsoft.VisualStudio.ArchitectureTools.PEReader + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.Coverage.Interprocess.dll + 2021-03-10T01:18:38.7888677Z + Microsoft.VisualStudio.Coverage.Interprocess + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\netstandard\v4.0_2.0.0.0__cc7b13ffcd2ddd51\netstandard.dll + 2019-12-07T09:10:37.6330785Z + netstandard + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.ValueTuple\v4.0_4.0.0.0__cc7b13ffcd2ddd51\System.ValueTuple.dll + 2019-12-07T09:10:37.742541Z + System.ValueTuple + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.Fakes.DataCollector.dll + 2021-03-10T01:18:40.1588666Z + Microsoft.VisualStudio.Fakes.DataCollector + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.CodedWebTestAdapter.dll + 2021-03-10T01:18:38.7898664Z + Microsoft.VisualStudio.TestPlatform.Extensions.CodedWebTestAdapter + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.TmiAdapter.dll + 2021-03-10T01:18:38.7968662Z + Microsoft.VisualStudio.TestPlatform.Extensions.TmiAdapter + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.Common.dll + 2021-03-10T01:18:39.2388684Z + Microsoft.VisualStudio.QualityTools.Common + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.GenericTestAdapter.dll + 2021-03-10T01:18:38.7918661Z + Microsoft.VisualStudio.TestPlatform.Extensions.GenericTestAdapter + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger.dll + 2021-03-10T01:18:38.792866Z + Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.OrderedTestAdapter.dll + 2021-03-10T01:18:38.7948677Z + Microsoft.VisualStudio.TestPlatform.Extensions.OrderedTestAdapter + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.dll + 2021-03-10T01:18:38.7998656Z + Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.VSTestIntegration.dll + 2021-03-10T01:18:38.8038671Z + Microsoft.VisualStudio.TestPlatform.Extensions.VSTestIntegration + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll + 2021-03-10T01:18:39.3488657Z + Microsoft.VisualStudio.QualityTools.UnitTestFramework + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll + 2020-09-04T22:37:08Z + System.Data + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.WebTestAdapter.dll + 2021-03-10T01:18:38.8048665Z + Microsoft.VisualStudio.TestPlatform.Extensions.WebTestAdapter + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.CppUnitTestFramework.ComInterfaces.dll + 2021-03-10T01:18:38.8068659Z + Microsoft.VisualStudio.TestTools.CppUnitTestFramework.ComInterfaces + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.CppUnitTestFramework.CppUnitTestExtension.dll + 2021-03-10T01:18:38.8078663Z + Microsoft.VisualStudio.TestTools.CppUnitTestFramework.CppUnitTestExtension + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.DataCollection.MediaRecorder.Model.dll + 2021-03-10T01:18:38.8098657Z + Microsoft.VisualStudio.TestTools.DataCollection.MediaRecorder.Model + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.DataCollection.VideoRecorderCollector.dll + 2021-03-10T01:18:38.8118659Z + Microsoft.VisualStudio.TestTools.DataCollection.VideoRecorderCollector + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TraceDataCollector.dll + 2021-03-10T01:18:38.8168685Z + Microsoft.VisualStudio.TraceDataCollector + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.IntelliTrace.Core.dll + 2021-03-10T01:18:39.1678662Z + Microsoft.IntelliTrace.Core + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\System.Reflection.Metadata.dll + 2021-03-10T01:18:39.5908676Z + System.Reflection.Metadata + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.dll + 2019-12-07T09:10:34.5552678Z + System.IO + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\System.Collections.Immutable.dll + 2021-03-10T01:18:39.5778692Z + System.Collections.Immutable + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll + 2019-12-07T09:10:36.0237545Z + System.Reflection + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO.FileSystem\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.FileSystem.dll + 2019-12-07T09:10:36.0095245Z + System.IO.FileSystem + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO.MemoryMappedFiles\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.MemoryMappedFiles.dll + 2019-12-07T09:10:34.5552678Z + System.IO.MemoryMappedFiles + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Handles\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Handles.dll + 2019-12-07T09:10:37.6643716Z + System.Runtime.Handles + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll + 2019-12-07T09:10:37.7113237Z + System.Reflection.Extensions + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll + 2019-12-07T09:10:34.5860246Z + System.Threading + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Text.Encoding\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Text.Encoding.dll + 2019-12-07T09:10:36.0705812Z + System.Text.Encoding + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll + 2019-12-07T09:10:37.6174386Z + System.Runtime.Extensions + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Text.Encoding.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Text.Encoding.Extensions.dll + 2019-12-07T09:10:36.1495949Z + System.Text.Encoding.Extensions + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.Fakes.dll + 2021-03-10T01:18:40.1608669Z + Microsoft.VisualStudio.TestPlatform.Fakes + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CommunicationUtilities.dll + 2021-03-10T01:18:39.1748663Z + Microsoft.TestPlatform.CommunicationUtilities + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Newtonsoft.Json.dll + 2021-03-10T01:18:39.451869Z + Newtonsoft.Json + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll + 2018-06-05T04:51:36Z + Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Resources.ResourceManager\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Resources.ResourceManager.dll + 2019-12-07T09:10:37.6960843Z + System.Resources.ResourceManager + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + 2018-06-05T04:47:02Z + Microsoft.VisualStudio.TestPlatform.TestFramework + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll + 2018-06-05T04:50:06Z + Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll + 2018-06-05T04:54:38Z + Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.ComponentModel.Composition\v4.0_4.0.0.0__b77a5c561934e089\System.ComponentModel.Composition.dll + 2019-12-07T09:10:36.1023351Z + System.ComponentModel.Composition + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll + 2019-12-07T09:10:36.086204Z + System.Linq + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml.ReaderWriter\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Xml.ReaderWriter.dll + 2019-12-07T09:10:34.5705678Z + System.Xml.ReaderWriter + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml.XDocument\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Xml.XDocument.dll + 2019-12-07T09:10:34.5552678Z + System.Xml.XDocument + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml.Linq\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.Linq.dll + 2019-12-07T09:10:35.962301Z + System.Xml.Linq + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll + 2019-12-07T09:10:36.086204Z + System.Globalization + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + 2021-03-10T01:18:39.3688664Z + Microsoft.VisualStudio.TestPlatform.ObjectModel + + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\UnitTestProject1.dll + 2019-04-19T07:50:12.1584913Z + UnitTestProject1 + + + + + + + <Module> + + + + + UnitTestProject1.UnitTest1 + + + + 100663297 + System.Void UnitTestProject1.UnitTest1::TestMethod1() + + + + + + + + + + + + + + 100663298 + System.Void UnitTestProject1.UnitTest1::TestMethod2() + + + + + + + + + + + + + + 100663299 + System.Void UnitTestProject1.UnitTest1::.ctor() + + + + + + + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll + 2021-03-10T01:18:39.1818661Z + Microsoft.TestPlatform.PlatformAbstractions + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll + 2018-06-05T04:54:38Z + Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll + 2020-10-08T02:21:33.8194762Z + System + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + 2021-03-10T01:18:39.3688664Z + Microsoft.VisualStudio.TestPlatform.ObjectModel + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll + 2021-03-10T01:18:39.1758657Z + Microsoft.TestPlatform.CoreUtilities + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll + 2021-03-10T01:18:39.1818661Z + Microsoft.TestPlatform.PlatformAbstractions + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll + 2018-06-05T04:54:38Z + Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll + 2020-10-08T02:21:33.8194762Z + System + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll + 2018-06-05T04:51:36Z + Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll + 2019-12-07T09:10:34.4923358Z + System.Runtime + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + 2018-06-05T04:47:02Z + Microsoft.VisualStudio.TestPlatform.TestFramework + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll + 2019-12-07T09:10:34.5705678Z + System.Collections + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll + 2018-06-05T04:50:06Z + Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll + 2019-12-07T09:10:36.0237545Z + System.Reflection + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll + 2019-12-07T09:10:36.086204Z + System.Globalization + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll + 2019-12-07T09:10:36.086204Z + System.Linq + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll + 2021-02-09T23:13:40.5754059Z + System.Core + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll + 2019-12-07T09:10:34.5860246Z + System.Threading + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll + 2019-12-07T09:10:37.7113237Z + System.Reflection.Extensions + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll + 2019-12-07T09:10:34.5705678Z + System.Threading.Tasks + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + 2021-03-10T01:18:39.3688664Z + Microsoft.VisualStudio.TestPlatform.ObjectModel + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll + 2019-12-07T09:10:37.6174386Z + System.Runtime.Extensions + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + 2018-06-05T04:53:44Z + Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Serialization\v4.0_4.0.0.0__b77a5c561934e089\System.Runtime.Serialization.dll + 2020-08-05T02:39:55.9298663Z + System.Runtime.Serialization + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + 2021-03-10T01:18:39.3688664Z + Microsoft.VisualStudio.TestPlatform.ObjectModel + + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject2\bin\debug\UnitTestProject2.dll + 2019-04-19T07:50:12.1584913Z + UnitTestProject2 + + + + + + + <Module> + + + + + UnitTestProject2.UnitTest1 + + + + 100663297 + System.Void UnitTestProject2.UnitTest1::TestMethod1() + + + + + + + + + + + + 100663298 + System.Void UnitTestProject2.UnitTest1::.ctor() + + + + + + + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll + 2021-03-10T01:18:39.1818661Z + Microsoft.TestPlatform.PlatformAbstractions + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll + 2018-06-05T04:54:38Z + Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + 2021-03-10T01:18:39.3688664Z + Microsoft.VisualStudio.TestPlatform.ObjectModel + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll + 2021-03-10T01:18:39.1758657Z + Microsoft.TestPlatform.CoreUtilities + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll + 2021-03-10T01:18:39.1818661Z + Microsoft.TestPlatform.PlatformAbstractions + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll + 2018-06-05T04:54:38Z + Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll + 2020-10-08T02:21:33.8194762Z + System + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll + 2018-06-05T04:51:36Z + Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll + 2019-12-07T09:10:34.4923358Z + System.Runtime + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + 2018-06-05T04:47:02Z + Microsoft.VisualStudio.TestPlatform.TestFramework + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll + 2019-12-07T09:10:34.5705678Z + System.Collections + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll + 2018-06-05T04:50:06Z + Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll + 2019-12-07T09:10:36.0237545Z + System.Reflection + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll + 2019-12-07T09:10:36.086204Z + System.Globalization + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll + 2019-12-07T09:10:36.086204Z + System.Linq + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll + 2021-02-09T23:13:40.5754059Z + System.Core + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll + 2019-12-07T09:10:34.5860246Z + System.Threading + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll + 2019-12-07T09:10:37.7113237Z + System.Reflection.Extensions + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll + 2019-12-07T09:10:34.5705678Z + System.Threading.Tasks + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + 2021-03-10T01:18:39.3688664Z + Microsoft.VisualStudio.TestPlatform.ObjectModel + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll + 2019-12-07T09:10:37.6174386Z + System.Runtime.Extensions + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + 2018-06-05T04:53:44Z + Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Serialization\v4.0_4.0.0.0__b77a5c561934e089\System.Runtime.Serialization.dll + 2020-08-05T02:39:55.9298663Z + System.Runtime.Serialization + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections.Concurrent\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.Concurrent.dll + 2019-12-07T09:10:34.5552678Z + System.Collections.Concurrent + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll + 2019-12-07T09:10:34.5705678Z + System.Threading.Tasks + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll + 2021-03-10T01:18:39.1818661Z + Microsoft.TestPlatform.PlatformAbstractions + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll + 2018-06-05T04:54:38Z + Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + 2021-03-10T01:18:39.3688664Z + Microsoft.VisualStudio.TestPlatform.ObjectModel + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll + 2021-03-10T01:18:39.1758657Z + Microsoft.TestPlatform.CoreUtilities + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll + 2021-03-10T01:18:39.1818661Z + Microsoft.TestPlatform.PlatformAbstractions + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll + 2018-06-05T04:54:38Z + Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll + 2020-10-08T02:21:33.8194762Z + System + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll + 2018-06-05T04:51:36Z + Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll + 2019-12-07T09:10:34.4923358Z + System.Runtime + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + 2018-06-05T04:47:02Z + Microsoft.VisualStudio.TestPlatform.TestFramework + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll + 2019-12-07T09:10:34.5705678Z + System.Collections + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll + 2019-12-07T09:10:36.0237545Z + System.Reflection + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll + 2019-12-07T09:10:37.6174386Z + System.Runtime.Extensions + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll + 2018-06-05T04:50:06Z + Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll + 2019-12-07T09:10:36.086204Z + System.Linq + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll + 2021-02-09T23:13:40.5754059Z + System.Core + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll + 2019-12-07T09:10:36.086204Z + System.Globalization + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.dll + 2019-12-07T09:10:34.5552678Z + System.IO + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + 2018-06-05T04:53:44Z + Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll + 2020-09-04T22:37:08Z + System.Data + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll + 2019-12-07T09:10:34.5860246Z + System.Threading + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll + 2019-12-07T09:10:37.7113237Z + System.Reflection.Extensions + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll + 2019-12-07T09:10:34.5705678Z + System.Threading.Tasks + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll + 2020-06-05T05:04:05.335002Z + System.Configuration + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll + 2019-12-07T09:10:37.7737543Z + System.Xml + + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\ClassLibrary1.dll + 2019-04-19T06:37:47.6105603Z + ClassLibrary1 + + + + + + + <Module> + + + + + ClassLibrary1.Calculation + + + + 100663297 + System.Int32 ClassLibrary1.Calculation::Add(System.Int32,System.Int32) + + + + + + + + + + + + 100663298 + System.Int32 ClassLibrary1.Calculation::Sub(System.Int32,System.Int32) + + + + + + + + + + + + 100663299 + System.Int32 ClassLibrary1.Calculation::Sub1(System.Int32,System.Int32) + + + + + + + + + + + + 100663300 + System.Void ClassLibrary1.Calculation::.ctor() + + + + + + + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Resources.ResourceManager\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Resources.ResourceManager.dll + 2019-12-07T09:10:37.6960843Z + System.Resources.ResourceManager + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + 2021-03-10T01:18:39.3688664Z + Microsoft.VisualStudio.TestPlatform.ObjectModel + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll + 2021-03-10T01:18:39.1758657Z + Microsoft.TestPlatform.CoreUtilities + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Text.RegularExpressions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Text.RegularExpressions.dll + 2019-12-07T09:10:34.4610177Z + System.Text.RegularExpressions + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll + 2021-03-10T01:18:39.1818661Z + Microsoft.TestPlatform.PlatformAbstractions + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll + 2018-06-05T04:54:38Z + Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + 2021-03-10T01:18:39.3688664Z + Microsoft.VisualStudio.TestPlatform.ObjectModel + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll + 2021-03-10T01:18:39.1758657Z + Microsoft.TestPlatform.CoreUtilities + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll + 2021-03-10T01:18:39.1818661Z + Microsoft.TestPlatform.PlatformAbstractions + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll + 2018-06-05T04:54:38Z + Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll + 2020-10-08T02:21:33.8194762Z + System + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll + 2018-06-05T04:51:36Z + Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll + 2019-12-07T09:10:34.4923358Z + System.Runtime + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + 2018-06-05T04:47:02Z + Microsoft.VisualStudio.TestPlatform.TestFramework + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll + 2019-12-07T09:10:34.5705678Z + System.Collections + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll + 2019-12-07T09:10:36.0237545Z + System.Reflection + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll + 2019-12-07T09:10:37.6174386Z + System.Runtime.Extensions + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll + 2018-06-05T04:50:06Z + Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll + 2019-12-07T09:10:36.086204Z + System.Linq + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll + 2021-02-09T23:13:40.5754059Z + System.Core + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll + 2019-12-07T09:10:36.086204Z + System.Globalization + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.dll + 2019-12-07T09:10:34.5552678Z + System.IO + + + + E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + 2018-06-05T04:53:44Z + Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll + 2020-09-04T22:37:08Z + System.Data + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll + 2019-12-07T09:10:34.5860246Z + System.Threading + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll + 2019-12-07T09:10:37.7113237Z + System.Reflection.Extensions + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll + 2019-12-07T09:10:34.5705678Z + System.Threading.Tasks + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll + 2020-06-05T05:04:05.335002Z + System.Configuration + + + + C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll + 2019-12-07T09:10:37.7737543Z + System.Xml + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.ExecutionCommon.dll + 2021-03-10T01:18:39.2588672Z + Microsoft.VisualStudio.QualityTools.ExecutionCommon + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.Resource.dll + 2021-03-10T01:18:39.3078688Z + Microsoft.VisualStudio.QualityTools.Resource + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.WebTestFramework.dll + 2021-03-10T01:18:39.3588667Z + Microsoft.VisualStudio.QualityTools.WebTestFramework + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + 2021-03-10T01:18:39.3688664Z + Microsoft.VisualStudio.TestPlatform.ObjectModel + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + 2021-03-10T01:18:39.3688664Z + Microsoft.VisualStudio.TestPlatform.ObjectModel + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + 2021-03-10T01:18:39.3688664Z + Microsoft.VisualStudio.TestPlatform.ObjectModel + + + + C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + 2021-03-10T01:18:39.3688664Z + Microsoft.VisualStudio.TestPlatform.ObjectModel + + + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_cube.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_cube.svg new file mode 100644 index 00000000..11b5cabf --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_cube.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_down-dir_active.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_down-dir_active.svg new file mode 100644 index 00000000..d11cf041 --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_down-dir_active.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_fork.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_fork.svg new file mode 100644 index 00000000..f0148b3a --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_fork.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_info-circled.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_info-circled.svg new file mode 100644 index 00000000..252166bb --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_info-circled.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_minus.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_minus.svg new file mode 100644 index 00000000..3c30c365 --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_minus.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_plus.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_plus.svg new file mode 100644 index 00000000..79327232 --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_plus.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-minus.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-minus.svg new file mode 100644 index 00000000..c174eb5e --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-minus.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-plus.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-plus.svg new file mode 100644 index 00000000..04b24ecc --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-plus.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir.svg new file mode 100644 index 00000000..567c11f3 --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir_active.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir_active.svg new file mode 100644 index 00000000..bb225544 --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir_active.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_wrench.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_wrench.svg new file mode 100644 index 00000000..0e9a8601 --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_wrench.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/index.htm b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/index.htm new file mode 100644 index 00000000..f2f23f7e --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/index.htm @@ -0,0 +1,71 @@ + + + + + + +Summary - Coverage Report + +
+

Summary

+ ++++ + + + + + + + + + + + + + + + +
Generated on:2021/3/26 - 下午 04:34:49
Parser:OpenCoverParser
Assemblies:3
Classes:3
Files:3
Covered lines:15
Uncovered lines:7
Coverable lines:22
Total lines:59
Line coverage:68.1% (15 of 22)
Covered branches:0
Total branches:0
Tag:Build.2019.11.11
+

Coverage History

+
+ +

Risk Hotspots

+ + +

No risk hotspots found.

+

Coverage

+ + +++++++++++ + + + + + + + + + +
NameCoveredUncoveredCoverableTotalLine coverageBranch coverage
ClassLibrary13691833.3%
  
 
ClassLibrary1.Calculation3691833.3%
  
 
UnitTestProject191102690%
  
 
UnitTestProject1.UnitTest191102690%
  
 
UnitTestProject230315100%
 
 
UnitTestProject2.UnitTest130315100%
 
 
+
+
+ + \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/main.js b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/main.js new file mode 100644 index 00000000..160252eb --- /dev/null +++ b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/main.js @@ -0,0 +1,274 @@ +/* Chartist.js 0.11.0 + * Copyright © 2017 Gion Kunz + * Free to use under either the WTFPL license or the MIT license. + * https://raw.githubusercontent.com/gionkunz/chartist-js/master/LICENSE-WTFPL + * https://raw.githubusercontent.com/gionkunz/chartist-js/master/LICENSE-MIT + */ + +!function (a, b) { "function" == typeof define && define.amd ? define("Chartist", [], function () { return a.Chartist = b() }) : "object" == typeof module && module.exports ? module.exports = b() : a.Chartist = b() }(this, function () { + var a = { version: "0.11.0" }; return function (a, b, c) { "use strict"; c.namespaces = { svg: "http://www.w3.org/2000/svg", xmlns: "http://www.w3.org/2000/xmlns/", xhtml: "http://www.w3.org/1999/xhtml", xlink: "http://www.w3.org/1999/xlink", ct: "http://gionkunz.github.com/chartist-js/ct" }, c.noop = function (a) { return a }, c.alphaNumerate = function (a) { return String.fromCharCode(97 + a % 26) }, c.extend = function (a) { var b, d, e; for (a = a || {}, b = 1; b < arguments.length; b++) { d = arguments[b]; for (var f in d) e = d[f], "object" != typeof e || null === e || e instanceof Array ? a[f] = e : a[f] = c.extend(a[f], e) } return a }, c.replaceAll = function (a, b, c) { return a.replace(new RegExp(b, "g"), c) }, c.ensureUnit = function (a, b) { return "number" == typeof a && (a += b), a }, c.quantity = function (a) { if ("string" == typeof a) { var b = /^(\d+)\s*(.*)$/g.exec(a); return { value: +b[1], unit: b[2] || void 0 } } return { value: a } }, c.querySelector = function (a) { return a instanceof Node ? a : b.querySelector(a) }, c.times = function (a) { return Array.apply(null, new Array(a)) }, c.sum = function (a, b) { return a + (b ? b : 0) }, c.mapMultiply = function (a) { return function (b) { return b * a } }, c.mapAdd = function (a) { return function (b) { return b + a } }, c.serialMap = function (a, b) { var d = [], e = Math.max.apply(null, a.map(function (a) { return a.length })); return c.times(e).forEach(function (c, e) { var f = a.map(function (a) { return a[e] }); d[e] = b.apply(null, f) }), d }, c.roundWithPrecision = function (a, b) { var d = Math.pow(10, b || c.precision); return Math.round(a * d) / d }, c.precision = 8, c.escapingMap = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }, c.serialize = function (a) { return null === a || void 0 === a ? a : ("number" == typeof a ? a = "" + a : "object" == typeof a && (a = JSON.stringify({ data: a })), Object.keys(c.escapingMap).reduce(function (a, b) { return c.replaceAll(a, b, c.escapingMap[b]) }, a)) }, c.deserialize = function (a) { if ("string" != typeof a) return a; a = Object.keys(c.escapingMap).reduce(function (a, b) { return c.replaceAll(a, c.escapingMap[b], b) }, a); try { a = JSON.parse(a), a = void 0 !== a.data ? a.data : a } catch (b) { } return a }, c.createSvg = function (a, b, d, e) { var f; return b = b || "100%", d = d || "100%", Array.prototype.slice.call(a.querySelectorAll("svg")).filter(function (a) { return a.getAttributeNS(c.namespaces.xmlns, "ct") }).forEach(function (b) { a.removeChild(b) }), f = new c.Svg("svg").attr({ width: b, height: d }).addClass(e), f._node.style.width = b, f._node.style.height = d, a.appendChild(f._node), f }, c.normalizeData = function (a, b, d) { var e, f = { raw: a, normalized: {} }; return f.normalized.series = c.getDataArray({ series: a.series || [] }, b, d), e = f.normalized.series.every(function (a) { return a instanceof Array }) ? Math.max.apply(null, f.normalized.series.map(function (a) { return a.length })) : f.normalized.series.length, f.normalized.labels = (a.labels || []).slice(), Array.prototype.push.apply(f.normalized.labels, c.times(Math.max(0, e - f.normalized.labels.length)).map(function () { return "" })), b && c.reverseData(f.normalized), f }, c.safeHasProperty = function (a, b) { return null !== a && "object" == typeof a && a.hasOwnProperty(b) }, c.isDataHoleValue = function (a) { return null === a || void 0 === a || "number" == typeof a && isNaN(a) }, c.reverseData = function (a) { a.labels.reverse(), a.series.reverse(); for (var b = 0; b < a.series.length; b++)"object" == typeof a.series[b] && void 0 !== a.series[b].data ? a.series[b].data.reverse() : a.series[b] instanceof Array && a.series[b].reverse() }, c.getDataArray = function (a, b, d) { function e(a) { if (c.safeHasProperty(a, "value")) return e(a.value); if (c.safeHasProperty(a, "data")) return e(a.data); if (a instanceof Array) return a.map(e); if (!c.isDataHoleValue(a)) { if (d) { var b = {}; return "string" == typeof d ? b[d] = c.getNumberOrUndefined(a) : b.y = c.getNumberOrUndefined(a), b.x = a.hasOwnProperty("x") ? c.getNumberOrUndefined(a.x) : b.x, b.y = a.hasOwnProperty("y") ? c.getNumberOrUndefined(a.y) : b.y, b } return c.getNumberOrUndefined(a) } } return a.series.map(e) }, c.normalizePadding = function (a, b) { return b = b || 0, "number" == typeof a ? { top: a, right: a, bottom: a, left: a } : { top: "number" == typeof a.top ? a.top : b, right: "number" == typeof a.right ? a.right : b, bottom: "number" == typeof a.bottom ? a.bottom : b, left: "number" == typeof a.left ? a.left : b } }, c.getMetaData = function (a, b) { var c = a.data ? a.data[b] : a[b]; return c ? c.meta : void 0 }, c.orderOfMagnitude = function (a) { return Math.floor(Math.log(Math.abs(a)) / Math.LN10) }, c.projectLength = function (a, b, c) { return b / c.range * a }, c.getAvailableHeight = function (a, b) { return Math.max((c.quantity(b.height).value || a.height()) - (b.chartPadding.top + b.chartPadding.bottom) - b.axisX.offset, 0) }, c.getHighLow = function (a, b, d) { function e(a) { if (void 0 !== a) if (a instanceof Array) for (var b = 0; b < a.length; b++)e(a[b]); else { var c = d ? +a[d] : +a; g && c > f.high && (f.high = c), h && c < f.low && (f.low = c) } } b = c.extend({}, b, d ? b["axis" + d.toUpperCase()] : {}); var f = { high: void 0 === b.high ? -Number.MAX_VALUE : +b.high, low: void 0 === b.low ? Number.MAX_VALUE : +b.low }, g = void 0 === b.high, h = void 0 === b.low; return (g || h) && e(a), (b.referenceValue || 0 === b.referenceValue) && (f.high = Math.max(b.referenceValue, f.high), f.low = Math.min(b.referenceValue, f.low)), f.high <= f.low && (0 === f.low ? f.high = 1 : f.low < 0 ? f.high = 0 : f.high > 0 ? f.low = 0 : (f.high = 1, f.low = 0)), f }, c.isNumeric = function (a) { return null !== a && isFinite(a) }, c.isFalseyButZero = function (a) { return !a && 0 !== a }, c.getNumberOrUndefined = function (a) { return c.isNumeric(a) ? +a : void 0 }, c.isMultiValue = function (a) { return "object" == typeof a && ("x" in a || "y" in a) }, c.getMultiValue = function (a, b) { return c.isMultiValue(a) ? c.getNumberOrUndefined(a[b || "y"]) : c.getNumberOrUndefined(a) }, c.rho = function (a) { function b(a, c) { return a % c === 0 ? c : b(c, a % c) } function c(a) { return a * a + 1 } if (1 === a) return a; var d, e = 2, f = 2; if (a % 2 === 0) return 2; do e = c(e) % a, f = c(c(f)) % a, d = b(Math.abs(e - f), a); while (1 === d); return d }, c.getBounds = function (a, b, d, e) { function f(a, b) { return a === (a += b) && (a *= 1 + (b > 0 ? o : -o)), a } var g, h, i, j = 0, k = { high: b.high, low: b.low }; k.valueRange = k.high - k.low, k.oom = c.orderOfMagnitude(k.valueRange), k.step = Math.pow(10, k.oom), k.min = Math.floor(k.low / k.step) * k.step, k.max = Math.ceil(k.high / k.step) * k.step, k.range = k.max - k.min, k.numberOfSteps = Math.round(k.range / k.step); var l = c.projectLength(a, k.step, k), m = l < d, n = e ? c.rho(k.range) : 0; if (e && c.projectLength(a, 1, k) >= d) k.step = 1; else if (e && n < k.step && c.projectLength(a, n, k) >= d) k.step = n; else for (; ;) { if (m && c.projectLength(a, k.step, k) <= d) k.step *= 2; else { if (m || !(c.projectLength(a, k.step / 2, k) >= d)) break; if (k.step /= 2, e && k.step % 1 !== 0) { k.step *= 2; break } } if (j++ > 1e3) throw new Error("Exceeded maximum number of iterations while optimizing scale step!") } var o = 2.221e-16; for (k.step = Math.max(k.step, o), h = k.min, i = k.max; h + k.step <= k.low;)h = f(h, k.step); for (; i - k.step >= k.high;)i = f(i, -k.step); k.min = h, k.max = i, k.range = k.max - k.min; var p = []; for (g = k.min; g <= k.max; g = f(g, k.step)) { var q = c.roundWithPrecision(g); q !== p[p.length - 1] && p.push(q) } return k.values = p, k }, c.polarToCartesian = function (a, b, c, d) { var e = (d - 90) * Math.PI / 180; return { x: a + c * Math.cos(e), y: b + c * Math.sin(e) } }, c.createChartRect = function (a, b, d) { var e = !(!b.axisX && !b.axisY), f = e ? b.axisY.offset : 0, g = e ? b.axisX.offset : 0, h = a.width() || c.quantity(b.width).value || 0, i = a.height() || c.quantity(b.height).value || 0, j = c.normalizePadding(b.chartPadding, d); h = Math.max(h, f + j.left + j.right), i = Math.max(i, g + j.top + j.bottom); var k = { padding: j, width: function () { return this.x2 - this.x1 }, height: function () { return this.y1 - this.y2 } }; return e ? ("start" === b.axisX.position ? (k.y2 = j.top + g, k.y1 = Math.max(i - j.bottom, k.y2 + 1)) : (k.y2 = j.top, k.y1 = Math.max(i - j.bottom - g, k.y2 + 1)), "start" === b.axisY.position ? (k.x1 = j.left + f, k.x2 = Math.max(h - j.right, k.x1 + 1)) : (k.x1 = j.left, k.x2 = Math.max(h - j.right - f, k.x1 + 1))) : (k.x1 = j.left, k.x2 = Math.max(h - j.right, k.x1 + 1), k.y2 = j.top, k.y1 = Math.max(i - j.bottom, k.y2 + 1)), k }, c.createGrid = function (a, b, d, e, f, g, h, i) { var j = {}; j[d.units.pos + "1"] = a, j[d.units.pos + "2"] = a, j[d.counterUnits.pos + "1"] = e, j[d.counterUnits.pos + "2"] = e + f; var k = g.elem("line", j, h.join(" ")); i.emit("draw", c.extend({ type: "grid", axis: d, index: b, group: g, element: k }, j)) }, c.createGridBackground = function (a, b, c, d) { var e = a.elem("rect", { x: b.x1, y: b.y2, width: b.width(), height: b.height() }, c, !0); d.emit("draw", { type: "gridBackground", group: a, element: e }) }, c.createLabel = function (a, d, e, f, g, h, i, j, k, l, m) { var n, o = {}; if (o[g.units.pos] = a + i[g.units.pos], o[g.counterUnits.pos] = i[g.counterUnits.pos], o[g.units.len] = d, o[g.counterUnits.len] = Math.max(0, h - 10), l) { var p = b.createElement("span"); p.className = k.join(" "), p.setAttribute("xmlns", c.namespaces.xhtml), p.innerText = f[e], p.style[g.units.len] = Math.round(o[g.units.len]) + "px", p.style[g.counterUnits.len] = Math.round(o[g.counterUnits.len]) + "px", n = j.foreignObject(p, c.extend({ style: "overflow: visible;" }, o)) } else n = j.elem("text", o, k.join(" ")).text(f[e]); m.emit("draw", c.extend({ type: "label", axis: g, index: e, group: j, element: n, text: f[e] }, o)) }, c.getSeriesOption = function (a, b, c) { if (a.name && b.series && b.series[a.name]) { var d = b.series[a.name]; return d.hasOwnProperty(c) ? d[c] : b[c] } return b[c] }, c.optionsProvider = function (b, d, e) { function f(b) { var f = h; if (h = c.extend({}, j), d) for (i = 0; i < d.length; i++) { var g = a.matchMedia(d[i][0]); g.matches && (h = c.extend(h, d[i][1])) } e && b && e.emit("optionsChanged", { previousOptions: f, currentOptions: h }) } function g() { k.forEach(function (a) { a.removeListener(f) }) } var h, i, j = c.extend({}, b), k = []; if (!a.matchMedia) throw "window.matchMedia not found! Make sure you're using a polyfill."; if (d) for (i = 0; i < d.length; i++) { var l = a.matchMedia(d[i][0]); l.addListener(f), k.push(l) } return f(), { removeMediaQueryListeners: g, getCurrentOptions: function () { return c.extend({}, h) } } }, c.splitIntoSegments = function (a, b, d) { var e = { increasingX: !1, fillHoles: !1 }; d = c.extend({}, e, d); for (var f = [], g = !0, h = 0; h < a.length; h += 2)void 0 === c.getMultiValue(b[h / 2].value) ? d.fillHoles || (g = !0) : (d.increasingX && h >= 2 && a[h] <= a[h - 2] && (g = !0), g && (f.push({ pathCoordinates: [], valueData: [] }), g = !1), f[f.length - 1].pathCoordinates.push(a[h], a[h + 1]), f[f.length - 1].valueData.push(b[h / 2])); return f } }(window, document, a), function (a, b, c) { "use strict"; c.Interpolation = {}, c.Interpolation.none = function (a) { var b = { fillHoles: !1 }; return a = c.extend({}, b, a), function (b, d) { for (var e = new c.Svg.Path, f = !0, g = 0; g < b.length; g += 2) { var h = b[g], i = b[g + 1], j = d[g / 2]; void 0 !== c.getMultiValue(j.value) ? (f ? e.move(h, i, !1, j) : e.line(h, i, !1, j), f = !1) : a.fillHoles || (f = !0) } return e } }, c.Interpolation.simple = function (a) { var b = { divisor: 2, fillHoles: !1 }; a = c.extend({}, b, a); var d = 1 / Math.max(1, a.divisor); return function (b, e) { for (var f, g, h, i = new c.Svg.Path, j = 0; j < b.length; j += 2) { var k = b[j], l = b[j + 1], m = (k - f) * d, n = e[j / 2]; void 0 !== n.value ? (void 0 === h ? i.move(k, l, !1, n) : i.curve(f + m, g, k - m, l, k, l, !1, n), f = k, g = l, h = n) : a.fillHoles || (f = k = h = void 0) } return i } }, c.Interpolation.cardinal = function (a) { var b = { tension: 1, fillHoles: !1 }; a = c.extend({}, b, a); var d = Math.min(1, Math.max(0, a.tension)), e = 1 - d; return function f(b, g) { var h = c.splitIntoSegments(b, g, { fillHoles: a.fillHoles }); if (h.length) { if (h.length > 1) { var i = []; return h.forEach(function (a) { i.push(f(a.pathCoordinates, a.valueData)) }), c.Svg.Path.join(i) } if (b = h[0].pathCoordinates, g = h[0].valueData, b.length <= 4) return c.Interpolation.none()(b, g); for (var j, k = (new c.Svg.Path).move(b[0], b[1], !1, g[0]), l = 0, m = b.length; m - 2 * !j > l; l += 2) { var n = [{ x: +b[l - 2], y: +b[l - 1] }, { x: +b[l], y: +b[l + 1] }, { x: +b[l + 2], y: +b[l + 3] }, { x: +b[l + 4], y: +b[l + 5] }]; j ? l ? m - 4 === l ? n[3] = { x: +b[0], y: +b[1] } : m - 2 === l && (n[2] = { x: +b[0], y: +b[1] }, n[3] = { x: +b[2], y: +b[3] }) : n[0] = { x: +b[m - 2], y: +b[m - 1] } : m - 4 === l ? n[3] = n[2] : l || (n[0] = { x: +b[l], y: +b[l + 1] }), k.curve(d * (-n[0].x + 6 * n[1].x + n[2].x) / 6 + e * n[2].x, d * (-n[0].y + 6 * n[1].y + n[2].y) / 6 + e * n[2].y, d * (n[1].x + 6 * n[2].x - n[3].x) / 6 + e * n[2].x, d * (n[1].y + 6 * n[2].y - n[3].y) / 6 + e * n[2].y, n[2].x, n[2].y, !1, g[(l + 2) / 2]) } return k } return c.Interpolation.none()([]) } }, c.Interpolation.monotoneCubic = function (a) { var b = { fillHoles: !1 }; return a = c.extend({}, b, a), function d(b, e) { var f = c.splitIntoSegments(b, e, { fillHoles: a.fillHoles, increasingX: !0 }); if (f.length) { if (f.length > 1) { var g = []; return f.forEach(function (a) { g.push(d(a.pathCoordinates, a.valueData)) }), c.Svg.Path.join(g) } if (b = f[0].pathCoordinates, e = f[0].valueData, b.length <= 4) return c.Interpolation.none()(b, e); var h, i, j = [], k = [], l = b.length / 2, m = [], n = [], o = [], p = []; for (h = 0; h < l; h++)j[h] = b[2 * h], k[h] = b[2 * h + 1]; for (h = 0; h < l - 1; h++)o[h] = k[h + 1] - k[h], p[h] = j[h + 1] - j[h], n[h] = o[h] / p[h]; for (m[0] = n[0], m[l - 1] = n[l - 2], h = 1; h < l - 1; h++)0 === n[h] || 0 === n[h - 1] || n[h - 1] > 0 != n[h] > 0 ? m[h] = 0 : (m[h] = 3 * (p[h - 1] + p[h]) / ((2 * p[h] + p[h - 1]) / n[h - 1] + (p[h] + 2 * p[h - 1]) / n[h]), isFinite(m[h]) || (m[h] = 0)); for (i = (new c.Svg.Path).move(j[0], k[0], !1, e[0]), h = 0; h < l - 1; h++)i.curve(j[h] + p[h] / 3, k[h] + m[h] * p[h] / 3, j[h + 1] - p[h] / 3, k[h + 1] - m[h + 1] * p[h] / 3, j[h + 1], k[h + 1], !1, e[h + 1]); return i } return c.Interpolation.none()([]) } }, c.Interpolation.step = function (a) { var b = { postpone: !0, fillHoles: !1 }; return a = c.extend({}, b, a), function (b, d) { for (var e, f, g, h = new c.Svg.Path, i = 0; i < b.length; i += 2) { var j = b[i], k = b[i + 1], l = d[i / 2]; void 0 !== l.value ? (void 0 === g ? h.move(j, k, !1, l) : (a.postpone ? h.line(j, f, !1, g) : h.line(e, k, !1, l), h.line(j, k, !1, l)), e = j, f = k, g = l) : a.fillHoles || (e = f = g = void 0) } return h } } }(window, document, a), function (a, b, c) { "use strict"; c.EventEmitter = function () { function a(a, b) { d[a] = d[a] || [], d[a].push(b) } function b(a, b) { d[a] && (b ? (d[a].splice(d[a].indexOf(b), 1), 0 === d[a].length && delete d[a]) : delete d[a]) } function c(a, b) { d[a] && d[a].forEach(function (a) { a(b) }), d["*"] && d["*"].forEach(function (c) { c(a, b) }) } var d = []; return { addEventHandler: a, removeEventHandler: b, emit: c } } }(window, document, a), function (a, b, c) { "use strict"; function d(a) { var b = []; if (a.length) for (var c = 0; c < a.length; c++)b.push(a[c]); return b } function e(a, b) { var d = b || this.prototype || c.Class, e = Object.create(d); c.Class.cloneDefinitions(e, a); var f = function () { var a, b = e.constructor || function () { }; return a = this === c ? Object.create(e) : this, b.apply(a, Array.prototype.slice.call(arguments, 0)), a }; return f.prototype = e, f["super"] = d, f.extend = this.extend, f } function f() { var a = d(arguments), b = a[0]; return a.splice(1, a.length - 1).forEach(function (a) { Object.getOwnPropertyNames(a).forEach(function (c) { delete b[c], Object.defineProperty(b, c, Object.getOwnPropertyDescriptor(a, c)) }) }), b } c.Class = { extend: e, cloneDefinitions: f } }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d) { return a && (this.data = a || {}, this.data.labels = this.data.labels || [], this.data.series = this.data.series || [], this.eventEmitter.emit("data", { type: "update", data: this.data })), b && (this.options = c.extend({}, d ? this.options : this.defaultOptions, b), this.initializeTimeoutId || (this.optionsProvider.removeMediaQueryListeners(), this.optionsProvider = c.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter))), this.initializeTimeoutId || this.createChart(this.optionsProvider.getCurrentOptions()), this } function e() { return this.initializeTimeoutId ? a.clearTimeout(this.initializeTimeoutId) : (a.removeEventListener("resize", this.resizeListener), this.optionsProvider.removeMediaQueryListeners()), this } function f(a, b) { return this.eventEmitter.addEventHandler(a, b), this } function g(a, b) { return this.eventEmitter.removeEventHandler(a, b), this } function h() { a.addEventListener("resize", this.resizeListener), this.optionsProvider = c.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter), this.eventEmitter.addEventHandler("optionsChanged", function () { this.update() }.bind(this)), this.options.plugins && this.options.plugins.forEach(function (a) { a instanceof Array ? a[0](this, a[1]) : a(this) }.bind(this)), this.eventEmitter.emit("data", { type: "initial", data: this.data }), this.createChart(this.optionsProvider.getCurrentOptions()), this.initializeTimeoutId = void 0 } function i(a, b, d, e, f) { this.container = c.querySelector(a), this.data = b || {}, this.data.labels = this.data.labels || [], this.data.series = this.data.series || [], this.defaultOptions = d, this.options = e, this.responsiveOptions = f, this.eventEmitter = c.EventEmitter(), this.supportsForeignObject = c.Svg.isSupported("Extensibility"), this.supportsAnimations = c.Svg.isSupported("AnimationEventsAttribute"), this.resizeListener = function () { this.update() }.bind(this), this.container && (this.container.__chartist__ && this.container.__chartist__.detach(), this.container.__chartist__ = this), this.initializeTimeoutId = setTimeout(h.bind(this), 0) } c.Base = c.Class.extend({ constructor: i, optionsProvider: void 0, container: void 0, svg: void 0, eventEmitter: void 0, createChart: function () { throw new Error("Base chart type can't be instantiated!") }, update: d, detach: e, on: f, off: g, version: c.version, supportsForeignObject: !1 }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, d, e, f, g) { a instanceof Element ? this._node = a : (this._node = b.createElementNS(c.namespaces.svg, a), "svg" === a && this.attr({ "xmlns:ct": c.namespaces.ct })), d && this.attr(d), e && this.addClass(e), f && (g && f._node.firstChild ? f._node.insertBefore(this._node, f._node.firstChild) : f._node.appendChild(this._node)) } function e(a, b) { return "string" == typeof a ? b ? this._node.getAttributeNS(b, a) : this._node.getAttribute(a) : (Object.keys(a).forEach(function (b) { if (void 0 !== a[b]) if (b.indexOf(":") !== -1) { var d = b.split(":"); this._node.setAttributeNS(c.namespaces[d[0]], b, a[b]) } else this._node.setAttribute(b, a[b]) }.bind(this)), this) } function f(a, b, d, e) { return new c.Svg(a, b, d, this, e) } function g() { return this._node.parentNode instanceof SVGElement ? new c.Svg(this._node.parentNode) : null } function h() { for (var a = this._node; "svg" !== a.nodeName;)a = a.parentNode; return new c.Svg(a) } function i(a) { var b = this._node.querySelector(a); return b ? new c.Svg(b) : null } function j(a) { var b = this._node.querySelectorAll(a); return b.length ? new c.Svg.List(b) : null } function k() { return this._node } function l(a, d, e, f) { if ("string" == typeof a) { var g = b.createElement("div"); g.innerHTML = a, a = g.firstChild } a.setAttribute("xmlns", c.namespaces.xmlns); var h = this.elem("foreignObject", d, e, f); return h._node.appendChild(a), h } function m(a) { return this._node.appendChild(b.createTextNode(a)), this } function n() { for (; this._node.firstChild;)this._node.removeChild(this._node.firstChild); return this } function o() { return this._node.parentNode.removeChild(this._node), this.parent() } function p(a) { return this._node.parentNode.replaceChild(a._node, this._node), a } function q(a, b) { return b && this._node.firstChild ? this._node.insertBefore(a._node, this._node.firstChild) : this._node.appendChild(a._node), this } function r() { return this._node.getAttribute("class") ? this._node.getAttribute("class").trim().split(/\s+/) : [] } function s(a) { return this._node.setAttribute("class", this.classes(this._node).concat(a.trim().split(/\s+/)).filter(function (a, b, c) { return c.indexOf(a) === b }).join(" ")), this } function t(a) { var b = a.trim().split(/\s+/); return this._node.setAttribute("class", this.classes(this._node).filter(function (a) { return b.indexOf(a) === -1 }).join(" ")), this } function u() { return this._node.setAttribute("class", ""), this } function v() { return this._node.getBoundingClientRect().height } function w() { return this._node.getBoundingClientRect().width } function x(a, b, d) { return void 0 === b && (b = !0), Object.keys(a).forEach(function (e) { function f(a, b) { var f, g, h, i = {}; a.easing && (h = a.easing instanceof Array ? a.easing : c.Svg.Easing[a.easing], delete a.easing), a.begin = c.ensureUnit(a.begin, "ms"), a.dur = c.ensureUnit(a.dur, "ms"), h && (a.calcMode = "spline", a.keySplines = h.join(" "), a.keyTimes = "0;1"), b && (a.fill = "freeze", i[e] = a.from, this.attr(i), g = c.quantity(a.begin || 0).value, a.begin = "indefinite"), f = this.elem("animate", c.extend({ attributeName: e }, a)), b && setTimeout(function () { try { f._node.beginElement() } catch (b) { i[e] = a.to, this.attr(i), f.remove() } }.bind(this), g), d && f._node.addEventListener("beginEvent", function () { d.emit("animationBegin", { element: this, animate: f._node, params: a }) }.bind(this)), f._node.addEventListener("endEvent", function () { d && d.emit("animationEnd", { element: this, animate: f._node, params: a }), b && (i[e] = a.to, this.attr(i), f.remove()) }.bind(this)) } a[e] instanceof Array ? a[e].forEach(function (a) { f.bind(this)(a, !1) }.bind(this)) : f.bind(this)(a[e], b) }.bind(this)), this } function y(a) { var b = this; this.svgElements = []; for (var d = 0; d < a.length; d++)this.svgElements.push(new c.Svg(a[d])); Object.keys(c.Svg.prototype).filter(function (a) { return ["constructor", "parent", "querySelector", "querySelectorAll", "replace", "append", "classes", "height", "width"].indexOf(a) === -1 }).forEach(function (a) { b[a] = function () { var d = Array.prototype.slice.call(arguments, 0); return b.svgElements.forEach(function (b) { c.Svg.prototype[a].apply(b, d) }), b } }) } c.Svg = c.Class.extend({ constructor: d, attr: e, elem: f, parent: g, root: h, querySelector: i, querySelectorAll: j, getNode: k, foreignObject: l, text: m, empty: n, remove: o, replace: p, append: q, classes: r, addClass: s, removeClass: t, removeAllClasses: u, height: v, width: w, animate: x }), c.Svg.isSupported = function (a) { return b.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#" + a, "1.1") }; var z = { easeInSine: [.47, 0, .745, .715], easeOutSine: [.39, .575, .565, 1], easeInOutSine: [.445, .05, .55, .95], easeInQuad: [.55, .085, .68, .53], easeOutQuad: [.25, .46, .45, .94], easeInOutQuad: [.455, .03, .515, .955], easeInCubic: [.55, .055, .675, .19], easeOutCubic: [.215, .61, .355, 1], easeInOutCubic: [.645, .045, .355, 1], easeInQuart: [.895, .03, .685, .22], easeOutQuart: [.165, .84, .44, 1], easeInOutQuart: [.77, 0, .175, 1], easeInQuint: [.755, .05, .855, .06], easeOutQuint: [.23, 1, .32, 1], easeInOutQuint: [.86, 0, .07, 1], easeInExpo: [.95, .05, .795, .035], easeOutExpo: [.19, 1, .22, 1], easeInOutExpo: [1, 0, 0, 1], easeInCirc: [.6, .04, .98, .335], easeOutCirc: [.075, .82, .165, 1], easeInOutCirc: [.785, .135, .15, .86], easeInBack: [.6, -.28, .735, .045], easeOutBack: [.175, .885, .32, 1.275], easeInOutBack: [.68, -.55, .265, 1.55] }; c.Svg.Easing = z, c.Svg.List = c.Class.extend({ constructor: y }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e, f, g) { var h = c.extend({ command: f ? a.toLowerCase() : a.toUpperCase() }, b, g ? { data: g } : {}); d.splice(e, 0, h) } function e(a, b) { a.forEach(function (c, d) { u[c.command.toLowerCase()].forEach(function (e, f) { b(c, e, d, f, a) }) }) } function f(a, b) { this.pathElements = [], this.pos = 0, this.close = a, this.options = c.extend({}, v, b) } function g(a) { return void 0 !== a ? (this.pos = Math.max(0, Math.min(this.pathElements.length, a)), this) : this.pos } function h(a) { return this.pathElements.splice(this.pos, a), this } function i(a, b, c, e) { return d("M", { x: +a, y: +b }, this.pathElements, this.pos++, c, e), this } function j(a, b, c, e) { return d("L", { x: +a, y: +b }, this.pathElements, this.pos++, c, e), this } function k(a, b, c, e, f, g, h, i) { return d("C", { x1: +a, y1: +b, x2: +c, y2: +e, x: +f, y: +g }, this.pathElements, this.pos++, h, i), this } function l(a, b, c, e, f, g, h, i, j) { return d("A", { rx: +a, ry: +b, xAr: +c, lAf: +e, sf: +f, x: +g, y: +h }, this.pathElements, this.pos++, i, j), this } function m(a) { var b = a.replace(/([A-Za-z])([0-9])/g, "$1 $2").replace(/([0-9])([A-Za-z])/g, "$1 $2").split(/[\s,]+/).reduce(function (a, b) { return b.match(/[A-Za-z]/) && a.push([]), a[a.length - 1].push(b), a }, []); "Z" === b[b.length - 1][0].toUpperCase() && b.pop(); var d = b.map(function (a) { var b = a.shift(), d = u[b.toLowerCase()]; return c.extend({ command: b }, d.reduce(function (b, c, d) { return b[c] = +a[d], b }, {})) }), e = [this.pos, 0]; return Array.prototype.push.apply(e, d), Array.prototype.splice.apply(this.pathElements, e), this.pos += d.length, this } function n() { var a = Math.pow(10, this.options.accuracy); return this.pathElements.reduce(function (b, c) { var d = u[c.command.toLowerCase()].map(function (b) { return this.options.accuracy ? Math.round(c[b] * a) / a : c[b] }.bind(this)); return b + c.command + d.join(",") }.bind(this), "") + (this.close ? "Z" : "") } function o(a, b) { return e(this.pathElements, function (c, d) { c[d] *= "x" === d[0] ? a : b }), this } function p(a, b) { return e(this.pathElements, function (c, d) { c[d] += "x" === d[0] ? a : b }), this } function q(a) { return e(this.pathElements, function (b, c, d, e, f) { var g = a(b, c, d, e, f); (g || 0 === g) && (b[c] = g) }), this } function r(a) { var b = new c.Svg.Path(a || this.close); return b.pos = this.pos, b.pathElements = this.pathElements.slice().map(function (a) { return c.extend({}, a) }), b.options = c.extend({}, this.options), b } function s(a) { var b = [new c.Svg.Path]; return this.pathElements.forEach(function (d) { d.command === a.toUpperCase() && 0 !== b[b.length - 1].pathElements.length && b.push(new c.Svg.Path), b[b.length - 1].pathElements.push(d) }), b } function t(a, b, d) { for (var e = new c.Svg.Path(b, d), f = 0; f < a.length; f++)for (var g = a[f], h = 0; h < g.pathElements.length; h++)e.pathElements.push(g.pathElements[h]); return e } var u = { m: ["x", "y"], l: ["x", "y"], c: ["x1", "y1", "x2", "y2", "x", "y"], a: ["rx", "ry", "xAr", "lAf", "sf", "x", "y"] }, v = { accuracy: 3 }; c.Svg.Path = c.Class.extend({ constructor: f, position: g, remove: h, move: i, line: j, curve: k, arc: l, scale: o, translate: p, transform: q, parse: m, stringify: n, clone: r, splitByCommand: s }), c.Svg.Path.elementDescriptions = u, c.Svg.Path.join = t }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, c, d) { this.units = a, this.counterUnits = a === f.x ? f.y : f.x, this.chartRect = b, this.axisLength = b[a.rectEnd] - b[a.rectStart], this.gridOffset = b[a.rectOffset], this.ticks = c, this.options = d } function e(a, b, d, e, f) { var g = e["axis" + this.units.pos.toUpperCase()], h = this.ticks.map(this.projectValue.bind(this)), i = this.ticks.map(g.labelInterpolationFnc); h.forEach(function (j, k) { var l, m = { x: 0, y: 0 }; l = h[k + 1] ? h[k + 1] - j : Math.max(this.axisLength - j, 30), c.isFalseyButZero(i[k]) && "" !== i[k] || ("x" === this.units.pos ? (j = this.chartRect.x1 + j, m.x = e.axisX.labelOffset.x, "start" === e.axisX.position ? m.y = this.chartRect.padding.top + e.axisX.labelOffset.y + (d ? 5 : 20) : m.y = this.chartRect.y1 + e.axisX.labelOffset.y + (d ? 5 : 20)) : (j = this.chartRect.y1 - j, m.y = e.axisY.labelOffset.y - (d ? l : 0), "start" === e.axisY.position ? m.x = d ? this.chartRect.padding.left + e.axisY.labelOffset.x : this.chartRect.x1 - 10 : m.x = this.chartRect.x2 + e.axisY.labelOffset.x + 10), g.showGrid && c.createGrid(j, k, this, this.gridOffset, this.chartRect[this.counterUnits.len](), a, [e.classNames.grid, e.classNames[this.units.dir]], f), g.showLabel && c.createLabel(j, l, k, i, this, g.offset, m, b, [e.classNames.label, e.classNames[this.units.dir], "start" === g.position ? e.classNames[g.position] : e.classNames.end], d, f)) }.bind(this)) } var f = { x: { pos: "x", len: "width", dir: "horizontal", rectStart: "x1", rectEnd: "x2", rectOffset: "y2" }, y: { pos: "y", len: "height", dir: "vertical", rectStart: "y2", rectEnd: "y1", rectOffset: "x1" } }; c.Axis = c.Class.extend({ constructor: d, createGridAndLabels: e, projectValue: function (a, b, c) { throw new Error("Base axis can't be instantiated!") } }), c.Axis.units = f }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { var f = e.highLow || c.getHighLow(b, e, a.pos); this.bounds = c.getBounds(d[a.rectEnd] - d[a.rectStart], f, e.scaleMinSpace || 20, e.onlyInteger), this.range = { min: this.bounds.min, max: this.bounds.max }, c.AutoScaleAxis["super"].constructor.call(this, a, d, this.bounds.values, e) } function e(a) { return this.axisLength * (+c.getMultiValue(a, this.units.pos) - this.bounds.min) / this.bounds.range } c.AutoScaleAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { var f = e.highLow || c.getHighLow(b, e, a.pos); this.divisor = e.divisor || 1, this.ticks = e.ticks || c.times(this.divisor).map(function (a, b) { return f.low + (f.high - f.low) / this.divisor * b }.bind(this)), this.ticks.sort(function (a, b) { return a - b }), this.range = { min: f.low, max: f.high }, c.FixedScaleAxis["super"].constructor.call(this, a, d, this.ticks, e), this.stepLength = this.axisLength / this.divisor } function e(a) { return this.axisLength * (+c.getMultiValue(a, this.units.pos) - this.range.min) / (this.range.max - this.range.min) } c.FixedScaleAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { c.StepAxis["super"].constructor.call(this, a, d, e.ticks, e); var f = Math.max(1, e.ticks.length - (e.stretch ? 1 : 0)); this.stepLength = this.axisLength / f } function e(a, b) { return this.stepLength * b } c.StepAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a) { var b = c.normalizeData(this.data, a.reverseData, !0); this.svg = c.createSvg(this.container, a.width, a.height, a.classNames.chart); var d, e, g = this.svg.elem("g").addClass(a.classNames.gridGroup), h = this.svg.elem("g"), i = this.svg.elem("g").addClass(a.classNames.labelGroup), j = c.createChartRect(this.svg, a, f.padding); d = void 0 === a.axisX.type ? new c.StepAxis(c.Axis.units.x, b.normalized.series, j, c.extend({}, a.axisX, { ticks: b.normalized.labels, stretch: a.fullWidth })) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, j, a.axisX), e = void 0 === a.axisY.type ? new c.AutoScaleAxis(c.Axis.units.y, b.normalized.series, j, c.extend({}, a.axisY, { high: c.isNumeric(a.high) ? a.high : a.axisY.high, low: c.isNumeric(a.low) ? a.low : a.axisY.low })) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, j, a.axisY), d.createGridAndLabels(g, i, this.supportsForeignObject, a, this.eventEmitter), e.createGridAndLabels(g, i, this.supportsForeignObject, a, this.eventEmitter), a.showGridBackground && c.createGridBackground(g, j, a.classNames.gridBackground, this.eventEmitter), b.raw.series.forEach(function (f, g) { var i = h.elem("g"); i.attr({ "ct:series-name": f.name, "ct:meta": c.serialize(f.meta) }), i.addClass([a.classNames.series, f.className || a.classNames.series + "-" + c.alphaNumerate(g)].join(" ")); var k = [], l = []; b.normalized.series[g].forEach(function (a, h) { var i = { x: j.x1 + d.projectValue(a, h, b.normalized.series[g]), y: j.y1 - e.projectValue(a, h, b.normalized.series[g]) }; k.push(i.x, i.y), l.push({ value: a, valueIndex: h, meta: c.getMetaData(f, h) }) }.bind(this)); var m = { lineSmooth: c.getSeriesOption(f, a, "lineSmooth"), showPoint: c.getSeriesOption(f, a, "showPoint"), showLine: c.getSeriesOption(f, a, "showLine"), showArea: c.getSeriesOption(f, a, "showArea"), areaBase: c.getSeriesOption(f, a, "areaBase") }, n = "function" == typeof m.lineSmooth ? m.lineSmooth : m.lineSmooth ? c.Interpolation.monotoneCubic() : c.Interpolation.none(), o = n(k, l); if (m.showPoint && o.pathElements.forEach(function (b) { var h = i.elem("line", { x1: b.x, y1: b.y, x2: b.x + .01, y2: b.y }, a.classNames.point).attr({ "ct:value": [b.data.value.x, b.data.value.y].filter(c.isNumeric).join(","), "ct:meta": c.serialize(b.data.meta) }); this.eventEmitter.emit("draw", { type: "point", value: b.data.value, index: b.data.valueIndex, meta: b.data.meta, series: f, seriesIndex: g, axisX: d, axisY: e, group: i, element: h, x: b.x, y: b.y }) }.bind(this)), m.showLine) { var p = i.elem("path", { d: o.stringify() }, a.classNames.line, !0); this.eventEmitter.emit("draw", { type: "line", values: b.normalized.series[g], path: o.clone(), chartRect: j, index: g, series: f, seriesIndex: g, seriesMeta: f.meta, axisX: d, axisY: e, group: i, element: p }) } if (m.showArea && e.range) { var q = Math.max(Math.min(m.areaBase, e.range.max), e.range.min), r = j.y1 - e.projectValue(q); o.splitByCommand("M").filter(function (a) { return a.pathElements.length > 1 }).map(function (a) { var b = a.pathElements[0], c = a.pathElements[a.pathElements.length - 1]; return a.clone(!0).position(0).remove(1).move(b.x, r).line(b.x, b.y).position(a.pathElements.length + 1).line(c.x, r) }).forEach(function (c) { var h = i.elem("path", { d: c.stringify() }, a.classNames.area, !0); this.eventEmitter.emit("draw", { type: "area", values: b.normalized.series[g], path: c.clone(), series: f, seriesIndex: g, axisX: d, axisY: e, chartRect: j, index: g, group: i, element: h }) }.bind(this)) } }.bind(this)), this.eventEmitter.emit("created", { bounds: e.bounds, chartRect: j, axisX: d, axisY: e, svg: this.svg, options: a }) } function e(a, b, d, e) { c.Line["super"].constructor.call(this, a, b, f, c.extend({}, f, d), e) } var f = { axisX: { offset: 30, position: "end", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, type: void 0 }, axisY: { offset: 40, position: "start", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, type: void 0, scaleMinSpace: 20, onlyInteger: !1 }, width: void 0, height: void 0, showLine: !0, showPoint: !0, showArea: !1, areaBase: 0, lineSmooth: !0, showGridBackground: !1, low: void 0, high: void 0, chartPadding: { top: 15, right: 15, bottom: 5, left: 10 }, fullWidth: !1, reverseData: !1, classNames: { chart: "ct-chart-line", label: "ct-label", labelGroup: "ct-labels", series: "ct-series", line: "ct-line", point: "ct-point", area: "ct-area", grid: "ct-grid", gridGroup: "ct-grids", gridBackground: "ct-grid-background", vertical: "ct-vertical", horizontal: "ct-horizontal", start: "ct-start", end: "ct-end" } }; c.Line = c.Base.extend({ constructor: e, createChart: d }) }(window, document, a), function (a, b, c) { + "use strict"; function d(a) { + var b, d; a.distributeSeries ? (b = c.normalizeData(this.data, a.reverseData, a.horizontalBars ? "x" : "y"), b.normalized.series = b.normalized.series.map(function (a) { return [a] })) : b = c.normalizeData(this.data, a.reverseData, a.horizontalBars ? "x" : "y"), this.svg = c.createSvg(this.container, a.width, a.height, a.classNames.chart + (a.horizontalBars ? " " + a.classNames.horizontalBars : "")); var e = this.svg.elem("g").addClass(a.classNames.gridGroup), g = this.svg.elem("g"), h = this.svg.elem("g").addClass(a.classNames.labelGroup); if (a.stackBars && 0 !== b.normalized.series.length) { + var i = c.serialMap(b.normalized.series, function () { + return Array.prototype.slice.call(arguments).map(function (a) { return a }).reduce(function (a, b) { return { x: a.x + (b && b.x) || 0, y: a.y + (b && b.y) || 0 } }, { x: 0, y: 0 }) + }); d = c.getHighLow([i], a, a.horizontalBars ? "x" : "y") + } else d = c.getHighLow(b.normalized.series, a, a.horizontalBars ? "x" : "y"); d.high = +a.high || (0 === a.high ? 0 : d.high), d.low = +a.low || (0 === a.low ? 0 : d.low); var j, k, l, m, n, o = c.createChartRect(this.svg, a, f.padding); k = a.distributeSeries && a.stackBars ? b.normalized.labels.slice(0, 1) : b.normalized.labels, a.horizontalBars ? (j = m = void 0 === a.axisX.type ? new c.AutoScaleAxis(c.Axis.units.x, b.normalized.series, o, c.extend({}, a.axisX, { highLow: d, referenceValue: 0 })) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, o, c.extend({}, a.axisX, { highLow: d, referenceValue: 0 })), l = n = void 0 === a.axisY.type ? new c.StepAxis(c.Axis.units.y, b.normalized.series, o, { ticks: k }) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, o, a.axisY)) : (l = m = void 0 === a.axisX.type ? new c.StepAxis(c.Axis.units.x, b.normalized.series, o, { ticks: k }) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, o, a.axisX), j = n = void 0 === a.axisY.type ? new c.AutoScaleAxis(c.Axis.units.y, b.normalized.series, o, c.extend({}, a.axisY, { highLow: d, referenceValue: 0 })) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, o, c.extend({}, a.axisY, { highLow: d, referenceValue: 0 }))); var p = a.horizontalBars ? o.x1 + j.projectValue(0) : o.y1 - j.projectValue(0), q = []; l.createGridAndLabels(e, h, this.supportsForeignObject, a, this.eventEmitter), j.createGridAndLabels(e, h, this.supportsForeignObject, a, this.eventEmitter), a.showGridBackground && c.createGridBackground(e, o, a.classNames.gridBackground, this.eventEmitter), b.raw.series.forEach(function (d, e) { var f, h, i = e - (b.raw.series.length - 1) / 2; f = a.distributeSeries && !a.stackBars ? l.axisLength / b.normalized.series.length / 2 : a.distributeSeries && a.stackBars ? l.axisLength / 2 : l.axisLength / b.normalized.series[e].length / 2, h = g.elem("g"), h.attr({ "ct:series-name": d.name, "ct:meta": c.serialize(d.meta) }), h.addClass([a.classNames.series, d.className || a.classNames.series + "-" + c.alphaNumerate(e)].join(" ")), b.normalized.series[e].forEach(function (g, k) { var r, s, t, u; if (u = a.distributeSeries && !a.stackBars ? e : a.distributeSeries && a.stackBars ? 0 : k, r = a.horizontalBars ? { x: o.x1 + j.projectValue(g && g.x ? g.x : 0, k, b.normalized.series[e]), y: o.y1 - l.projectValue(g && g.y ? g.y : 0, u, b.normalized.series[e]) } : { x: o.x1 + l.projectValue(g && g.x ? g.x : 0, u, b.normalized.series[e]), y: o.y1 - j.projectValue(g && g.y ? g.y : 0, k, b.normalized.series[e]) }, l instanceof c.StepAxis && (l.options.stretch || (r[l.units.pos] += f * (a.horizontalBars ? -1 : 1)), r[l.units.pos] += a.stackBars || a.distributeSeries ? 0 : i * a.seriesBarDistance * (a.horizontalBars ? -1 : 1)), t = q[k] || p, q[k] = t - (p - r[l.counterUnits.pos]), void 0 !== g) { var v = {}; v[l.units.pos + "1"] = r[l.units.pos], v[l.units.pos + "2"] = r[l.units.pos], !a.stackBars || "accumulate" !== a.stackMode && a.stackMode ? (v[l.counterUnits.pos + "1"] = p, v[l.counterUnits.pos + "2"] = r[l.counterUnits.pos]) : (v[l.counterUnits.pos + "1"] = t, v[l.counterUnits.pos + "2"] = q[k]), v.x1 = Math.min(Math.max(v.x1, o.x1), o.x2), v.x2 = Math.min(Math.max(v.x2, o.x1), o.x2), v.y1 = Math.min(Math.max(v.y1, o.y2), o.y1), v.y2 = Math.min(Math.max(v.y2, o.y2), o.y1); var w = c.getMetaData(d, k); s = h.elem("line", v, a.classNames.bar).attr({ "ct:value": [g.x, g.y].filter(c.isNumeric).join(","), "ct:meta": c.serialize(w) }), this.eventEmitter.emit("draw", c.extend({ type: "bar", value: g, index: k, meta: w, series: d, seriesIndex: e, axisX: m, axisY: n, chartRect: o, group: h, element: s }, v)) } }.bind(this)) }.bind(this)), this.eventEmitter.emit("created", { bounds: j.bounds, chartRect: o, axisX: m, axisY: n, svg: this.svg, options: a }) + } function e(a, b, d, e) { c.Bar["super"].constructor.call(this, a, b, f, c.extend({}, f, d), e) } var f = { axisX: { offset: 30, position: "end", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, scaleMinSpace: 30, onlyInteger: !1 }, axisY: { offset: 40, position: "start", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, scaleMinSpace: 20, onlyInteger: !1 }, width: void 0, height: void 0, high: void 0, low: void 0, referenceValue: 0, chartPadding: { top: 15, right: 15, bottom: 5, left: 10 }, seriesBarDistance: 15, stackBars: !1, stackMode: "accumulate", horizontalBars: !1, distributeSeries: !1, reverseData: !1, showGridBackground: !1, classNames: { chart: "ct-chart-bar", horizontalBars: "ct-horizontal-bars", label: "ct-label", labelGroup: "ct-labels", series: "ct-series", bar: "ct-bar", grid: "ct-grid", gridGroup: "ct-grids", gridBackground: "ct-grid-background", vertical: "ct-vertical", horizontal: "ct-horizontal", start: "ct-start", end: "ct-end" } }; c.Bar = c.Base.extend({ constructor: e, createChart: d }) + }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, c) { var d = b.x > a.x; return d && "explode" === c || !d && "implode" === c ? "start" : d && "implode" === c || !d && "explode" === c ? "end" : "middle" } function e(a) { var b, e, f, h, i, j = c.normalizeData(this.data), k = [], l = a.startAngle; this.svg = c.createSvg(this.container, a.width, a.height, a.donut ? a.classNames.chartDonut : a.classNames.chartPie), e = c.createChartRect(this.svg, a, g.padding), f = Math.min(e.width() / 2, e.height() / 2), i = a.total || j.normalized.series.reduce(function (a, b) { return a + b }, 0); var m = c.quantity(a.donutWidth); "%" === m.unit && (m.value *= f / 100), f -= a.donut && !a.donutSolid ? m.value / 2 : 0, h = "outside" === a.labelPosition || a.donut && !a.donutSolid ? f : "center" === a.labelPosition ? 0 : a.donutSolid ? f - m.value / 2 : f / 2, h += a.labelOffset; var n = { x: e.x1 + e.width() / 2, y: e.y2 + e.height() / 2 }, o = 1 === j.raw.series.filter(function (a) { return a.hasOwnProperty("value") ? 0 !== a.value : 0 !== a }).length; j.raw.series.forEach(function (a, b) { k[b] = this.svg.elem("g", null, null) }.bind(this)), a.showLabel && (b = this.svg.elem("g", null, null)), j.raw.series.forEach(function (e, g) { if (0 !== j.normalized.series[g] || !a.ignoreEmptyValues) { k[g].attr({ "ct:series-name": e.name }), k[g].addClass([a.classNames.series, e.className || a.classNames.series + "-" + c.alphaNumerate(g)].join(" ")); var p = i > 0 ? l + j.normalized.series[g] / i * 360 : 0, q = Math.max(0, l - (0 === g || o ? 0 : .2)); p - q >= 359.99 && (p = q + 359.99); var r, s, t, u = c.polarToCartesian(n.x, n.y, f, q), v = c.polarToCartesian(n.x, n.y, f, p), w = new c.Svg.Path(!a.donut || a.donutSolid).move(v.x, v.y).arc(f, f, 0, p - l > 180, 0, u.x, u.y); a.donut ? a.donutSolid && (t = f - m.value, r = c.polarToCartesian(n.x, n.y, t, l - (0 === g || o ? 0 : .2)), s = c.polarToCartesian(n.x, n.y, t, p), w.line(r.x, r.y), w.arc(t, t, 0, p - l > 180, 1, s.x, s.y)) : w.line(n.x, n.y); var x = a.classNames.slicePie; a.donut && (x = a.classNames.sliceDonut, a.donutSolid && (x = a.classNames.sliceDonutSolid)); var y = k[g].elem("path", { d: w.stringify() }, x); if (y.attr({ "ct:value": j.normalized.series[g], "ct:meta": c.serialize(e.meta) }), a.donut && !a.donutSolid && (y._node.style.strokeWidth = m.value + "px"), this.eventEmitter.emit("draw", { type: "slice", value: j.normalized.series[g], totalDataSum: i, index: g, meta: e.meta, series: e, group: k[g], element: y, path: w.clone(), center: n, radius: f, startAngle: l, endAngle: p }), a.showLabel) { var z; z = 1 === j.raw.series.length ? { x: n.x, y: n.y } : c.polarToCartesian(n.x, n.y, h, l + (p - l) / 2); var A; A = j.normalized.labels && !c.isFalseyButZero(j.normalized.labels[g]) ? j.normalized.labels[g] : j.normalized.series[g]; var B = a.labelInterpolationFnc(A, g); if (B || 0 === B) { var C = b.elem("text", { dx: z.x, dy: z.y, "text-anchor": d(n, z, a.labelDirection) }, a.classNames.label).text("" + B); this.eventEmitter.emit("draw", { type: "label", index: g, group: b, element: C, text: "" + B, x: z.x, y: z.y }) } } l = p } }.bind(this)), this.eventEmitter.emit("created", { chartRect: e, svg: this.svg, options: a }) } function f(a, b, d, e) { c.Pie["super"].constructor.call(this, a, b, g, c.extend({}, g, d), e) } var g = { width: void 0, height: void 0, chartPadding: 5, classNames: { chartPie: "ct-chart-pie", chartDonut: "ct-chart-donut", series: "ct-series", slicePie: "ct-slice-pie", sliceDonut: "ct-slice-donut", sliceDonutSolid: "ct-slice-donut-solid", label: "ct-label" }, startAngle: 0, total: void 0, donut: !1, donutSolid: !1, donutWidth: 60, showLabel: !0, labelOffset: 0, labelPosition: "inside", labelInterpolationFnc: c.noop, labelDirection: "neutral", reverseData: !1, ignoreEmptyValues: !1 }; c.Pie = c.Base.extend({ constructor: f, createChart: e, determineAnchorPosition: d }) }(window, document, a), a +}); + +var i, l, selectedLine = null; + +/* Navigate to hash without browser history entry */ +var navigateToHash = function () { + if (window.history !== undefined && window.history.replaceState !== undefined) { + window.history.replaceState(undefined, undefined, this.getAttribute("href")); + } +}; + +var hashLinks = document.getElementsByClassName('navigatetohash'); +for (i = 0, l = hashLinks.length; i < l; i++) { + hashLinks[i].addEventListener('click', navigateToHash); +} + +/* Switch test method */ +var switchTestMethod = function () { + var method = this.getAttribute("value"); + console.log("Selected test method: " + method); + + var lines, i, l, coverageData, lineAnalysis, cells; + + lines = document.querySelectorAll('.lineAnalysis tr'); + + for (i = 1, l = lines.length; i < l; i++) { + coverageData = JSON.parse(lines[i].getAttribute('data-coverage').replace(/'/g, '"')); + lineAnalysis = coverageData[method]; + cells = lines[i].querySelectorAll('td'); + if (lineAnalysis === undefined) { + lineAnalysis = coverageData.AllTestMethods; + if (lineAnalysis.LVS !== 'gray') { + cells[0].setAttribute('class', 'red'); + cells[1].innerText = cells[1].textContent = '0'; + cells[4].setAttribute('class', 'lightred'); + } + } else { + cells[0].setAttribute('class', lineAnalysis.LVS); + cells[1].innerText = cells[1].textContent = lineAnalysis.VC; + cells[4].setAttribute('class', 'light' + lineAnalysis.LVS); + } + } +}; + +var testMethods = document.getElementsByClassName('switchtestmethod'); +for (i = 0, l = testMethods.length; i < l; i++) { + testMethods[i].addEventListener('change', switchTestMethod); +} + +/* Highlight test method by line */ +var toggleLine = function () { + if (selectedLine === this) { + selectedLine = null; + } else { + selectedLine = null; + unhighlightTestMethods(); + highlightTestMethods.call(this); + selectedLine = this; + } + +}; +var highlightTestMethods = function () { + if (selectedLine !== null) { + return; + } + + var lineAnalysis; + var coverageData = JSON.parse(this.getAttribute('data-coverage').replace(/'/g, '"')); + var testMethods = document.getElementsByClassName('testmethod'); + + for (i = 0, l = testMethods.length; i < l; i++) { + lineAnalysis = coverageData[testMethods[i].id]; + if (lineAnalysis === undefined) { + testMethods[i].className = testMethods[i].className.replace(/\s*light.+/g, ""); + } else { + testMethods[i].className += ' light' + lineAnalysis.LVS; + } + } +}; +var unhighlightTestMethods = function () { + if (selectedLine !== null) { + return; + } + + var testMethods = document.getElementsByClassName('testmethod'); + for (i = 0, l = testMethods.length; i < l; i++) { + testMethods[i].className = testMethods[i].className.replace(/\s*light.+/g, ""); + } +}; +var coverableLines = document.getElementsByClassName('coverableline'); +for (i = 0, l = coverableLines.length; i < l; i++) { + coverableLines[i].addEventListener('click', toggleLine); + coverableLines[i].addEventListener('mouseenter', highlightTestMethods); + coverableLines[i].addEventListener('mouseleave', unhighlightTestMethods); +} + +/* History charts */ +var renderChart = function (chart) { + // Remove current children (e.g. PNG placeholder) + while (chart.firstChild) { + chart.firstChild.remove(); + } + + var chartData = window[chart.getAttribute('data-data')]; + var options = { + axisY: { + type: undefined, + onlyInteger: true + }, + lineSmooth: false, + low: 0, + high: 100, + scaleMinSpace: 20, + onlyInteger: true, + fullWidth: true + }; + var lineChart = new Chartist.Line(chart, { + labels: [], + series: chartData.series + }, options); + + /* Zoom */ + var zoomButtonDiv = document.createElement("div"); + zoomButtonDiv.className = "toggleZoom"; + var zoomButtonLink = document.createElement("a"); + zoomButtonLink.setAttribute("href", ""); + var zoomButtonText = document.createElement("i"); + zoomButtonText.className = "icon-search-plus"; + + zoomButtonLink.appendChild(zoomButtonText); + zoomButtonDiv.appendChild(zoomButtonLink); + + chart.appendChild(zoomButtonDiv); + + zoomButtonDiv.addEventListener('click', function (event) { + event.preventDefault(); + + if (options.axisY.type === undefined) { + options.axisY.type = Chartist.AutoScaleAxis; + zoomButtonText.className = "icon-search-minus"; + } else { + options.axisY.type = undefined; + zoomButtonText.className = "icon-search-plus"; + } + + lineChart.update(null, options); + }); + + var tooltip = document.createElement("div"); + tooltip.className = "tooltip"; + + chart.appendChild(tooltip); + + /* Tooltips */ + var showToolTip = function () { + var point = this; + var index = [].slice.call(chart.getElementsByClassName('ct-point')).indexOf(point); + + tooltip.innerHTML = chartData.tooltips[index % chartData.tooltips.length]; + tooltip.style.display = 'block'; + }; + + var moveToolTip = function (event) { + var box = chart.getBoundingClientRect(); + var left = event.pageX - box.left - window.pageXOffset; + var top = event.pageY - box.top - window.pageYOffset; + + tooltip.style.left = left - tooltip.offsetWidth / 2 - 5 + 'px'; + tooltip.style.top = top - tooltip.offsetHeight - 40 + 'px'; + }; + + var hideToolTip = function () { + tooltip.style.display = 'none'; + }; + + chart.addEventListener('mousemove', moveToolTip); + + lineChart.on('created', function () { + var chartPoints = chart.getElementsByClassName('ct-point'); + for (i = 0, l = chartPoints.length; i < l; i++) { + chartPoints[i].addEventListener('mousemove', showToolTip); + chartPoints[i].addEventListener('mouseout', hideToolTip); + } + }); +}; + +var charts = document.getElementsByClassName('historychart'); +for (i = 0, l = charts.length; i < l; i++) { + renderChart(charts[i]); +} + +var assemblies = [ + { + "name": "ClassLibrary1", + "classes": [ + { "name": "ClassLibrary1.Calculation", "rp": "ClassLibrary1_Calculation.htm", "cl": 3, "ucl": 6, "cal": 9, "tl": 18, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [33.3], "bch": [], "hc": [{ "et": "2021/3/26 - 下午 04:34:47", "cl": 3, "ucl": 6, "cal": 9, "tl": 18, "lcq": 33.3, "cb": 0, "tb": 0, "bcq": 0 }] }, + ]}, + { + "name": "UnitTestProject1", + "classes": [ + { "name": "UnitTestProject1.UnitTest1", "rp": "UnitTestProject1_UnitTest1.htm", "cl": 9, "ucl": 1, "cal": 10, "tl": 26, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [90], "bch": [], "hc": [{ "et": "2021/3/26 - 下午 04:34:47", "cl": 9, "ucl": 1, "cal": 10, "tl": 26, "lcq": 90, "cb": 0, "tb": 0, "bcq": 0 }] }, + ]}, + { + "name": "UnitTestProject2", + "classes": [ + { "name": "UnitTestProject2.UnitTest1", "rp": "UnitTestProject2_UnitTest1.htm", "cl": 3, "ucl": 0, "cal": 3, "tl": 15, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [100], "bch": [], "hc": [{ "et": "2021/3/26 - 下午 04:34:47", "cl": 3, "ucl": 0, "cal": 3, "tl": 15, "lcq": 100, "cb": 0, "tb": 0, "bcq": 0 }] }, + ]}, +]; + +var historicCoverageExecutionTimes = []; + +var riskHotspotMetrics = [ +]; + +var riskHotspots = [ +]; + +var branchCoverageAvailable = true; + + +var translations = { +'top': 'Top:', +'all': 'All', +'assembly': 'Assembly', +'class': 'Class', +'method': 'Method', +'lineCoverage': 'LineCoverage', +'noGrouping': 'No grouping', +'byAssembly': 'By assembly', +'byNamespace': 'By namespace, Level:', +'all': 'All', +'collapseAll': 'Collapse all', +'expandAll': 'Expand all', +'grouping': 'Grouping:', +'filter': 'Filter:', +'name': 'Name', +'covered': 'Covered', +'uncovered': 'Uncovered', +'coverable': 'Coverable', +'total': 'Total', +'coverage': 'Line coverage', +'branchCoverage': 'Branch coverage', +'history': 'Coverage History', +'compareHistory': 'Compare with:', +'date': 'Date', +'allChanges': 'All changes', +'lineCoverageIncreaseOnly': 'Line coverage: Increase only', +'lineCoverageDecreaseOnly': 'Line coverage: Decrease only', +'branchCoverageIncreaseOnly': 'Branch coverage: Increase only', +'branchCoverageDecreaseOnly': 'Branch coverage: Decrease only' +}; + + +!function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c",this._properties=n&&n.properties||{},this._zoneDelegate=new c(this,this._parent&&this._parent._zoneDelegate,n)}return n.assertZonePatched=function(){if(t.Promise!==T.ZoneAwarePromise)throw new Error("Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten.\nMost likely cause is that a Promise polyfill has been loaded after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. If you must load one, do so before loading zone.js.)")},Object.defineProperty(n,"root",{get:function(){for(var t=n.current;t.parent;)t=t.parent;return t},enumerable:!0,configurable:!0}),Object.defineProperty(n,"current",{get:function(){return j.zone},enumerable:!0,configurable:!0}),Object.defineProperty(n,"currentTask",{get:function(){return M},enumerable:!0,configurable:!0}),n.__load_patch=function(o,i){if(T.hasOwnProperty(o))throw Error("Already loaded patch: "+o);if(!t["__Zone_disable_"+o]){var u="Zone:"+o;r(u),T[o]=i(t,n,O),e(u,u)}},Object.defineProperty(n.prototype,"parent",{get:function(){return this._parent},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"name",{get:function(){return this._name},enumerable:!0,configurable:!0}),n.prototype.get=function(t){var n=this.getZoneWith(t);if(n)return n._properties[t]},n.prototype.getZoneWith=function(t){for(var n=this;n;){if(n._properties.hasOwnProperty(t))return n;n=n._parent}return null},n.prototype.fork=function(t){if(!t)throw new Error("ZoneSpec required!");return this._zoneDelegate.fork(this,t)},n.prototype.wrap=function(t,n){if("function"!=typeof t)throw new Error("Expecting function got: "+t);var r=this._zoneDelegate.intercept(this,t,n),e=this;return function(){return e.runGuarded(r,this,arguments,n)}},n.prototype.run=function(t,n,r,e){void 0===n&&(n=void 0),void 0===r&&(r=null),void 0===e&&(e=null),j={parent:j,zone:this};try{return this._zoneDelegate.invoke(this,t,n,r,e)}finally{j=j.parent}},n.prototype.runGuarded=function(t,n,r,e){void 0===n&&(n=null),void 0===r&&(r=null),void 0===e&&(e=null),j={parent:j,zone:this};try{try{return this._zoneDelegate.invoke(this,t,n,r,e)}catch(o){if(this._zoneDelegate.handleError(this,o))throw o}}finally{j=j.parent}},n.prototype.runTask=function(t,n,r){if(t.zone!=this)throw new Error("A task can only be run in the zone of creation! (Creation: "+(t.zone||d).name+"; Execution: "+this.name+")");if(t.state!==g||t.type!==F){var e=t.state!=_;e&&t._transitionTo(_,m),t.runCount++;var o=M;M=t,j={parent:j,zone:this};try{t.type==S&&t.data&&!t.data.isPeriodic&&(t.cancelFn=null);try{return this._zoneDelegate.invokeTask(this,t,n,r)}catch(i){if(this._zoneDelegate.handleError(this,i))throw i}}finally{t.state!==g&&t.state!==w&&(t.type==F||t.data&&t.data.isPeriodic?e&&t._transitionTo(m,_):(t.runCount=0,this._updateTaskCount(t,-1),e&&t._transitionTo(g,_,g))),j=j.parent,M=o}}},n.prototype.scheduleTask=function(t){if(t.zone&&t.zone!==this)for(var n=this;n;){if(n===t.zone)throw Error("can not reschedule task to "+this.name+" which is descendants of the original zone "+t.zone.name);n=n.parent}t._transitionTo(b,g);var r=[];t._zoneDelegates=r,t._zone=this;try{t=this._zoneDelegate.scheduleTask(this,t)}catch(e){throw t._transitionTo(w,b,g),this._zoneDelegate.handleError(this,e),e}return t._zoneDelegates===r&&this._updateTaskCount(t,1),t.state==b&&t._transitionTo(m,b),t},n.prototype.scheduleMicroTask=function(t,n,r,e){return this.scheduleTask(new a(x,t,n,r,e,null))},n.prototype.scheduleMacroTask=function(t,n,r,e,o){return this.scheduleTask(new a(S,t,n,r,e,o))},n.prototype.scheduleEventTask=function(t,n,r,e,o){return this.scheduleTask(new a(F,t,n,r,e,o))},n.prototype.cancelTask=function(t){if(t.zone!=this)throw new Error("A task can only be cancelled in the zone of creation! (Creation: "+(t.zone||d).name+"; Execution: "+this.name+")");t._transitionTo(k,m,_);try{this._zoneDelegate.cancelTask(this,t)}catch(n){throw t._transitionTo(w,k),this._zoneDelegate.handleError(this,n),n}return this._updateTaskCount(t,-1),t._transitionTo(g,k),t.runCount=0,t},n.prototype._updateTaskCount=function(t,n){var r=t._zoneDelegates;-1==n&&(t._zoneDelegates=null);for(var e=0;e0,macroTask:r.macroTask>0,eventTask:r.eventTask>0,change:t})},t}(),a=function(){function n(r,e,o,i,u,c){this._zone=null,this.runCount=0,this._zoneDelegates=null,this._state="notScheduled",this.type=r,this.source=e,this.data=i,this.scheduleFn=u,this.cancelFn=c,this.callback=o;var a=this;this.invoke=r===F&&i&&i.useG?n.invokeTask:function(){return n.invokeTask.call(t,a,this,arguments)}}return n.invokeTask=function(t,n,r){t||(t=this),K++;try{return t.runCount++,t.zone.runTask(t,n,r)}finally{1==K&&y(),K--}},Object.defineProperty(n.prototype,"zone",{get:function(){return this._zone},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"state",{get:function(){return this._state},enumerable:!0,configurable:!0}),n.prototype.cancelScheduleRequest=function(){this._transitionTo(g,b)},n.prototype._transitionTo=function(t,n,r){if(this._state!==n&&this._state!==r)throw new Error(this.type+" '"+this.source+"': can not transition to '"+t+"', expecting state '"+n+"'"+(r?" or '"+r+"'":"")+", was '"+this._state+"'.");this._state=t,t==g&&(this._zoneDelegates=null)},n.prototype.toString=function(){return this.data&&void 0!==this.data.handleId?this.data.handleId:Object.prototype.toString.call(this)},n.prototype.toJSON=function(){return{type:this.type,state:this.state,source:this.source,zone:this.zone.name,runCount:this.runCount}},n}(),f=X("setTimeout"),s=X("Promise"),l=X("then"),h=[],p=!1;function v(n){0===K&&0===h.length&&(o||t[s]&&(o=t[s].resolve(0)),o?o[l](y):t[f](y,0)),n&&h.push(n)}function y(){if(!p){for(p=!0;h.length;){var t=h;h=[];for(var n=0;n=0;r--)"function"==typeof t[r]&&(t[r]=h(t[r],n+"_"+r));return t}function k(t){return!t||!1!==t.writable&&!("function"==typeof t.get&&void 0===t.set)}var w="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope,x=!("nw"in g)&&void 0!==g.process&&"[object process]"==={}.toString.call(g.process),S=!x&&!w&&!(!y||!d.HTMLElement),F=void 0!==g.process&&"[object process]"==={}.toString.call(g.process)&&!w&&!(!y||!d.HTMLElement),T={},O=function(t){if(t=t||g.event){var n=T[t.type];n||(n=T[t.type]=v("ON_PROPERTY"+t.type));var r=(this||t.target||g)[n],e=r&&r.apply(this,arguments);return null==e||e||t.preventDefault(),e}};function j(r,e,o){var i=t(r,e);if(!i&&o&&t(o,e)&&(i={enumerable:!0,configurable:!0}),i&&i.configurable){delete i.writable,delete i.value;var u=i.get,c=i.set,a=e.substr(2),f=T[a];f||(f=T[a]=v("ON_PROPERTY"+a)),i.set=function(t){var n=this;n||r!==g||(n=g),n&&(n[f]&&n.removeEventListener(a,O),c&&c.apply(n,m),"function"==typeof t?(n[f]=t,n.addEventListener(a,O,!1)):n[f]=null)},i.get=function(){var t=this;if(t||r!==g||(t=g),!t)return null;var n=t[f];if(n)return n;if(u){var o=u&&u.call(this);if(o)return i.set.call(this,o),"function"==typeof t[b]&&t.removeAttribute(e),o}return null},n(r,e,i)}}function M(t,n,r){if(n)for(var e=0;e1?new c(n,r):new c(n),l=t(s,"onmessage");return l&&!1===l.configurable?(a=e(s),f=s,[i,u,"send","close"].forEach(function(t){a[t]=function(){var n=o.call(arguments);if(t===i||t===u){var r=n.length>0?n[0]:void 0;if(r){var e=Zone.__symbol__("ON_PROPERTY"+r);s[e]=a[e]}}return s[t].apply(s,n)}})):a=s,M(a,["close","error","message","open"],f),a};var a=r.WebSocket;for(var f in c)a[f]=c[f]}(0,a)}}var lt=v("unbound");Zone.__load_patch("util",function(t,n,r){r.patchOnProperties=M,r.patchMethod=X,r.bindArguments=_}),Zone.__load_patch("timers",function(t){G(t,"set","clear","Timeout"),G(t,"set","clear","Interval"),G(t,"set","clear","Immediate")}),Zone.__load_patch("requestAnimationFrame",function(t){G(t,"request","cancel","AnimationFrame"),G(t,"mozRequest","mozCancel","AnimationFrame"),G(t,"webkitRequest","webkitCancel","AnimationFrame")}),Zone.__load_patch("blocking",function(t,n){for(var r=["alert","prompt","confirm"],e=0;e=0&&"function"==typeof r[e.cbIdx]?p(e.name,r[e.cbIdx],e,i,null):t.apply(n,r)}})}()}),Zone.__load_patch("XHR",function(t,n){!function(n){var f=XMLHttpRequest.prototype,s=f[c],l=f[a];if(!s){var h=t.XMLHttpRequestEventTarget;if(h){var v=h.prototype;s=v[c],l=v[a]}}var y="readystatechange",d="scheduled";function g(t){XMLHttpRequest[i]=!1;var n=t.data,e=n.target,u=e[o];s||(s=e[c],l=e[a]),u&&l.call(e,y,u);var f=e[o]=function(){e.readyState===e.DONE&&!n.aborted&&XMLHttpRequest[i]&&t.state===d&&t.invoke()};return s.call(e,y,f),e[r]||(e[r]=t),k.apply(e,n.args),XMLHttpRequest[i]=!0,t}function b(){}function m(t){var n=t.data;return n.aborted=!0,w.apply(n.target,n.args)}var _=X(f,"open",function(){return function(t,n){return t[e]=0==n[2],t[u]=n[1],_.apply(t,n)}}),k=X(f,"send",function(){return function(t,n){return t[e]?k.apply(t,n):p("XMLHttpRequest.send",b,{target:t,url:t[u],isPeriodic:!1,delay:null,args:n,aborted:!1},g,m)}}),w=X(f,"abort",function(){return function(t){var n=t[r];if(n&&"string"==typeof n.type){if(null==n.cancelFn||n.data&&n.data.aborted)return;n.zone.cancelTask(n)}}})}();var r=v("xhrTask"),e=v("xhrSync"),o=v("xhrListener"),i=v("xhrScheduled"),u=v("xhrURL")}),Zone.__load_patch("geolocation",function(n){n.navigator&&n.navigator.geolocation&&function(n,r){for(var e=n.constructor.name,o=function(o){var i=r[o],u=n[i];if(u){if(!k(t(n,i)))return"continue";n[i]=function(t){var n=function(){return t.apply(this,_(arguments,e+"."+i))};return Z(n,t),n}(u)}},i=0;if;)a.call(t,u=c[f++])&&n.push(u);return n}},"1TsA":function(t,n){t.exports=function(t,n){return{value:n,done:!!t}}},"1sa7":function(t,n){t.exports=Math.log1p||function(t){return(t=+t)>-1e-8&&t<1e-8?t-t*t/2:Math.log(1+t)}},"25dN":function(t,n,r){var e=r("XKFU");e(e.S,"Object",{is:r("g6HL")})},"2OiF":function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},"2Spj":function(t,n,r){var e=r("XKFU");e(e.P,"Function",{bind:r("8MEG")})},"2atp":function(t,n,r){var e=r("XKFU"),o=Math.atanh;e(e.S+e.F*!(o&&1/o(-0)<0),"Math",{atanh:function(t){return 0==(t=+t)?t:Math.log((1+t)/(1-t))/2}})},"3Lyj":function(t,n,r){var e=r("KroJ");t.exports=function(t,n,r){for(var o in n)e(t,o,n[o],r);return t}},"4A4+":function(t,n,r){r("2Spj"),r("f3/d"),r("IXt9"),t.exports=r("g3g5").Function},"4LiD":function(t,n,r){"use strict";var e=r("dyZX"),o=r("XKFU"),i=r("KroJ"),u=r("3Lyj"),c=r("Z6vF"),a=r("SlkY"),f=r("9gX7"),s=r("0/R4"),l=r("eeVq"),h=r("XMVh"),p=r("fyDq"),v=r("Xbzi");t.exports=function(t,n,r,y,d,g){var b=e[t],m=b,_=d?"set":"add",k=m&&m.prototype,w={},x=function(t){var n=k[t];i(k,t,"delete"==t?function(t){return!(g&&!s(t))&&n.call(this,0===t?0:t)}:"has"==t?function(t){return!(g&&!s(t))&&n.call(this,0===t?0:t)}:"get"==t?function(t){return g&&!s(t)?void 0:n.call(this,0===t?0:t)}:"add"==t?function(t){return n.call(this,0===t?0:t),this}:function(t,r){return n.call(this,0===t?0:t,r),this})};if("function"==typeof m&&(g||k.forEach&&!l(function(){(new m).entries().next()}))){var S=new m,F=S[_](g?{}:-0,1)!=S,T=l(function(){S.has(1)}),O=h(function(t){new m(t)}),j=!g&&l(function(){for(var t=new m,n=5;n--;)t[_](n,n);return!t.has(-0)});O||((m=n(function(n,r){f(n,m,t);var e=v(new b,n,m);return null!=r&&a(r,d,e[_],e),e})).prototype=k,k.constructor=m),(T||j)&&(x("delete"),x("has"),d&&x("get")),(j||F)&&x(_),g&&k.clear&&delete k.clear}else m=y.getConstructor(n,t,d,_),u(m.prototype,r),c.NEED=!0;return p(m,t),w[t]=m,o(o.G+o.W+o.F*(m!=b),w),g||y.setStrong(m,t,d),m}},"4R4u":function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},"5Pf0":function(t,n,r){var e=r("S/j/"),o=r("OP3Y");r("Xtr8")("getPrototypeOf",function(){return function(t){return o(e(t))}})},"69bn":function(t,n,r){var e=r("y3w9"),o=r("2OiF"),i=r("K0xU")("species");t.exports=function(t,n){var r,u=e(t).constructor;return void 0===u||null==(r=e(u)[i])?n:o(r)}},"6AQ9":function(t,n,r){"use strict";var e=r("XKFU"),o=r("8a7r");e(e.S+e.F*r("eeVq")(function(){function t(){}return!(Array.of.call(t)instanceof t)}),"Array",{of:function(){for(var t=0,n=arguments.length,r=new("function"==typeof this?this:Array)(n);n>t;)o(r,t,arguments[t++]);return r.length=n,r}})},"6FMO":function(t,n,r){var e=r("0/R4"),o=r("EWmC"),i=r("K0xU")("species");t.exports=function(t){var n;return o(t)&&("function"!=typeof(n=t.constructor)||n!==Array&&!o(n.prototype)||(n=void 0),e(n)&&null===(n=n[i])&&(n=void 0)),void 0===n?Array:n}},"7h0T":function(t,n,r){var e=r("XKFU");e(e.S,"Number",{isNaN:function(t){return t!=t}})},"8+KV":function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(0),i=r("LyE8")([].forEach,!0);e(e.P+e.F*!i,"Array",{forEach:function(t){return o(this,t,arguments[1])}})},"84bF":function(t,n,r){"use strict";r("OGtf")("small",function(t){return function(){return t(this,"small","","")}})},"8MEG":function(t,n,r){"use strict";var e=r("2OiF"),o=r("0/R4"),i=r("MfQN"),u=[].slice,c={};t.exports=Function.bind||function(t){var n=e(this),r=u.call(arguments,1),a=function(){var e=r.concat(u.call(arguments));return this instanceof a?function(t,n,r){if(!(n in c)){for(var e=[],o=0;o0?arguments[0]:void 0)}},{get:function(t){var n=e.getEntry(o(this,"Map"),t);return n&&n.v},set:function(t,n){return e.def(o(this,"Map"),0===t?0:t,n)}},e,!0)},"9P93":function(t,n,r){var e=r("XKFU"),o=Math.imul;e(e.S+e.F*r("eeVq")(function(){return-5!=o(4294967295,5)||2!=o.length}),"Math",{imul:function(t,n){var r=+t,e=+n,o=65535&r,i=65535&e;return 0|o*i+((65535&r>>>16)*i+o*(65535&e>>>16)<<16>>>0)}})},"9VmF":function(t,n,r){"use strict";var e=r("XKFU"),o=r("ne8i"),i=r("0sh+"),u="".startsWith;e(e.P+e.F*r("UUeW")("startsWith"),"String",{startsWith:function(t){var n=i(this,t,"startsWith"),r=o(Math.min(arguments.length>1?arguments[1]:void 0,n.length)),e=String(t);return u?u.call(n,e,r):n.slice(r,r+e.length)===e}})},"9gX7":function(t,n){t.exports=function(t,n,r,e){if(!(t instanceof n)||void 0!==e&&e in t)throw TypeError(r+": incorrect invocation!");return t}},A2zW:function(t,n,r){"use strict";var e=r("XKFU"),o=r("RYi7"),i=r("vvmO"),u=r("l0Rn"),c=1..toFixed,a=Math.floor,f=[0,0,0,0,0,0],s="Number.toFixed: incorrect invocation!",l=function(t,n){for(var r=-1,e=n;++r<6;)f[r]=(e+=t*f[r])%1e7,e=a(e/1e7)},h=function(t){for(var n=6,r=0;--n>=0;)f[n]=a((r+=f[n])/t),r=r%t*1e7},p=function(){for(var t=6,n="";--t>=0;)if(""!==n||0===t||0!==f[t]){var r=String(f[t]);n=""===n?r:n+u.call("0",7-r.length)+r}return n},v=function(t,n,r){return 0===n?r:n%2==1?v(t,n-1,r*t):v(t*t,n/2,r)};e(e.P+e.F*(!!c&&("0.000"!==8e-5.toFixed(3)||"1"!==.9.toFixed(0)||"1.25"!==1.255.toFixed(2)||"1000000000000000128"!==(0xde0b6b3a7640080).toFixed(0))||!r("eeVq")(function(){c.call({})})),"Number",{toFixed:function(t){var n,r,e,c,a=i(this,s),f=o(t),y="",d="0";if(f<0||f>20)throw RangeError(s);if(a!=a)return"NaN";if(a<=-1e21||a>=1e21)return String(a);if(a<0&&(y="-",a=-a),a>1e-21)if(r=(n=function(t){for(var n=0,r=t;r>=4096;)n+=12,r/=4096;for(;r>=2;)n+=1,r/=2;return n}(a*v(2,69,1))-69)<0?a*v(2,-n,1):a/v(2,n,1),r*=4503599627370496,(n=52-n)>0){for(l(0,r),e=f;e>=7;)l(1e7,0),e-=7;for(l(v(10,e,1),0),e=n-1;e>=23;)h(1<<23),e-=23;h(1<0?y+((c=d.length)<=f?"0."+u.call("0",f-c)+d:d.slice(0,c-f)+"."+d.slice(c-f)):y+d}})},A5AN:function(t,n,r){"use strict";var e=r("AvRE")(!0);t.exports=function(t,n,r){return n+(r?e(t,n).length:1)}},Afnz:function(t,n,r){"use strict";var e=r("LQAc"),o=r("XKFU"),i=r("KroJ"),u=r("Mukb"),c=r("hPIQ"),a=r("QaDb"),f=r("fyDq"),s=r("OP3Y"),l=r("K0xU")("iterator"),h=!([].keys&&"next"in[].keys()),p=function(){return this};t.exports=function(t,n,r,v,y,d,g){a(r,n,v);var b,m,_,k=function(t){if(!h&&t in F)return F[t];switch(t){case"keys":case"values":return function(){return new r(this,t)}}return function(){return new r(this,t)}},w=n+" Iterator",x="values"==y,S=!1,F=t.prototype,T=F[l]||F["@@iterator"]||y&&F[y],O=T||k(y),j=y?x?k("entries"):O:void 0,M="Array"==n&&F.entries||T;if(M&&(_=s(M.call(new t)))!==Object.prototype&&_.next&&(f(_,w,!0),e||"function"==typeof _[l]||u(_,l,p)),x&&T&&"values"!==T.name&&(S=!0,O=function(){return T.call(this)}),e&&!g||!h&&!S&&F[l]||u(F,l,O),c[n]=O,c[w]=p,y)if(b={values:x?O:k("values"),keys:d?O:k("keys"),entries:j},g)for(m in b)m in F||i(F,m,b[m]);else o(o.P+o.F*(h||S),n,b);return b}},AphP:function(t,n,r){"use strict";var e=r("XKFU"),o=r("S/j/"),i=r("apmT");e(e.P+e.F*r("eeVq")(function(){return null!==new Date(NaN).toJSON()||1!==Date.prototype.toJSON.call({toISOString:function(){return 1}})}),"Date",{toJSON:function(t){var n=o(this),r=i(n);return"number"!=typeof r||isFinite(r)?n.toISOString():null}})},AvRE:function(t,n,r){var e=r("RYi7"),o=r("vhPU");t.exports=function(t){return function(n,r){var i,u,c=String(o(n)),a=e(r),f=c.length;return a<0||a>=f?t?"":void 0:(i=c.charCodeAt(a))<55296||i>56319||a+1===f||(u=c.charCodeAt(a+1))<56320||u>57343?t?c.charAt(a):i:t?c.slice(a,a+2):u-56320+(i-55296<<10)+65536}}},BC7C:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{fround:r("kcoS")})},"BJ/l":function(t,n,r){var e=r("XKFU");e(e.S,"Math",{log1p:r("1sa7")})},BP8U:function(t,n,r){var e=r("XKFU"),o=r("PKUr");e(e.S+e.F*(Number.parseInt!=o),"Number",{parseInt:o})},Btvt:function(t,n,r){"use strict";var e=r("I8a+"),o={};o[r("K0xU")("toStringTag")]="z",o+""!="[object z]"&&r("KroJ")(Object.prototype,"toString",function(){return"[object "+e(this)+"]"},!0)},"C/va":function(t,n,r){"use strict";var e=r("y3w9");t.exports=function(){var t=e(this),n="";return t.global&&(n+="g"),t.ignoreCase&&(n+="i"),t.multiline&&(n+="m"),t.unicode&&(n+="u"),t.sticky&&(n+="y"),n}},CkkT:function(t,n,r){var e=r("m0Pp"),o=r("Ymqv"),i=r("S/j/"),u=r("ne8i"),c=r("zRwo");t.exports=function(t,n){var r=1==t,a=2==t,f=3==t,s=4==t,l=6==t,h=5==t||l,p=n||c;return function(n,c,v){for(var y,d,g=i(n),b=o(g),m=e(c,v,3),_=u(b.length),k=0,w=r?p(n,_):a?p(n,0):void 0;_>k;k++)if((h||k in b)&&(d=m(y=b[k],k,g),t))if(r)w[k]=d;else if(d)switch(t){case 3:return!0;case 5:return y;case 6:return k;case 2:w.push(y)}else if(s)return!1;return l?-1:f||s?s:w}}},CuTL:function(t,n,r){r("fyVe"),r("U2t9"),r("2atp"),r("+auO"),r("MtdB"),r("Jcmo"),r("nzyx"),r("BC7C"),r("x8ZO"),r("9P93"),r("eHKK"),r("BJ/l"),r("pp/T"),r("CyHz"),r("bBoP"),r("x8Yj"),r("hLT2"),t.exports=r("g3g5").Math},CyHz:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{sign:r("lvtm")})},DNiP:function(t,n,r){"use strict";var e=r("XKFU"),o=r("eyMr");e(e.P+e.F*!r("LyE8")([].reduce,!0),"Array",{reduce:function(t){return o(this,t,arguments.length,arguments[1],!1)}})},DVgA:function(t,n,r){var e=r("zhAb"),o=r("4R4u");t.exports=Object.keys||function(t){return e(t,o)}},DW2E:function(t,n,r){var e=r("0/R4"),o=r("Z6vF").onFreeze;r("Xtr8")("freeze",function(t){return function(n){return t&&e(n)?t(o(n)):n}})},EK0E:function(t,n,r){"use strict";var e,o=r("CkkT")(0),i=r("KroJ"),u=r("Z6vF"),c=r("czNK"),a=r("ZD67"),f=r("0/R4"),s=r("eeVq"),l=r("s5qY"),h=u.getWeak,p=Object.isExtensible,v=a.ufstore,y={},d=function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},g={get:function(t){if(f(t)){var n=h(t);return!0===n?v(l(this,"WeakMap")).get(t):n?n[this._i]:void 0}},set:function(t,n){return a.def(l(this,"WeakMap"),t,n)}},b=t.exports=r("4LiD")("WeakMap",d,g,a,!0,!0);s(function(){return 7!=(new b).set((Object.freeze||Object)(y),7).get(y)})&&(c((e=a.getConstructor(d,"WeakMap")).prototype,g),u.NEED=!0,o(["delete","has","get","set"],function(t){var n=b.prototype,r=n[t];i(n,t,function(n,o){if(f(n)&&!p(n)){this._f||(this._f=new e);var i=this._f[t](n,o);return"set"==t?this:i}return r.call(this,n,o)})}))},EWmC:function(t,n,r){var e=r("LZWt");t.exports=Array.isArray||function(t){return"Array"==e(t)}},EemH:function(t,n,r){var e=r("UqcF"),o=r("RjD/"),i=r("aCFj"),u=r("apmT"),c=r("aagx"),a=r("xpql"),f=Object.getOwnPropertyDescriptor;n.f=r("nh4g")?f:function(t,n){if(t=i(t),n=u(n,!0),a)try{return f(t,n)}catch(r){}if(c(t,n))return o(!e.f.call(t,n),t[n])}},FEjr:function(t,n,r){"use strict";r("OGtf")("strike",function(t){return function(){return t(this,"strike","","")}})},FJW5:function(t,n,r){var e=r("hswa"),o=r("y3w9"),i=r("DVgA");t.exports=r("nh4g")?Object.defineProperties:function(t,n){o(t);for(var r,u=i(n),c=u.length,a=0;c>a;)e.f(t,r=u[a++],n[r]);return t}},FLlr:function(t,n,r){var e=r("XKFU");e(e.P,"String",{repeat:r("l0Rn")})},FlsD:function(t,n,r){var e=r("0/R4");r("Xtr8")("isExtensible",function(t){return function(n){return!!e(n)&&(!t||t(n))}})},GNAe:function(t,n,r){var e=r("XKFU"),o=r("PKUr");e(e.G+e.F*(parseInt!=o),{parseInt:o})},H6hf:function(t,n,r){var e=r("y3w9");t.exports=function(t,n,r,o){try{return o?n(e(r)[0],r[1]):n(r)}catch(u){var i=t.return;throw void 0!==i&&e(i.call(t)),u}}},"HAE/":function(t,n,r){var e=r("XKFU");e(e.S+e.F*!r("nh4g"),"Object",{defineProperty:r("hswa").f})},HEwt:function(t,n,r){"use strict";var e=r("m0Pp"),o=r("XKFU"),i=r("S/j/"),u=r("H6hf"),c=r("M6Qj"),a=r("ne8i"),f=r("8a7r"),s=r("J+6e");o(o.S+o.F*!r("XMVh")(function(t){Array.from(t)}),"Array",{from:function(t){var n,r,o,l,h=i(t),p="function"==typeof this?this:Array,v=arguments.length,y=v>1?arguments[1]:void 0,d=void 0!==y,g=0,b=s(h);if(d&&(y=e(y,v>2?arguments[2]:void 0,2)),null==b||p==Array&&c(b))for(r=new p(n=a(h.length));n>g;g++)f(r,g,d?y(h[g],g):h[g]);else for(l=b.call(h),r=new p;!(o=l.next()).done;g++)f(r,g,d?u(l,y,[o.value,g],!0):o.value);return r.length=g,r}})},I78e:function(t,n,r){"use strict";var e=r("XKFU"),o=r("+rLv"),i=r("LZWt"),u=r("d/Gc"),c=r("ne8i"),a=[].slice;e(e.P+e.F*r("eeVq")(function(){o&&a.call(o)}),"Array",{slice:function(t,n){var r=c(this.length),e=i(this);if(n=void 0===n?r:n,"Array"==e)return a.call(this,t,n);for(var o=u(t,r),f=u(n,r),s=c(f-o),l=new Array(s),h=0;h1?arguments[1]:void 0)}}),r("nGyu")(i)},"IU+Z":function(t,n,r){"use strict";r("sMXx");var e=r("KroJ"),o=r("Mukb"),i=r("eeVq"),u=r("vhPU"),c=r("K0xU"),a=r("Ugos"),f=c("species"),s=!i(function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")}),l=function(){var t=/(?:)/,n=t.exec;t.exec=function(){return n.apply(this,arguments)};var r="ab".split(t);return 2===r.length&&"a"===r[0]&&"b"===r[1]}();t.exports=function(t,n,r){var h=c(t),p=!i(function(){var n={};return n[h]=function(){return 7},7!=""[t](n)}),v=p?!i(function(){var n=!1,r=/a/;return r.exec=function(){return n=!0,null},"split"===t&&(r.constructor={},r.constructor[f]=function(){return r}),r[h](""),!n}):void 0;if(!p||!v||"replace"===t&&!s||"split"===t&&!l){var y=/./[h],d=r(u,h,""[t],function(t,n,r,e,o){return n.exec===a?p&&!o?{done:!0,value:y.call(n,r,e)}:{done:!0,value:t.call(r,n,e)}:{done:!1}}),g=d[1];e(String.prototype,t,d[0]),o(RegExp.prototype,h,2==n?function(t,n){return g.call(t,this,n)}:function(t){return g.call(t,this)})}}},IXt9:function(t,n,r){"use strict";var e=r("0/R4"),o=r("OP3Y"),i=r("K0xU")("hasInstance"),u=Function.prototype;i in u||r("hswa").f(u,i,{value:function(t){if("function"!=typeof this||!e(t))return!1;if(!e(this.prototype))return t instanceof this;for(;t=o(t);)if(this.prototype===t)return!0;return!1}})},Iw71:function(t,n,r){var e=r("0/R4"),o=r("dyZX").document,i=e(o)&&e(o.createElement);t.exports=function(t){return i?o.createElement(t):{}}},"J+6e":function(t,n,r){var e=r("I8a+"),o=r("K0xU")("iterator"),i=r("hPIQ");t.exports=r("g3g5").getIteratorMethod=function(t){if(null!=t)return t[o]||t["@@iterator"]||i[e(t)]}},JCqj:function(t,n,r){"use strict";r("OGtf")("sup",function(t){return function(){return t(this,"sup","","")}})},Jcmo:function(t,n,r){var e=r("XKFU"),o=Math.exp;e(e.S,"Math",{cosh:function(t){return(o(t=+t)+o(-t))/2}})},JduL:function(t,n,r){r("Xtr8")("getOwnPropertyNames",function(){return r("e7yV").f})},JiEa:function(t,n){n.f=Object.getOwnPropertySymbols},K0xU:function(t,n,r){var e=r("VTer")("wks"),o=r("ylqs"),i=r("dyZX").Symbol,u="function"==typeof i;(t.exports=function(t){return e[t]||(e[t]=u&&i[t]||(u?i:o)("Symbol."+t))}).store=e},KKXr:function(t,n,r){"use strict";var e=r("quPj"),o=r("y3w9"),i=r("69bn"),u=r("A5AN"),c=r("ne8i"),a=r("Xxuz"),f=r("Ugos"),s=Math.min,l=[].push,h=!!function(){try{return new RegExp("x","y")}catch(t){}}();r("IU+Z")("split",2,function(t,n,r,p){var v;return v="c"=="abbc".split(/(b)*/)[1]||4!="test".split(/(?:)/,-1).length||2!="ab".split(/(?:ab)*/).length||4!=".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length?function(t,n){var o=String(this);if(void 0===t&&0===n)return[];if(!e(t))return r.call(o,t,n);for(var i,u,c,a=[],s=0,h=void 0===n?4294967295:n>>>0,p=new RegExp(t.source,(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":"")+"g");(i=f.call(p,o))&&!((u=p.lastIndex)>s&&(a.push(o.slice(s,i.index)),i.length>1&&i.index=h));)p.lastIndex===i.index&&p.lastIndex++;return s===o.length?!c&&p.test("")||a.push(""):a.push(o.slice(s)),a.length>h?a.slice(0,h):a}:"0".split(void 0,0).length?function(t,n){return void 0===t&&0===n?[]:r.call(this,t,n)}:r,[function(r,e){var o=t(this),i=null==r?void 0:r[n];return void 0!==i?i.call(r,o,e):v.call(String(o),r,e)},function(t,n){var e=p(v,t,this,n,v!==r);if(e.done)return e.value;var f=o(t),l=String(this),y=i(f,RegExp),d=f.unicode,g=new y(h?f:"^(?:"+f.source+")",(f.ignoreCase?"i":"")+(f.multiline?"m":"")+(f.unicode?"u":"")+(h?"y":"g")),b=void 0===n?4294967295:n>>>0;if(0===b)return[];if(0===l.length)return null===a(g,l)?[l]:[];for(var m=0,_=0,k=[];_document.F=Object<\/script>"),t.close(),a=t.F;e--;)delete a.prototype[i[e]];return a()};t.exports=Object.create||function(t,n){var r;return null!==t?(c.prototype=e(t),r=new c,c.prototype=null,r[u]=t):r=a(),void 0===n?r:o(r,n)}},L9s1:function(t,n,r){"use strict";var e=r("XKFU"),o=r("0sh+");e(e.P+e.F*r("UUeW")("includes"),"String",{includes:function(t){return!!~o(this,t,"includes").indexOf(t,arguments.length>1?arguments[1]:void 0)}})},LK8F:function(t,n,r){var e=r("XKFU");e(e.S,"Array",{isArray:r("EWmC")})},LQAc:function(t,n){t.exports=!1},LVwc:function(t,n){var r=Math.expm1;t.exports=!r||r(10)>22025.465794806718||r(10)<22025.465794806718||-2e-17!=r(-2e-17)?function(t){return 0==(t=+t)?t:t>-1e-6&&t<1e-6?t+t*t/2:Math.exp(t)-1}:r},LZWt:function(t,n){var r={}.toString;t.exports=function(t){return r.call(t).slice(8,-1)}},Ljet:function(t,n,r){var e=r("XKFU");e(e.S,"Number",{EPSILON:Math.pow(2,-52)})},Lmuc:function(t,n,r){r("xfY5"),r("A2zW"),r("VKir"),r("Ljet"),r("/KAi"),r("fN96"),r("7h0T"),r("sbF8"),r("h/M4"),r("knhD"),r("XfKG"),r("BP8U"),t.exports=r("g3g5").Number},LyE8:function(t,n,r){"use strict";var e=r("eeVq");t.exports=function(t,n){return!!t&&e(function(){n?t.call(null,function(){},1):t.call(null)})}},M6Qj:function(t,n,r){var e=r("hPIQ"),o=r("K0xU")("iterator"),i=Array.prototype;t.exports=function(t){return void 0!==t&&(e.Array===t||i[o]===t)}},MfQN:function(t,n){t.exports=function(t,n,r){var e=void 0===r;switch(n.length){case 0:return e?t():t.call(r);case 1:return e?t(n[0]):t.call(r,n[0]);case 2:return e?t(n[0],n[1]):t.call(r,n[0],n[1]);case 3:return e?t(n[0],n[1],n[2]):t.call(r,n[0],n[1],n[2]);case 4:return e?t(n[0],n[1],n[2],n[3]):t.call(r,n[0],n[1],n[2],n[3])}return t.apply(r,n)}},MtdB:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{clz32:function(t){return(t>>>=0)?31-Math.floor(Math.log(t+.5)*Math.LOG2E):32}})},Mukb:function(t,n,r){var e=r("hswa"),o=r("RjD/");t.exports=r("nh4g")?function(t,n,r){return e.f(t,n,o(1,r))}:function(t,n,r){return t[n]=r,t}},N8g3:function(t,n,r){n.f=r("K0xU")},Nr18:function(t,n,r){"use strict";var e=r("S/j/"),o=r("d/Gc"),i=r("ne8i");t.exports=function(t){for(var n=e(this),r=i(n.length),u=arguments.length,c=o(u>1?arguments[1]:void 0,r),a=u>2?arguments[2]:void 0,f=void 0===a?r:o(a,r);f>c;)n[c++]=t;return n}},Nz9U:function(t,n,r){"use strict";var e=r("XKFU"),o=r("aCFj"),i=[].join;e(e.P+e.F*(r("Ymqv")!=Object||!r("LyE8")(i)),"Array",{join:function(t){return i.call(o(this),void 0===t?",":t)}})},OEbY:function(t,n,r){r("nh4g")&&"g"!=/./g.flags&&r("hswa").f(RegExp.prototype,"flags",{configurable:!0,get:r("C/va")})},OG14:function(t,n,r){"use strict";var e=r("y3w9"),o=r("g6HL"),i=r("Xxuz");r("IU+Z")("search",1,function(t,n,r,u){return[function(r){var e=t(this),o=null==r?void 0:r[n];return void 0!==o?o.call(r,e):new RegExp(r)[n](String(e))},function(t){var n=u(r,t,this);if(n.done)return n.value;var c=e(t),a=String(this),f=c.lastIndex;o(f,0)||(c.lastIndex=0);var s=i(c,a);return o(c.lastIndex,f)||(c.lastIndex=f),null===s?-1:s.index}]})},OGtf:function(t,n,r){var e=r("XKFU"),o=r("eeVq"),i=r("vhPU"),u=/"/g,c=function(t,n,r,e){var o=String(i(t)),c="<"+n;return""!==r&&(c+=" "+r+'="'+String(e).replace(u,""")+'"'),c+">"+o+""};t.exports=function(t,n){var r={};r[t]=n(c),e(e.P+e.F*o(function(){var n=""[t]('"');return n!==n.toLowerCase()||n.split('"').length>3}),"String",r)}},OP3Y:function(t,n,r){var e=r("aagx"),o=r("S/j/"),i=r("YTvA")("IE_PROTO"),u=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=o(t),e(t,i)?t[i]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?u:null}},OnI7:function(t,n,r){var e=r("dyZX"),o=r("g3g5"),i=r("LQAc"),u=r("N8g3"),c=r("hswa").f;t.exports=function(t){var n=o.Symbol||(o.Symbol=i?{}:e.Symbol||{});"_"==t.charAt(0)||t in n||c(n,t,{value:u.f(t)})}},Oyvg:function(t,n,r){var e=r("dyZX"),o=r("Xbzi"),i=r("hswa").f,u=r("kJMx").f,c=r("quPj"),a=r("C/va"),f=e.RegExp,s=f,l=f.prototype,h=/a/g,p=/a/g,v=new f(h)!==h;if(r("nh4g")&&(!v||r("eeVq")(function(){return p[r("K0xU")("match")]=!1,f(h)!=h||f(p)==p||"/a/i"!=f(h,"i")}))){f=function(t,n){var r=this instanceof f,e=c(t),i=void 0===n;return!r&&e&&t.constructor===f&&i?t:o(v?new s(e&&!i?t.source:t,n):s((e=t instanceof f)?t.source:t,e&&i?a.call(t):n),r?this:l,f)};for(var y=function(t){t in f||i(f,t,{configurable:!0,get:function(){return s[t]},set:function(n){s[t]=n}})},d=u(s),g=0;d.length>g;)y(d[g++]);l.constructor=f,f.prototype=l,r("KroJ")(e,"RegExp",f)}r("elZq")("RegExp")},PKUr:function(t,n,r){var e=r("dyZX").parseInt,o=r("qncB").trim,i=r("/e88"),u=/^[-+]?0[xX]/;t.exports=8!==e(i+"08")||22!==e(i+"0x16")?function(t,n){var r=o(String(t),3);return e(r,n>>>0||(u.test(r)?16:10))}:e},QaDb:function(t,n,r){"use strict";var e=r("Kuth"),o=r("RjD/"),i=r("fyDq"),u={};r("Mukb")(u,r("K0xU")("iterator"),function(){return this}),t.exports=function(t,n,r){t.prototype=e(u,{next:o(1,r)}),i(t,n+" Iterator")}},RW0V:function(t,n,r){var e=r("S/j/"),o=r("DVgA");r("Xtr8")("keys",function(){return function(t){return o(e(t))}})},RYi7:function(t,n){var r=Math.ceil,e=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?e:r)(t)}},"RjD/":function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},"S/j/":function(t,n,r){var e=r("vhPU");t.exports=function(t){return Object(e(t))}},SMB2:function(t,n,r){"use strict";r("OGtf")("bold",function(t){return function(){return t(this,"b","","")}})},SPin:function(t,n,r){"use strict";var e=r("XKFU"),o=r("eyMr");e(e.P+e.F*!r("LyE8")([].reduceRight,!0),"Array",{reduceRight:function(t){return o(this,t,arguments.length,arguments[1],!0)}})},SRfc:function(t,n,r){"use strict";var e=r("y3w9"),o=r("ne8i"),i=r("A5AN"),u=r("Xxuz");r("IU+Z")("match",1,function(t,n,r,c){return[function(r){var e=t(this),o=null==r?void 0:r[n];return void 0!==o?o.call(r,e):new RegExp(r)[n](String(e))},function(t){var n=c(r,t,this);if(n.done)return n.value;var a=e(t),f=String(this);if(!a.global)return u(a,f);var s=a.unicode;a.lastIndex=0;for(var l,h=[],p=0;null!==(l=u(a,f));){var v=String(l[0]);h[p]=v,""===v&&(a.lastIndex=i(f,o(a.lastIndex),s)),p++}return 0===p?null:h}]})},SlkY:function(t,n,r){var e=r("m0Pp"),o=r("H6hf"),i=r("M6Qj"),u=r("y3w9"),c=r("ne8i"),a=r("J+6e"),f={},s={};(n=t.exports=function(t,n,r,l,h){var p,v,y,d,g=h?function(){return t}:a(t),b=e(r,l,n?2:1),m=0;if("function"!=typeof g)throw TypeError(t+" is not iterable!");if(i(g)){for(p=c(t.length);p>m;m++)if((d=n?b(u(v=t[m])[0],v[1]):b(t[m]))===f||d===s)return d}else for(y=g.call(t);!(v=y.next()).done;)if((d=o(y,b,v.value,n))===f||d===s)return d}).BREAK=f,n.RETURN=s},T39b:function(t,n,r){"use strict";var e=r("wmvG"),o=r("s5qY");t.exports=r("4LiD")("Set",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{add:function(t){return e.def(o(this,"Set"),t=0===t?0:t,t)}},e)},Tze0:function(t,n,r){"use strict";r("qncB")("trim",function(t){return function(){return t(this,3)}})},U2t9:function(t,n,r){var e=r("XKFU"),o=Math.asinh;e(e.S+e.F*!(o&&1/o(0)>0),"Math",{asinh:function t(n){return isFinite(n=+n)&&0!=n?n<0?-t(-n):Math.log(n+Math.sqrt(n*n+1)):n}})},UUeW:function(t,n,r){var e=r("K0xU")("match");t.exports=function(t){var n=/./;try{"/./"[t](n)}catch(r){try{return n[e]=!1,!"/./"[t](n)}catch(o){}}return!0}},Ugos:function(t,n,r){"use strict";var e,o,i=r("C/va"),u=RegExp.prototype.exec,c=String.prototype.replace,a=u,f=(o=/b*/g,u.call(e=/a/,"a"),u.call(o,"a"),0!==e.lastIndex||0!==o.lastIndex),s=void 0!==/()??/.exec("")[1];(f||s)&&(a=function(t){var n,r,e,o,a=this;return s&&(r=new RegExp("^"+a.source+"$(?!\\s)",i.call(a))),f&&(n=a.lastIndex),e=u.call(a,t),f&&e&&(a.lastIndex=a.global?e.index+e[0].length:n),s&&e&&e.length>1&&c.call(e[0],r,function(){for(o=1;ou;){if(n=+arguments[u++],o(n,1114111)!==n)throw RangeError(n+" is not a valid code point");r.push(n<65536?i(n):i(55296+((n-=65536)>>10),n%1024+56320))}return r.join("")}})},WLL4:function(t,n,r){var e=r("XKFU");e(e.S+e.F*!r("nh4g"),"Object",{defineProperties:r("FJW5")})},XKFU:function(t,n,r){var e=r("dyZX"),o=r("g3g5"),i=r("Mukb"),u=r("KroJ"),c=r("m0Pp"),a=function(t,n,r){var f,s,l,h,p=t&a.F,v=t&a.G,y=t&a.P,d=t&a.B,g=v?e:t&a.S?e[n]||(e[n]={}):(e[n]||{}).prototype,b=v?o:o[n]||(o[n]={}),m=b.prototype||(b.prototype={});for(f in v&&(r=n),r)l=((s=!p&&g&&void 0!==g[f])?g:r)[f],h=d&&s?c(l,e):y&&"function"==typeof l?c(Function.call,l):l,g&&u(g,f,l,t&a.U),b[f]!=l&&i(b,f,h),y&&m[f]!=l&&(m[f]=l)};e.core=o,a.F=1,a.G=2,a.S=4,a.P=8,a.B=16,a.W=32,a.U=64,a.R=128,t.exports=a},XMVh:function(t,n,r){var e=r("K0xU")("iterator"),o=!1;try{var i=[7][e]();i.return=function(){o=!0},Array.from(i,function(){throw 2})}catch(u){}t.exports=function(t,n){if(!n&&!o)return!1;var r=!1;try{var i=[7],c=i[e]();c.next=function(){return{done:r=!0}},i[e]=function(){return c},t(i)}catch(u){}return r}},Xbzi:function(t,n,r){var e=r("0/R4"),o=r("i5dc").set;t.exports=function(t,n,r){var i,u=n.constructor;return u!==r&&"function"==typeof u&&(i=u.prototype)!==r.prototype&&e(i)&&o&&o(t,i),t}},XfKG:function(t,n,r){var e=r("XKFU"),o=r("11IZ");e(e.S+e.F*(Number.parseFloat!=o),"Number",{parseFloat:o})},XfO3:function(t,n,r){"use strict";var e=r("AvRE")(!0);r("Afnz")(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,n=this._t,r=this._i;return r>=n.length?{value:void 0,done:!0}:(t=e(n,r),this._i+=t.length,{value:t,done:!1})})},Xtr8:function(t,n,r){var e=r("XKFU"),o=r("g3g5"),i=r("eeVq");t.exports=function(t,n){var r=(o.Object||{})[t]||Object[t],u={};u[t]=n(r),e(e.S+e.F*i(function(){r(1)}),"Object",u)}},Xxuz:function(t,n,r){"use strict";var e=r("I8a+"),o=RegExp.prototype.exec;t.exports=function(t,n){var r=t.exec;if("function"==typeof r){var i=r.call(t,n);if("object"!=typeof i)throw new TypeError("RegExp exec method returned something other than an Object or null");return i}if("RegExp"!==e(t))throw new TypeError("RegExp#exec called on incompatible receiver");return o.call(t,n)}},YJVH:function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(4);e(e.P+e.F*!r("LyE8")([].every,!0),"Array",{every:function(t){return o(this,t,arguments[1])}})},YTvA:function(t,n,r){var e=r("VTer")("keys"),o=r("ylqs");t.exports=function(t){return e[t]||(e[t]=o(t))}},Ymqv:function(t,n,r){var e=r("LZWt");t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==e(t)?t.split(""):Object(t)}},Z6vF:function(t,n,r){var e=r("ylqs")("meta"),o=r("0/R4"),i=r("aagx"),u=r("hswa").f,c=0,a=Object.isExtensible||function(){return!0},f=!r("eeVq")(function(){return a(Object.preventExtensions({}))}),s=function(t){u(t,e,{value:{i:"O"+ ++c,w:{}}})},l=t.exports={KEY:e,NEED:!1,fastKey:function(t,n){if(!o(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!i(t,e)){if(!a(t))return"F";if(!n)return"E";s(t)}return t[e].i},getWeak:function(t,n){if(!i(t,e)){if(!a(t))return!0;if(!n)return!1;s(t)}return t[e].w},onFreeze:function(t){return f&&l.NEED&&a(t)&&!i(t,e)&&s(t),t}}},ZD67:function(t,n,r){"use strict";var e=r("3Lyj"),o=r("Z6vF").getWeak,i=r("y3w9"),u=r("0/R4"),c=r("9gX7"),a=r("SlkY"),f=r("CkkT"),s=r("aagx"),l=r("s5qY"),h=f(5),p=f(6),v=0,y=function(t){return t._l||(t._l=new d)},d=function(){this.a=[]},g=function(t,n){return h(t.a,function(t){return t[0]===n})};d.prototype={get:function(t){var n=g(this,t);if(n)return n[1]},has:function(t){return!!g(this,t)},set:function(t,n){var r=g(this,t);r?r[1]=n:this.a.push([t,n])},delete:function(t){var n=p(this.a,function(n){return n[0]===t});return~n&&this.a.splice(n,1),!!~n}},t.exports={getConstructor:function(t,n,r,i){var f=t(function(t,e){c(t,f,n,"_i"),t._t=n,t._i=v++,t._l=void 0,null!=e&&a(e,r,t[i],t)});return e(f.prototype,{delete:function(t){if(!u(t))return!1;var r=o(t);return!0===r?y(l(this,n)).delete(t):r&&s(r,this._i)&&delete r[this._i]},has:function(t){if(!u(t))return!1;var r=o(t);return!0===r?y(l(this,n)).has(t):r&&s(r,this._i)}}),f},def:function(t,n,r){var e=o(i(n),!0);return!0===e?y(t).set(n,r):e[t._i]=r,t},ufstore:y}},Zshi:function(t,n,r){var e=r("0/R4");r("Xtr8")("isFrozen",function(t){return function(n){return!e(n)||!!t&&t(n)}})},Zz4T:function(t,n,r){"use strict";r("OGtf")("sub",function(t){return function(){return t(this,"sub","","")}})},a1Th:function(t,n,r){"use strict";r("OEbY");var e=r("y3w9"),o=r("C/va"),i=r("nh4g"),u=/./.toString,c=function(t){r("KroJ")(RegExp.prototype,"toString",t,!0)};r("eeVq")(function(){return"/a/b"!=u.call({source:"a",flags:"b"})})?c(function(){var t=e(this);return"/".concat(t.source,"/","flags"in t?t.flags:!i&&t instanceof RegExp?o.call(t):void 0)}):"toString"!=u.name&&c(function(){return u.call(this)})},aCFj:function(t,n,r){var e=r("Ymqv"),o=r("vhPU");t.exports=function(t){return e(o(t))}},aagx:function(t,n){var r={}.hasOwnProperty;t.exports=function(t,n){return r.call(t,n)}},apmT:function(t,n,r){var e=r("0/R4");t.exports=function(t,n){if(!e(t))return t;var r,o;if(n&&"function"==typeof(r=t.toString)&&!e(o=r.call(t)))return o;if("function"==typeof(r=t.valueOf)&&!e(o=r.call(t)))return o;if(!n&&"function"==typeof(r=t.toString)&&!e(o=r.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},bBoP:function(t,n,r){var e=r("XKFU"),o=r("LVwc"),i=Math.exp;e(e.S+e.F*r("eeVq")(function(){return-2e-17!=!Math.sinh(-2e-17)}),"Math",{sinh:function(t){return Math.abs(t=+t)<1?(o(t)-o(-t))/2:(i(t-1)-i(-t-1))*(Math.E/2)}})},bDcW:function(t,n,r){"use strict";r("OGtf")("fontcolor",function(t){return function(n){return t(this,"font","color",n)}})},bHtr:function(t,n,r){var e=r("XKFU");e(e.P,"Array",{fill:r("Nr18")}),r("nGyu")("fill")},bWfx:function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(1);e(e.P+e.F*!r("LyE8")([].map,!0),"Array",{map:function(t){return o(this,t,arguments[1])}})},czNK:function(t,n,r){"use strict";var e=r("DVgA"),o=r("JiEa"),i=r("UqcF"),u=r("S/j/"),c=r("Ymqv"),a=Object.assign;t.exports=!a||r("eeVq")(function(){var t={},n={},r=Symbol(),e="abcdefghijklmnopqrst";return t[r]=7,e.split("").forEach(function(t){n[t]=t}),7!=a({},t)[r]||Object.keys(a({},n)).join("")!=e})?function(t,n){for(var r=u(t),a=arguments.length,f=1,s=o.f,l=i.f;a>f;)for(var h,p=c(arguments[f++]),v=s?e(p).concat(s(p)):e(p),y=v.length,d=0;y>d;)l.call(p,h=v[d++])&&(r[h]=p[h]);return r}:a},"d/Gc":function(t,n,r){var e=r("RYi7"),o=Math.max,i=Math.min;t.exports=function(t,n){return(t=e(t))<0?o(t+n,0):i(t,n)}},"dE+T":function(t,n,r){var e=r("XKFU");e(e.P,"Array",{copyWithin:r("upKx")}),r("nGyu")("copyWithin")},dQfE:function(t,n,r){r("XfO3"),r("LK8F"),r("HEwt"),r("6AQ9"),r("Nz9U"),r("I78e"),r("Vd3H"),r("8+KV"),r("bWfx"),r("0l/t"),r("dZ+Y"),r("YJVH"),r("DNiP"),r("SPin"),r("V+eJ"),r("mGWK"),r("dE+T"),r("bHtr"),r("dRSK"),r("INYr"),r("0E+W"),r("yt8O"),t.exports=r("g3g5").Array},dRSK:function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(5),i=!0;"find"in[]&&Array(1).find(function(){i=!1}),e(e.P+e.F*i,"Array",{find:function(t){return o(this,t,arguments.length>1?arguments[1]:void 0)}}),r("nGyu")("find")},"dZ+Y":function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(3);e(e.P+e.F*!r("LyE8")([].some,!0),"Array",{some:function(t){return o(this,t,arguments[1])}})},dyZX:function(t,n){var r=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},e7yV:function(t,n,r){var e=r("aCFj"),o=r("kJMx").f,i={}.toString,u="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];t.exports.f=function(t){return u&&"[object Window]"==i.call(t)?function(t){try{return o(t)}catch(n){return u.slice()}}(t):o(e(t))}},eHKK:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{log10:function(t){return Math.log(t)*Math.LOG10E}})},eI33:function(t,n,r){var e=r("XKFU"),o=r("aCFj"),i=r("ne8i");e(e.S,"String",{raw:function(t){for(var n=o(t.raw),r=i(n.length),e=arguments.length,u=[],c=0;r>c;)u.push(String(n[c++])),c=0:l>h;h+=p)h in s&&(c=n(c,s[h],h,f));return c}},"f3/d":function(t,n,r){var e=r("hswa").f,o=Function.prototype,i=/^\s*function ([^ (]*)/;"name"in o||r("nh4g")&&e(o,"name",{configurable:!0,get:function(){try{return(""+this).match(i)[1]}catch(t){return""}}})},fN96:function(t,n,r){var e=r("XKFU");e(e.S,"Number",{isInteger:r("nBIS")})},fyDq:function(t,n,r){var e=r("hswa").f,o=r("aagx"),i=r("K0xU")("toStringTag");t.exports=function(t,n,r){t&&!o(t=r?t:t.prototype,i)&&e(t,i,{configurable:!0,value:n})}},fyVe:function(t,n,r){var e=r("XKFU"),o=r("1sa7"),i=Math.sqrt,u=Math.acosh;e(e.S+e.F*!(u&&710==Math.floor(u(Number.MAX_VALUE))&&u(1/0)==1/0),"Math",{acosh:function(t){return(t=+t)<1?NaN:t>94906265.62425156?Math.log(t)+Math.LN2:o(t-1+i(t-1)*i(t+1))}})},g3g5:function(t,n){var r=t.exports={version:"2.6.1"};"number"==typeof __e&&(__e=r)},g4EE:function(t,n,r){"use strict";var e=r("y3w9"),o=r("apmT");t.exports=function(t){if("string"!==t&&"number"!==t&&"default"!==t)throw TypeError("Incorrect hint");return o(e(this),"number"!=t)}},g6HL:function(t,n){t.exports=Object.is||function(t,n){return t===n?0!==t||1/t==1/n:t!=t&&n!=n}},"h/M4":function(t,n,r){var e=r("XKFU");e(e.S,"Number",{MAX_SAFE_INTEGER:9007199254740991})},h7Nl:function(t,n,r){var e=Date.prototype,o=e.toString,i=e.getTime;new Date(NaN)+""!="Invalid Date"&&r("KroJ")(e,"toString",function(){var t=i.call(this);return t==t?o.call(this):"Invalid Date"})},hEkN:function(t,n,r){"use strict";r("OGtf")("anchor",function(t){return function(n){return t(this,"a","name",n)}})},hHhE:function(t,n,r){var e=r("XKFU");e(e.S,"Object",{create:r("Kuth")})},hLT2:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{trunc:function(t){return(t>0?Math.floor:Math.ceil)(t)}})},"hN/g":function(t,n,r){"use strict";r.r(n),r("dQfE"),r("nx1v"),r("4A4+"),r("qKs0"),r("CuTL"),r("Lmuc"),r("99sg"),r("ifmr"),r("oka+"),r("rfyP"),r("VXxg"),r("V5/Y"),r("vqGA"),r("hYbK"),r("0TWp")},hPIQ:function(t,n){t.exports={}},hYbK:function(t,n,r){r("Btvt"),r("yt8O"),r("EK0E"),t.exports=r("g3g5").WeakMap},hswa:function(t,n,r){var e=r("y3w9"),o=r("xpql"),i=r("apmT"),u=Object.defineProperty;n.f=r("nh4g")?Object.defineProperty:function(t,n,r){if(e(t),n=i(n,!0),e(r),o)try{return u(t,n,r)}catch(c){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(t[n]=r.value),t}},i5dc:function(t,n,r){var e=r("0/R4"),o=r("y3w9"),i=function(t,n){if(o(t),!e(n)&&null!==n)throw TypeError(n+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,n,e){try{(e=r("m0Pp")(Function.call,r("EemH").f(Object.prototype,"__proto__").set,2))(t,[]),n=!(t instanceof Array)}catch(o){n=!0}return function(t,r){return i(t,r),n?t.__proto__=r:e(t,r),t}}({},!1):void 0),check:i}},ifmr:function(t,n,r){r("tyy+"),t.exports=r("g3g5").parseFloat},ioFf:function(t,n,r){"use strict";var e=r("dyZX"),o=r("aagx"),i=r("nh4g"),u=r("XKFU"),c=r("KroJ"),a=r("Z6vF").KEY,f=r("eeVq"),s=r("VTer"),l=r("fyDq"),h=r("ylqs"),p=r("K0xU"),v=r("N8g3"),y=r("OnI7"),d=r("1MBn"),g=r("EWmC"),b=r("y3w9"),m=r("0/R4"),_=r("aCFj"),k=r("apmT"),w=r("RjD/"),x=r("Kuth"),S=r("e7yV"),F=r("EemH"),T=r("hswa"),O=r("DVgA"),j=F.f,M=T.f,K=S.f,U=e.Symbol,X=e.JSON,Z=X&&X.stringify,z=p("_hidden"),E=p("toPrimitive"),D={}.propertyIsEnumerable,A=s("symbol-registry"),L=s("symbols"),I=s("op-symbols"),P=Object.prototype,C="function"==typeof U,q=e.QObject,V=!q||!q.prototype||!q.prototype.findChild,R=i&&f(function(){return 7!=x(M({},"a",{get:function(){return M(this,"a",{value:7}).a}})).a})?function(t,n,r){var e=j(P,n);e&&delete P[n],M(t,n,r),e&&t!==P&&M(P,n,e)}:M,G=function(t){var n=L[t]=x(U.prototype);return n._k=t,n},J=C&&"symbol"==typeof U.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof U},Y=function(t,n,r){return t===P&&Y(I,n,r),b(t),n=k(n,!0),b(r),o(L,n)?(r.enumerable?(o(t,z)&&t[z][n]&&(t[z][n]=!1),r=x(r,{enumerable:w(0,!1)})):(o(t,z)||M(t,z,w(1,{})),t[z][n]=!0),R(t,n,r)):M(t,n,r)},H=function(t,n){b(t);for(var r,e=d(n=_(n)),o=0,i=e.length;i>o;)Y(t,r=e[o++],n[r]);return t},N=function(t){var n=D.call(this,t=k(t,!0));return!(this===P&&o(L,t)&&!o(I,t))&&(!(n||!o(this,t)||!o(L,t)||o(this,z)&&this[z][t])||n)},W=function(t,n){if(t=_(t),n=k(n,!0),t!==P||!o(L,n)||o(I,n)){var r=j(t,n);return!r||!o(L,n)||o(t,z)&&t[z][n]||(r.enumerable=!0),r}},B=function(t){for(var n,r=K(_(t)),e=[],i=0;r.length>i;)o(L,n=r[i++])||n==z||n==a||e.push(n);return e},Q=function(t){for(var n,r=t===P,e=K(r?I:_(t)),i=[],u=0;e.length>u;)!o(L,n=e[u++])||r&&!o(P,n)||i.push(L[n]);return i};C||(c((U=function(){if(this instanceof U)throw TypeError("Symbol is not a constructor!");var t=h(arguments.length>0?arguments[0]:void 0),n=function(r){this===P&&n.call(I,r),o(this,z)&&o(this[z],t)&&(this[z][t]=!1),R(this,t,w(1,r))};return i&&V&&R(P,t,{configurable:!0,set:n}),G(t)}).prototype,"toString",function(){return this._k}),F.f=W,T.f=Y,r("kJMx").f=S.f=B,r("UqcF").f=N,r("JiEa").f=Q,i&&!r("LQAc")&&c(P,"propertyIsEnumerable",N,!0),v.f=function(t){return G(p(t))}),u(u.G+u.W+u.F*!C,{Symbol:U});for(var $="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),tt=0;$.length>tt;)p($[tt++]);for(var nt=O(p.store),rt=0;nt.length>rt;)y(nt[rt++]);u(u.S+u.F*!C,"Symbol",{for:function(t){return o(A,t+="")?A[t]:A[t]=U(t)},keyFor:function(t){if(!J(t))throw TypeError(t+" is not a symbol!");for(var n in A)if(A[n]===t)return n},useSetter:function(){V=!0},useSimple:function(){V=!1}}),u(u.S+u.F*!C,"Object",{create:function(t,n){return void 0===n?x(t):H(x(t),n)},defineProperty:Y,defineProperties:H,getOwnPropertyDescriptor:W,getOwnPropertyNames:B,getOwnPropertySymbols:Q}),X&&u(u.S+u.F*(!C||f(function(){var t=U();return"[null]"!=Z([t])||"{}"!=Z({a:t})||"{}"!=Z(Object(t))})),"JSON",{stringify:function(t){for(var n,r,e=[t],o=1;arguments.length>o;)e.push(arguments[o++]);if(r=n=e[1],(m(n)||void 0!==t)&&!J(t))return g(n)||(n=function(t,n){if("function"==typeof r&&(n=r.call(this,t,n)),!J(n))return n}),e[1]=n,Z.apply(X,e)}}),U.prototype[E]||r("Mukb")(U.prototype,E,U.prototype.valueOf),l(U,"Symbol"),l(Math,"Math",!0),l(e.JSON,"JSON",!0)},jqX0:function(t,n,r){var e=r("XKFU"),o=r("jtBr");e(e.P+e.F*(Date.prototype.toISOString!==o),"Date",{toISOString:o})},jtBr:function(t,n,r){"use strict";var e=r("eeVq"),o=Date.prototype.getTime,i=Date.prototype.toISOString,u=function(t){return t>9?t:"0"+t};t.exports=e(function(){return"0385-07-25T07:06:39.999Z"!=i.call(new Date(-5e13-1))})||!e(function(){i.call(new Date(NaN))})?function(){if(!isFinite(o.call(this)))throw RangeError("Invalid time value");var t=this,n=t.getUTCFullYear(),r=t.getUTCMilliseconds(),e=n<0?"-":n>9999?"+":"";return e+("00000"+Math.abs(n)).slice(e?-6:-4)+"-"+u(t.getUTCMonth()+1)+"-"+u(t.getUTCDate())+"T"+u(t.getUTCHours())+":"+u(t.getUTCMinutes())+":"+u(t.getUTCSeconds())+"."+(r>99?r:"0"+u(r))+"Z"}:i},kJMx:function(t,n,r){var e=r("zhAb"),o=r("4R4u").concat("length","prototype");n.f=Object.getOwnPropertyNames||function(t){return e(t,o)}},kcoS:function(t,n,r){var e=r("lvtm"),o=Math.pow,i=o(2,-52),u=o(2,-23),c=o(2,127)*(2-u),a=o(2,-126);t.exports=Math.fround||function(t){var n,r,o=Math.abs(t),f=e(t);return oc||r!=r?f*(1/0):f*r}},knhD:function(t,n,r){var e=r("XKFU");e(e.S,"Number",{MIN_SAFE_INTEGER:-9007199254740991})},l0Rn:function(t,n,r){"use strict";var e=r("RYi7"),o=r("vhPU");t.exports=function(t){var n=String(o(this)),r="",i=e(t);if(i<0||i==1/0)throw RangeError("Count can't be negative");for(;i>0;(i>>>=1)&&(n+=n))1&i&&(r+=n);return r}},lvtm:function(t,n){t.exports=Math.sign||function(t){return 0==(t=+t)||t!=t?t:t<0?-1:1}},m0Pp:function(t,n,r){var e=r("2OiF");t.exports=function(t,n,r){if(e(t),void 0===n)return t;switch(r){case 1:return function(r){return t.call(n,r)};case 2:return function(r,e){return t.call(n,r,e)};case 3:return function(r,e,o){return t.call(n,r,e,o)}}return function(){return t.apply(n,arguments)}}},mGWK:function(t,n,r){"use strict";var e=r("XKFU"),o=r("aCFj"),i=r("RYi7"),u=r("ne8i"),c=[].lastIndexOf,a=!!c&&1/[1].lastIndexOf(1,-0)<0;e(e.P+e.F*(a||!r("LyE8")(c)),"Array",{lastIndexOf:function(t){if(a)return c.apply(this,arguments)||0;var n=o(this),r=u(n.length),e=r-1;for(arguments.length>1&&(e=Math.min(e,i(arguments[1]))),e<0&&(e=r+e);e>=0;e--)if(e in n&&n[e]===t)return e||0;return-1}})},mYba:function(t,n,r){var e=r("aCFj"),o=r("EemH").f;r("Xtr8")("getOwnPropertyDescriptor",function(){return function(t,n){return o(e(t),n)}})},mura:function(t,n,r){var e=r("0/R4"),o=r("Z6vF").onFreeze;r("Xtr8")("preventExtensions",function(t){return function(n){return t&&e(n)?t(o(n)):n}})},nBIS:function(t,n,r){var e=r("0/R4"),o=Math.floor;t.exports=function(t){return!e(t)&&isFinite(t)&&o(t)===t}},nGyu:function(t,n,r){var e=r("K0xU")("unscopables"),o=Array.prototype;null==o[e]&&r("Mukb")(o,e,{}),t.exports=function(t){o[e][t]=!0}},nIY7:function(t,n,r){"use strict";r("OGtf")("big",function(t){return function(){return t(this,"big","","")}})},ne8i:function(t,n,r){var e=r("RYi7"),o=Math.min;t.exports=function(t){return t>0?o(e(t),9007199254740991):0}},nh4g:function(t,n,r){t.exports=!r("eeVq")(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},nsiH:function(t,n,r){"use strict";r("OGtf")("fontsize",function(t){return function(n){return t(this,"font","size",n)}})},nx1v:function(t,n,r){r("eM6i"),r("AphP"),r("jqX0"),r("h7Nl"),r("yM4b"),t.exports=Date},nzyx:function(t,n,r){var e=r("XKFU"),o=r("LVwc");e(e.S+e.F*(o!=Math.expm1),"Math",{expm1:o})},oDIu:function(t,n,r){"use strict";var e=r("XKFU"),o=r("AvRE")(!1);e(e.P,"String",{codePointAt:function(t){return o(this,t)}})},"oka+":function(t,n,r){r("GNAe"),t.exports=r("g3g5").parseInt},pIFo:function(t,n,r){"use strict";var e=r("y3w9"),o=r("S/j/"),i=r("ne8i"),u=r("RYi7"),c=r("A5AN"),a=r("Xxuz"),f=Math.max,s=Math.min,l=Math.floor,h=/\$([$&`']|\d\d?|<[^>]*>)/g,p=/\$([$&`']|\d\d?)/g;r("IU+Z")("replace",2,function(t,n,r,v){return[function(e,o){var i=t(this),u=null==e?void 0:e[n];return void 0!==u?u.call(e,i,o):r.call(String(i),e,o)},function(t,n){var o=v(r,t,this,n);if(o.done)return o.value;var l=e(t),h=String(this),p="function"==typeof n;p||(n=String(n));var d=l.global;if(d){var g=l.unicode;l.lastIndex=0}for(var b=[];;){var m=a(l,h);if(null===m)break;if(b.push(m),!d)break;""===String(m[0])&&(l.lastIndex=c(h,i(l.lastIndex),g))}for(var _,k="",w=0,x=0;x=w&&(k+=h.slice(w,F)+K,w=F+S.length)}return k+h.slice(w)}];function y(t,n,e,i,u,c){var a=e+t.length,f=i.length,s=p;return void 0!==u&&(u=o(u),s=h),r.call(c,s,function(r,o){var c;switch(o.charAt(0)){case"$":return"$";case"&":return t;case"`":return n.slice(0,e);case"'":return n.slice(a);case"<":c=u[o.slice(1,-1)];break;default:var s=+o;if(0===s)return o;if(s>f){var h=l(s/10);return 0===h?o:h<=f?void 0===i[h-1]?o.charAt(1):i[h-1]+o.charAt(1):o}c=i[s-1]}return void 0===c?"":c})}})},"pp/T":function(t,n,r){var e=r("XKFU");e(e.S,"Math",{log2:function(t){return Math.log(t)/Math.LN2}})},qKs0:function(t,n,r){r("Btvt"),r("XfO3"),r("rGqo"),r("9AAn"),t.exports=r("g3g5").Map},qncB:function(t,n,r){var e=r("XKFU"),o=r("vhPU"),i=r("eeVq"),u=r("/e88"),c="["+u+"]",a=RegExp("^"+c+c+"*"),f=RegExp(c+c+"*$"),s=function(t,n,r){var o={},c=i(function(){return!!u[t]()||"\u200b\x85"!="\u200b\x85"[t]()}),a=o[t]=c?n(l):u[t];r&&(o[r]=a),e(e.P+e.F*c,"String",o)},l=s.trim=function(t,n){return t=String(o(t)),1&n&&(t=t.replace(a,"")),2&n&&(t=t.replace(f,"")),t};t.exports=s},quPj:function(t,n,r){var e=r("0/R4"),o=r("LZWt"),i=r("K0xU")("match");t.exports=function(t){var n;return e(t)&&(void 0!==(n=t[i])?!!n:"RegExp"==o(t))}},rGqo:function(t,n,r){for(var e=r("yt8O"),o=r("DVgA"),i=r("KroJ"),u=r("dyZX"),c=r("Mukb"),a=r("hPIQ"),f=r("K0xU"),s=f("iterator"),l=f("toStringTag"),h=a.Array,p={CSSRuleList:!0,CSSStyleDeclaration:!1,CSSValueList:!1,ClientRectList:!1,DOMRectList:!1,DOMStringList:!1,DOMTokenList:!0,DataTransferItemList:!1,FileList:!1,HTMLAllCollection:!1,HTMLCollection:!1,HTMLFormElement:!1,HTMLSelectElement:!1,MediaList:!0,MimeTypeArray:!1,NamedNodeMap:!1,NodeList:!0,PaintRequestList:!1,Plugin:!1,PluginArray:!1,SVGLengthList:!1,SVGNumberList:!1,SVGPathSegList:!1,SVGPointList:!1,SVGStringList:!1,SVGTransformList:!1,SourceBufferList:!1,StyleSheetList:!0,TextTrackCueList:!1,TextTrackList:!1,TouchList:!1},v=o(p),y=0;y1?arguments[1]:void 0,e=o(n.length),c=void 0===r?e:Math.min(o(r),e),a=String(t);return u?u.call(n,a,c):n.slice(c-a.length,c)===a}})},s5qY:function(t,n,r){var e=r("0/R4");t.exports=function(t,n){if(!e(t)||t._t!==n)throw TypeError("Incompatible receiver, "+n+" required!");return t}},sMXx:function(t,n,r){"use strict";var e=r("Ugos");r("XKFU")({target:"RegExp",proto:!0,forced:e!==/./.exec},{exec:e})},sbF8:function(t,n,r){var e=r("XKFU"),o=r("nBIS"),i=Math.abs;e(e.S,"Number",{isSafeInteger:function(t){return o(t)&&i(t)<=9007199254740991}})},tUrg:function(t,n,r){"use strict";r("OGtf")("link",function(t){return function(n){return t(this,"a","href",n)}})},"tyy+":function(t,n,r){var e=r("XKFU"),o=r("11IZ");e(e.G+e.F*(parseFloat!=o),{parseFloat:o})},upKx:function(t,n,r){"use strict";var e=r("S/j/"),o=r("d/Gc"),i=r("ne8i");t.exports=[].copyWithin||function(t,n){var r=e(this),u=i(r.length),c=o(t,u),a=o(n,u),f=arguments.length>2?arguments[2]:void 0,s=Math.min((void 0===f?u:o(f,u))-a,u-c),l=1;for(a0;)a in r?r[c]=r[a]:delete r[c],c+=l,a+=l;return r}},vhPU:function(t,n){t.exports=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t}},vqGA:function(t,n,r){r("ioFf"),r("Btvt"),t.exports=r("g3g5").Symbol},vvmO:function(t,n,r){var e=r("LZWt");t.exports=function(t,n){if("number"!=typeof t&&"Number"!=e(t))throw TypeError(n);return+t}},w2a5:function(t,n,r){var e=r("aCFj"),o=r("ne8i"),i=r("d/Gc");t.exports=function(t){return function(n,r,u){var c,a=e(n),f=o(a.length),s=i(u,f);if(t&&r!=r){for(;f>s;)if((c=a[s++])!=c)return!0}else for(;f>s;s++)if((t||s in a)&&a[s]===r)return t||s||0;return!t&&-1}}},wmvG:function(t,n,r){"use strict";var e=r("hswa").f,o=r("Kuth"),i=r("3Lyj"),u=r("m0Pp"),c=r("9gX7"),a=r("SlkY"),f=r("Afnz"),s=r("1TsA"),l=r("elZq"),h=r("nh4g"),p=r("Z6vF").fastKey,v=r("s5qY"),y=h?"_s":"size",d=function(t,n){var r,e=p(n);if("F"!==e)return t._i[e];for(r=t._f;r;r=r.n)if(r.k==n)return r};t.exports={getConstructor:function(t,n,r,f){var s=t(function(t,e){c(t,s,n,"_i"),t._t=n,t._i=o(null),t._f=void 0,t._l=void 0,t[y]=0,null!=e&&a(e,r,t[f],t)});return i(s.prototype,{clear:function(){for(var t=v(this,n),r=t._i,e=t._f;e;e=e.n)e.r=!0,e.p&&(e.p=e.p.n=void 0),delete r[e.i];t._f=t._l=void 0,t[y]=0},delete:function(t){var r=v(this,n),e=d(r,t);if(e){var o=e.n,i=e.p;delete r._i[e.i],e.r=!0,i&&(i.n=o),o&&(o.p=i),r._f==e&&(r._f=o),r._l==e&&(r._l=i),r[y]--}return!!e},forEach:function(t){v(this,n);for(var r,e=u(t,arguments.length>1?arguments[1]:void 0,3);r=r?r.n:this._f;)for(e(r.v,r.k,this);r&&r.r;)r=r.p},has:function(t){return!!d(v(this,n),t)}}),h&&e(s.prototype,"size",{get:function(){return v(this,n)[y]}}),s},def:function(t,n,r){var e,o,i=d(t,n);return i?i.v=r:(t._l=i={i:o=p(n,!0),k:n,v:r,p:e=t._l,n:void 0,r:!1},t._f||(t._f=i),e&&(e.n=i),t[y]++,"F"!==o&&(t._i[o]=i)),t},getEntry:d,setStrong:function(t,n,r){f(t,n,function(t,r){this._t=v(t,n),this._k=r,this._l=void 0},function(){for(var t=this._k,n=this._l;n&&n.r;)n=n.p;return this._t&&(this._l=n=n?n.n:this._t._f)?s(0,"keys"==t?n.k:"values"==t?n.v:[n.k,n.v]):(this._t=void 0,s(1))},r?"entries":"values",!r,!0),l(n)}}},x8Yj:function(t,n,r){var e=r("XKFU"),o=r("LVwc"),i=Math.exp;e(e.S,"Math",{tanh:function(t){var n=o(t=+t),r=o(-t);return n==1/0?1:r==1/0?-1:(n-r)/(i(t)+i(-t))}})},x8ZO:function(t,n,r){var e=r("XKFU"),o=Math.abs;e(e.S,"Math",{hypot:function(t,n){for(var r,e,i=0,u=0,c=arguments.length,a=0;u0?(e=r/a)*e:r;return a===1/0?1/0:a*Math.sqrt(i)}})},xfY5:function(t,n,r){"use strict";var e=r("dyZX"),o=r("aagx"),i=r("LZWt"),u=r("Xbzi"),c=r("apmT"),a=r("eeVq"),f=r("kJMx").f,s=r("EemH").f,l=r("hswa").f,h=r("qncB").trim,p=e.Number,v=p,y=p.prototype,d="Number"==i(r("Kuth")(y)),g="trim"in String.prototype,b=function(t){var n=c(t,!1);if("string"==typeof n&&n.length>2){var r,e,o,i=(n=g?n.trim():h(n,3)).charCodeAt(0);if(43===i||45===i){if(88===(r=n.charCodeAt(2))||120===r)return NaN}else if(48===i){switch(n.charCodeAt(1)){case 66:case 98:e=2,o=49;break;case 79:case 111:e=8,o=55;break;default:return+n}for(var u,a=n.slice(2),f=0,s=a.length;fo)return NaN;return parseInt(a,e)}}return+n};if(!p(" 0o1")||!p("0b1")||p("+0x1")){p=function(t){var n=arguments.length<1?0:t,r=this;return r instanceof p&&(d?a(function(){y.valueOf.call(r)}):"Number"!=i(r))?u(new v(b(n)),r,p):b(n)};for(var m,_=r("nh4g")?f(v):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger".split(","),k=0;_.length>k;k++)o(v,m=_[k])&&!o(p,m)&&l(p,m,s(v,m));p.prototype=y,y.constructor=p,r("KroJ")(e,"Number",p)}},xpql:function(t,n,r){t.exports=!r("nh4g")&&!r("eeVq")(function(){return 7!=Object.defineProperty(r("Iw71")("div"),"a",{get:function(){return 7}}).a})},y3w9:function(t,n,r){var e=r("0/R4");t.exports=function(t){if(!e(t))throw TypeError(t+" is not an object!");return t}},yM4b:function(t,n,r){var e=r("K0xU")("toPrimitive"),o=Date.prototype;e in o||r("Mukb")(o,e,r("g4EE"))},ylqs:function(t,n){var r=0,e=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++r+e).toString(36))}},yt8O:function(t,n,r){"use strict";var e=r("nGyu"),o=r("1TsA"),i=r("hPIQ"),u=r("aCFj");t.exports=r("Afnz")(Array,"Array",function(t,n){this._t=u(t),this._i=0,this._k=n},function(){var t=this._t,n=this._k,r=this._i++;return!t||r>=t.length?(this._t=void 0,o(1)):o(0,"keys"==n?r:"values"==n?t[r]:[r,t[r]])},"values"),i.Arguments=i.Array,e("keys"),e("values"),e("entries")},z2o2:function(t,n,r){var e=r("0/R4"),o=r("Z6vF").onFreeze;r("Xtr8")("seal",function(t){return function(n){return t&&e(n)?t(o(n)):n}})},zRwo:function(t,n,r){var e=r("6FMO");t.exports=function(t,n){return new(e(t))(n)}},zhAb:function(t,n,r){var e=r("aagx"),o=r("aCFj"),i=r("w2a5")(!1),u=r("YTvA")("IE_PROTO");t.exports=function(t,n){var r,c=o(t),a=0,f=[];for(r in c)r!=u&&e(c,r)&&f.push(r);for(;n.length>a;)e(c,r=n[a++])&&(~i(f,r)||f.push(r));return f}}},[[1,0]]]); + +(window.webpackJsonp=window.webpackJsonp||[]).push([[1],{0:function(n,t,e){n.exports=e("zUnb")},crnd:function(n,t){function e(n){return Promise.resolve().then(function(){var t=new Error("Cannot find module '"+n+"'");throw t.code="MODULE_NOT_FOUND",t})}e.keys=function(){return[]},e.resolve=e,n.exports=e,e.id="crnd"},zUnb:function(n,t,e){"use strict";e.r(t);var r=function(n,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,t){n.__proto__=t}||function(n,t){for(var e in t)t.hasOwnProperty(e)&&(n[e]=t[e])})(n,t)};function o(n,t){function e(){this.constructor=n}r(n,t),n.prototype=null===t?Object.create(t):(e.prototype=t.prototype,new e)}var i=function(){return(i=Object.assign||function(n){for(var t,e=1,r=arguments.length;e=0;u--)(o=n[u])&&(l=(i<3?o(l):i>3?o(t,e,l):o(t,e))||l);return i>3&&l&&Object.defineProperty(t,e,l),l}function u(n,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(n,t)}function s(n){var t="function"==typeof Symbol&&n[Symbol.iterator],e=0;return t?t.call(n):{next:function(){return n&&e>=n.length&&(n=void 0),{value:n&&n[e++],done:!n}}}}function a(n,t){var e="function"==typeof Symbol&&n[Symbol.iterator];if(!e)return n;var r,o,i=e.call(n),l=[];try{for(;(void 0===t||t-- >0)&&!(r=i.next()).done;)l.push(r.value)}catch(u){o={error:u}}finally{try{r&&!r.done&&(e=i.return)&&e.call(i)}finally{if(o)throw o.error}}return l}function c(){for(var n=[],t=0;t0?this._next(t.shift()):0===this.active&&this.hasCompleted&&this.destination.complete()},t}(W);function rn(n){return n}function on(){return function(n){return n.lift(new ln(n))}}var ln=function(){function n(n){this.connectable=n}return n.prototype.call=function(n,t){var e=this.connectable;e._refCount++;var r=new un(n,e),o=t.subscribe(r);return r.closed||(r.connection=e.connect()),o},n}(),un=function(n){function t(t,e){var r=n.call(this,t)||this;return r.connectable=e,r}return o(t,n),t.prototype._unsubscribe=function(){var n=this.connectable;if(n){this.connectable=null;var t=n._refCount;if(t<=0)this.connection=null;else if(n._refCount=t-1,t>1)this.connection=null;else{var e=this.connection,r=n._connection;this.connection=null,!r||e&&r!==e||r.unsubscribe()}}else this.connection=null},t}(S),sn=function(n){function t(t,e){var r=n.call(this)||this;return r.source=t,r.subjectFactory=e,r._refCount=0,r._isComplete=!1,r}return o(t,n),t.prototype._subscribe=function(n){return this.getSubject().subscribe(n)},t.prototype.getSubject=function(){var n=this._subject;return n&&!n.isStopped||(this._subject=this.subjectFactory()),this._subject},t.prototype.connect=function(){var n=this._connection;return n||(this._isComplete=!1,(n=this._connection=new _).add(this.source.subscribe(new cn(this.getSubject(),this))),n.closed?(this._connection=null,n=_.EMPTY):this._connection=n),n},t.prototype.refCount=function(){return on()(this)},t}(I).prototype,an={operator:{value:null},_refCount:{value:0,writable:!0},_subject:{value:null,writable:!0},_connection:{value:null,writable:!0},_subscribe:{value:sn._subscribe},_isComplete:{value:sn._isComplete,writable:!0},getSubject:{value:sn.getSubject},connect:{value:sn.connect},refCount:{value:sn.refCount}},cn=function(n){function t(t,e){var r=n.call(this,t)||this;return r.connectable=e,r}return o(t,n),t.prototype._error=function(t){this._unsubscribe(),n.prototype._error.call(this,t)},t.prototype._complete=function(){this.connectable._isComplete=!0,this._unsubscribe(),n.prototype._complete.call(this)},t.prototype._unsubscribe=function(){var n=this.connectable;if(n){this.connectable=null;var t=n._connection;n._refCount=0,n._subject=null,n._connection=null,t&&t.unsubscribe()}},t}(V);function fn(){return new H}function hn(n){for(var t in n)if(n[t]===hn)return t;throw Error("Could not find renamed property on target object.")}var pn=hn({ngComponentDef:hn}),dn=hn({ngInjectableDef:hn}),gn=hn({ngInjectorDef:hn}),vn=hn({ngModuleDef:hn}),yn=hn({__NG_ELEMENT_ID__:hn});function mn(n){return{providedIn:n.providedIn||null,factory:n.factory,value:void 0}}function bn(n){return n.hasOwnProperty(dn)?n[dn]:null}function _n(n){return n.hasOwnProperty(gn)?n[gn]:null}var wn=function(){function n(n,t){this._desc=n,this.ngMetadataName="InjectionToken",this.ngInjectableDef=void 0!==t?mn({providedIn:t.providedIn||"root",factory:t.factory}):void 0}return n.prototype.toString=function(){return"InjectionToken "+this._desc},n}(),Cn="__parameters__";function En(n,t,e){var r=function(n){return function(){for(var t=[],e=0;e=Yn?e:e[ot]}function Nt(n){return n[St]}function Dt(n){var t=Nt(n);return t?Array.isArray(t)?t:t.lViewData:null}function Mt(n){return 32767&n}function Vt(n,t){for(var e=n>>16,r=t;e>0;)r=r[pt],e--;return r}var Ht,Rt,jt,Lt,zt,Ft,Bt,Ut,Gt=("undefined"!=typeof requestAnimationFrame&&requestAnimationFrame||setTimeout).bind(Sn);function Zt(){return Ht}function qt(){return Rt}function $t(){return jt}function Qt(n){jt=n}function Wt(n,t){jt=n,Ut=t}function Kt(){return Lt}function Jt(n){Lt=n}function Yt(){return zt}function Xt(){return Bt}function ne(){return Ut}var te=!1;function ee(){return te}function re(n){te=n}var oe=!0;function ie(n){oe=n}function le(n,t){var e=Ut;return zt=n&&n[Xn],Bt=n&&1==(1&n[nt]),oe=n&&zt.firstTemplatePass,Ht=n&&n[ct],jt=t,Lt=!0,Ut=n,e&&(e[rt]=Ft),Ft=n&&n[rt],e}function ue(n,t){t||(te||yt(Ut,zt.viewHooks,zt.viewCheckHooks,Bt),Ut[nt]&=-6),Ut[nt]|=16,Ut[lt]=zt.bindingStartIndex,le(n,null)}var se=!1;function ae(n){var t=se;return se=n,t}var ce=255,fe=0;function he(n,t){var e=de(n,t);if(-1!==e)return e;var r=t[Xn];r.firstTemplatePass&&(n.injectorIndex=t.length,pe(r.data,n),pe(t,null),pe(r.blueprint,null));var o=ge(n,t),i=Mt(o),l=Vt(o,t),u=n.injectorIndex;if(o!==Wn)for(var s=l[Xn].data,a=0;a<8;a++)t[u+a]=l[i+a]|s[i+a];return t[u+$n]=o,u}function pe(n,t){n.push(0,0,0,0,0,0,0,0,t)}function de(n,t){return-1===n.injectorIndex||n.parent&&n.parent.injectorIndex===n.injectorIndex||null==t[n.injectorIndex+$n]?-1:n.injectorIndex}function ge(n,t){if(n.parent&&-1!==n.parent.injectorIndex)return n.parent.injectorIndex;for(var e=t[it],r=1;e&&-1===e.injectorIndex;)e=(t=t[pt])[it],r++;return e?e.injectorIndex|r<<16|(e&&3===e.type?32768:0):-1}var ve={};function ye(n,t,e,r){var o=t[Xn],i=o.data[n+qn],l=i.flags,u=i.providerIndexes,s=o.data,a=!1;(null==r&&function(n){return 4096==(4096&n.flags)}(i)&&se||null!=r&&r!=o&&(null==o.node||3===o.node.type))&&(a=!0);for(var c=65535&u,f=l>>16,h=4095&l,p=a?c:c+(u>>16);p=f&&d.type===e)return me(s,t,p,i)}return ve}function me(n,t,e,r){var o,i=t[e];if(null!=(o=i)&&"object"==typeof o&&Object.getPrototypeOf(o)==Jn){var l=i;if(l.resolving)throw new Error("Circular dep for "+At(n[e]));var u=ae(l.canSeeViewProviders);l.resolving=!0;var s=void 0;l.injectImpl&&(s=Bn(l.injectImpl));var a=$t(),c=ne();Wt(r,t);try{i=t[e]=l.factory(null,n,t,r)}finally{l.injectImpl&&Bn(s),ae(u),l.resolving=!1,Wt(a,c)}}return i}function be(n,t,e){var r=64&n,o=32&n;return!!((128&n?r?o?e[t+7]:e[t+6]:o?e[t+5]:e[t+4]:r?o?e[t+3]:e[t+2]:o?e[t+1]:e[t])&1< ");else if("object"==typeof t){var o=[];for(var i in t)if(t.hasOwnProperty(i)){var l=t[i];o.push(i+":"+("string"==typeof l?JSON.stringify(l):Dn(l)))}r="{"+o.join(", ")+"}"}return"StaticInjectorError"+(e?"("+e+")":"")+"["+r+"]: "+n.replace(ze,"\n ")}function Ze(n,t){return new Error(Ge(n,t))}var qe=function(){return function(){}}(),$e=function(){return function(){}}(),Qe="ngProjectAs";function We(n){return!!n.listen}var Ke={createRenderer:function(n,t){return document}},Je=[];function Ye(n){for(var t=n[it];t&&2===t.type;)t=(n=n[tt])[it];return n}function Xe(n,t,e,r,o){0===n?We(t)?t.insertBefore(e,r,o):e.insertBefore(r,o,!0):1===n?We(t)?t.removeChild(e,r):e.removeChild(r):2===n&&t.destroyNode(r)}function nr(n){var t=n[Xn].childIndex;return-1===t?null:n[t]}function tr(n,t){var e;return n.length>=Yn&&(e=n[it])&&2===e.type?function(t,e){if(-1===t.index){var r=n[ht];return r>-1?n[tt][r]:null}return n[tt][t.parent.index]}(e):n[tt]===t?null:n[tt]}function er(n){if(n.length>=Yn){var t=n;!function(n){var t=n[Xn].cleanup;if(null!=t){for(var e=0;e=Yn?t[Xn].childIndex>-1&&(e=nr(t)):t[Ot].length&&(e=t[Ot][0]),null==e){for(;t&&!t[et]&&t!==n;)er(t),t=tr(t,n);er(t||n),e=t&&t[et]}t=e}}(n),n[nt]|=32},n.prototype.onDestroy=function(n){var t,e;e=n,function(n){return n[ut]||(n[ut]=[])}(t=this._view).push(e),t[Xn].firstTemplatePass&&function(n){return n[Xn].cleanup||(n[Xn].cleanup=[])}(t).push(t[ut].length-1,null)},n.prototype.markForCheck=function(){!function(n){for(var t=n;t&&!(64&t[nt]);)t[nt]|=4,t=t[tt];var e,r,o;t[nt]|=4,o=0===(e=t[st]).flags,e.flags|=1,o&&e.clean==or&&(e.clean=new Promise(function(n){return r=n}),e.scheduler(function(){if(1&e.flags&&(e.flags&=-2,mr(e)),2&e.flags){e.flags&=-3;var n=e.playerHandler;n&&n.flushPlayers()}e.clean=or,r(null)}))}(this._view)},n.prototype.detach=function(){this._view[nt]&=-9},n.prototype.reattach=function(){this._view[nt]|=8},n.prototype.detectChanges=function(){var n=qt();n.begin&&n.begin(),br(this.context),n.end&&n.end()},n.prototype.checkNoChanges=function(){!function(n){re(!0);try{br(n)}finally{re(!1)}}(this.context)},n.prototype.attachToViewContainerRef=function(n){this._viewContainerRef=n},n.prototype.detachFromAppRef=function(){this._appRef=null},n.prototype.attachToAppRef=function(n){this._appRef=n},n.prototype._lookUpContext=function(){return this._context=this._view[tt][this._componentIndex]},n}());function Or(n,t,e,r,o){var i=e[Xn],l=function(n,t,e){var r=$t();n.firstTemplatePass&&(e.providersResolver&&e.providersResolver(e),function(n,t,e){var o=-(r.index-Yn),i=n.data.length-(65535&r.providerIndexes);(n.expandoInstructions||(n.expandoInstructions=[])).push(o,i,1)}(n),function(n,t,e,r){n.data.push(e);var o=new Kn(r,function(n){return null!==n.template}(e),null);n.blueprint.push(o),t.push(o),function(n,t){n.expandoInstructions.push(t.hostBindings||Ee),t.hostVars&&n.expandoInstructions.push(t.hostVars)}(n,e)}(n,t,e,e.factory));var o=me(n.data,t,t.length-1,r);return function(n,t,e,r){var o=It(t,n);Ce(e,n),o&&Ce(o,n),null!=r.attributes&&3==t.type&&function(n,t){for(var e=Zt(),r=We(e),o=0;o>16,r=e+(4095&n),o=e;o',!this.inertBodyElement.querySelector||this.inertBodyElement.querySelector("svg")?(this.inertBodyElement.innerHTML='

',this.getInertBodyElement=this.inertBodyElement.querySelector&&this.inertBodyElement.querySelector("svg img")&&function(){try{return!!window.DOMParser}catch(n){return!1}}()?this.getInertBodyElement_DOMParser:this.getInertBodyElement_InertDocument):this.getInertBodyElement=this.getInertBodyElement_XHR}return n.prototype.getInertBodyElement_XHR=function(n){n=""+n+"";try{n=encodeURI(n)}catch(r){return null}var t=new XMLHttpRequest;t.responseType="document",t.open("GET","data:text/html;charset=utf-8,"+n,!1),t.send(void 0);var e=t.response.body;return e.removeChild(e.firstChild),e},n.prototype.getInertBodyElement_DOMParser=function(n){n=""+n+"";try{var t=(new window.DOMParser).parseFromString(n,"text/html").body;return t.removeChild(t.firstChild),t}catch(e){return null}},n.prototype.getInertBodyElement_InertDocument=function(n){var t=this.inertDocument.createElement("template");return"content"in t?(t.innerHTML=n,t):(this.inertBodyElement.innerHTML=n,this.defaultDoc.documentMode&&this.stripCustomNsAttrs(this.inertBodyElement),this.inertBodyElement)},n.prototype.stripCustomNsAttrs=function(n){for(var t=n.attributes,e=t.length-1;0"),!0},n.prototype.endElement=function(n){var t=n.nodeName.toLowerCase();Oo.hasOwnProperty(t)&&!wo.hasOwnProperty(t)&&(this.buf.push(""))},n.prototype.chars=function(n){this.buf.push(No(n))},n.prototype.checkClobberedElement=function(n,t){if(t&&(n.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_CONTAINED_BY)===Node.DOCUMENT_POSITION_CONTAINED_BY)throw new Error("Failed to sanitize html because the element is clobbered: "+n.outerHTML);return t},n}(),Io=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,Po=/([^\#-~ |!])/g;function No(n){return n.replace(/&/g,"&").replace(Io,function(n){return"&#"+(1024*(n.charCodeAt(0)-55296)+(n.charCodeAt(1)-56320)+65536)+";"}).replace(Po,function(n){return"&#"+n.charCodeAt(0)+";"}).replace(//g,">")}function Do(n){return"content"in n&&function(n){return n.nodeType===Node.ELEMENT_NODE&&"TEMPLATE"===n.nodeName}(n)?n.content:null}var Mo={provide:Zr,useFactory:function(){return new eo},deps:[]},Vo=function(n){function t(t,e){var r=n.call(this)||this;return r._bootstrapComponents=[],r.destroyCbs=[],r._bootstrapComponents=(t[vn]||null).bootstrap,r.injector=function(n,t,e){return void 0===t&&(t=null),void 0===e&&(e=null),t=t||Dr(),new Mr(n,e,t)}(t,e,[Mo,{provide:qe,useValue:r}]),r.instance=r.injector.get(t),r.componentFactoryResolver=new eo,r}return o(t,n),t.prototype.destroy=function(){this.destroyCbs.forEach(function(n){return n()}),this.destroyCbs=null},t.prototype.onDestroy=function(n){this.destroyCbs.push(n)},t}(qe);!function(n){function t(t){var e=n.call(this)||this;return e.moduleType=t,e}o(t,n),t.prototype.create=function(n){return new Vo(this.moduleType,n)}}($e);var Ho=function(n){function t(t){void 0===t&&(t=!1);var e=n.call(this)||this;return e.__isAsync=t,e}return o(t,n),t.prototype.emit=function(t){n.prototype.next.call(this,t)},t.prototype.subscribe=function(t,e,r){var o,i=function(n){return null},l=function(){return null};t&&"object"==typeof t?(o=this.__isAsync?function(n){setTimeout(function(){return t.next(n)})}:function(n){t.next(n)},t.error&&(i=this.__isAsync?function(n){setTimeout(function(){return t.error(n)})}:function(n){t.error(n)}),t.complete&&(l=this.__isAsync?function(){setTimeout(function(){return t.complete()})}:function(){t.complete()})):(o=this.__isAsync?function(n){setTimeout(function(){return t(n)})}:function(n){t(n)},e&&(i=this.__isAsync?function(n){setTimeout(function(){return e(n)})}:function(n){e(n)}),r&&(l=this.__isAsync?function(){setTimeout(function(){return r()})}:function(){r()}));var u=n.prototype.subscribe.call(this,o,i,l);return t instanceof _&&t.add(u),u},t}(H),Ro=function(){function n(){}return n.__NG_ELEMENT_ID__=function(){return jo(n,Qr)},n}(),jo=Ee,Lo=function(n){return n[n.NONE=0]="NONE",n[n.HTML=1]="HTML",n[n.STYLE=2]="STYLE",n[n.SCRIPT=3]="SCRIPT",n[n.URL=4]="URL",n[n.RESOURCE_URL=5]="RESOURCE_URL",n}({}),zo=function(){return function(){}}(),Fo=new RegExp("^([-,.\"'%_!# a-zA-Z0-9]+|(?:(?:matrix|translate|scale|rotate|skew|perspective)(?:X|Y|3d)?|(?:rgb|hsl)a?|(?:repeating-)?(?:linear|radial)-gradient|(?:calc|attr))\\([-0-9.%, #a-zA-Z]+\\))$","g"),Bo=/^url\(([^)]+)\)$/;Function,String,String;var Uo="ngDebugContext",Go="ngOriginalError",Zo="ngErrorLogger";function qo(n){return n[Uo]}function $o(n){return n[Go]}function Qo(n){for(var t=[],e=1;e0&&(o=setTimeout(function(){r._callbacks=r._callbacks.filter(function(n){return n.timeoutId!==o}),n(r._didWork,r.getPendingTasks())},t)),this._callbacks.push({doneCb:n,timeoutId:o,updateCb:e})},n.prototype.whenStable=function(n,t,e){if(e&&!this.taskTrackingZone)throw new Error('Task tracking zone is required when passing an update callback to whenStable(). Is "zone.js/dist/task-tracking.js" loaded?');this.addCallback(n,t,e),this._runCallbacksIfReady()},n.prototype.getPendingRequestCount=function(){return this._pendingCount},n.prototype.findProviders=function(n,t,e){return[]},n}(),ki=function(){function n(){this._applications=new Map,Si.addToWindow(this)}return n.prototype.registerApplication=function(n,t){this._applications.set(n,t)},n.prototype.unregisterApplication=function(n){this._applications.delete(n)},n.prototype.unregisterAllApplications=function(){this._applications.clear()},n.prototype.getTestability=function(n){return this._applications.get(n)||null},n.prototype.getAllTestabilities=function(){return Array.from(this._applications.values())},n.prototype.getAllRootElements=function(){return Array.from(this._applications.keys())},n.prototype.findTestabilityInTree=function(n,t){return void 0===t&&(t=!0),Si.findTestabilityInTree(this,n,t)},l([u("design:paramtypes",[])],n)}(),Si=new(function(){function n(){}return n.prototype.addToWindow=function(n){},n.prototype.findTestabilityInTree=function(n,t,e){return null},n}()),Ai=new wn("AllowMultipleToken"),Ti=function(){return function(n,t){this.name=n,this.token=t}}();function Ii(n,t,e){void 0===e&&(e=[]);var r="Platform: "+t,o=new wn(r);return function(t){void 0===t&&(t=[]);var i=Pi();if(!i||i.injector.get(Ai,!1))if(n)n(e.concat(t).concat({provide:o,useValue:!0}));else{var l=e.concat(t).concat({provide:o,useValue:!0});!function(n){if(Ei&&!Ei.destroyed&&!Ei.injector.get(Ai,!1))throw new Error("There can be only one platform. Destroy the previous one to create a new one.");Ei=n.get(Ni);var t=n.get(ri,null);t&&t.forEach(function(n){return n()})}(Ne.create({providers:l,name:r}))}return function(n){var t=Pi();if(!t)throw new Error("No platform exists!");if(!t.injector.get(n,null))throw new Error("A platform with a different configuration has been created. Please destroy it first.");return t}(o)}}function Pi(){return Ei&&!Ei.destroyed?Ei:null}var Ni=function(){function n(n){this._injector=n,this._modules=[],this._destroyListeners=[],this._destroyed=!1}return n.prototype.bootstrapModuleFactory=function(n,t){var e,r=this,o="noop"===(e=t?t.ngZone:void 0)?new xi:("zone.js"===e?void 0:e)||new yi({enableLongStackTrace:ho()}),i=[{provide:yi,useValue:o}];return o.run(function(){var t=Ne.create({providers:i,parent:r.injector,name:n.moduleType.name}),e=n.create(t),l=e.injector.get(Wo,null);if(!l)throw new Error("No ErrorHandler. Is platform module (BrowserModule) included?");return e.onDestroy(function(){return Vi(r._modules,e)}),o.runOutsideAngular(function(){return o.onError.subscribe({next:function(n){l.handleError(n)}})}),function(n,t,o){try{var i=((l=e.injector.get(Xo)).runInitializers(),l.donePromise.then(function(){return r._moduleDoBootstrap(e),e}));return Ko(i)?i.catch(function(e){throw t.runOutsideAngular(function(){return n.handleError(e)}),e}):i}catch(u){throw t.runOutsideAngular(function(){return n.handleError(u)}),u}var l}(l,o)})},n.prototype.bootstrapModule=function(n,t){var e=this;void 0===t&&(t=[]);var r=Di({},t);return function(n,t,e){return n.get(fi).createCompiler([t]).compileModuleAsync(e)}(this.injector,r,n).then(function(n){return e.bootstrapModuleFactory(n,r)})},n.prototype._moduleDoBootstrap=function(n){var t=n.injector.get(Mi);if(n._bootstrapComponents.length>0)n._bootstrapComponents.forEach(function(n){return t.bootstrap(n)});else{if(!n.instance.ngDoBootstrap)throw new Error("The module "+Dn(n.instance.constructor)+' was bootstrapped, but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. Please define one of these.');n.instance.ngDoBootstrap(t)}this._modules.push(n)},n.prototype.onDestroy=function(n){this._destroyListeners.push(n)},Object.defineProperty(n.prototype,"injector",{get:function(){return this._injector},enumerable:!0,configurable:!0}),n.prototype.destroy=function(){if(this._destroyed)throw new Error("The platform has already been destroyed!");this._modules.slice().forEach(function(n){return n.destroy()}),this._destroyListeners.forEach(function(n){return n()}),this._destroyed=!0},Object.defineProperty(n.prototype,"destroyed",{get:function(){return this._destroyed},enumerable:!0,configurable:!0}),n}();function Di(n,t){return Array.isArray(t)?t.reduce(Di,n):i({},n,t)}var Mi=function(){function n(n,t,e,r,o,i){var l=this;this._zone=n,this._console=t,this._injector=e,this._exceptionHandler=r,this._componentFactoryResolver=o,this._initStatus=i,this._bootstrapListeners=[],this._views=[],this._runningTick=!1,this._enforceNoNewChanges=!1,this._stable=!0,this.componentTypes=[],this.components=[],this._enforceNoNewChanges=ho(),this._zone.onMicrotaskEmpty.subscribe({next:function(){l._zone.run(function(){l.tick()})}});var u=new I(function(n){l._stable=l._zone.isStable&&!l._zone.hasPendingMacrotasks&&!l._zone.hasPendingMicrotasks,l._zone.runOutsideAngular(function(){n.next(l._stable),n.complete()})}),s=new I(function(n){var t;l._zone.runOutsideAngular(function(){t=l._zone.onStable.subscribe(function(){yi.assertNotInAngularZone(),Pn(function(){l._stable||l._zone.hasPendingMacrotasks||l._zone.hasPendingMicrotasks||(l._stable=!0,n.next(!0))})})});var e=l._zone.onUnstable.subscribe(function(){yi.assertInAngularZone(),l._stable&&(l._stable=!1,l._zone.runOutsideAngular(function(){n.next(!1)}))});return function(){t.unsubscribe(),e.unsubscribe()}});this.isStable=function(){for(var n=[],t=0;t1&&"number"==typeof n[n.length-1]&&(r=n.pop())):"number"==typeof i&&(r=n.pop()),null===o&&1===n.length&&n[0]instanceof I?n[0]:function(n){return void 0===n&&(n=Number.POSITIVE_INFINITY),function n(t,e,r){return void 0===r&&(r=Number.POSITIVE_INFINITY),"function"==typeof e?function(o){return o.pipe(n(function(n,r){return nn(t(n,r)).pipe(K(function(t,o){return e(n,t,r,o)}))},r))}:("number"==typeof e&&(r=e),function(n){return n.lift(new tn(t,r))})}(rn,n)}(r)(X(n,o))}(u,s.pipe(function(n){return on()((t=fn,function(n){var e;e="function"==typeof t?t:function(){return t};var r=Object.create(n,an);return r.source=n,r.subjectFactory=e,r})(n));var t}))}var t;return t=n,n.prototype.bootstrap=function(n,t){var e,r=this;if(!this._initStatus.done)throw new Error("Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.");e=n instanceof Fr?n:this._componentFactoryResolver.resolveComponentFactory(n),this.componentTypes.push(e.componentType);var o=e instanceof $r?null:this._injector.get(qe),i=e.create(Ne.NULL,[],t||e.selector,o);i.onDestroy(function(){r._unloadComponent(i)});var l=i.injector.get(Oi,null);return l&&i.injector.get(ki).registerApplication(i.location.nativeElement,l),this._loadComponent(i),ho()&&this._console.log("Angular is running in the development mode. Call enableProdMode() to enable the production mode."),i},n.prototype.tick=function(){var n=this;if(this._runningTick)throw new Error("ApplicationRef.tick is called recursively");var e=t._tickScope();try{this._runningTick=!0,this._views.forEach(function(n){return n.detectChanges()}),this._enforceNoNewChanges&&this._views.forEach(function(n){return n.checkNoChanges()})}catch(r){this._zone.runOutsideAngular(function(){return n._exceptionHandler.handleError(r)})}finally{this._runningTick=!1,vi(e)}},n.prototype.attachView=function(n){var t=n;this._views.push(t),t.attachToAppRef(this)},n.prototype.detachView=function(n){var t=n;Vi(this._views,t),t.detachFromAppRef()},n.prototype._loadComponent=function(n){this.attachView(n.hostView),this.tick(),this.components.push(n),this._injector.get(ii,[]).concat(this._bootstrapListeners).forEach(function(t){return t(n)})},n.prototype._unloadComponent=function(n){this.detachView(n.hostView),Vi(this.components,n)},n.prototype.ngOnDestroy=function(){this._views.slice().forEach(function(n){return n.destroy()})},Object.defineProperty(n.prototype,"viewCount",{get:function(){return this._views.length},enumerable:!0,configurable:!0}),n._tickScope=gi("ApplicationRef#tick()"),n}();function Vi(n,t){var e=n.indexOf(t);e>-1&&n.splice(e,1)}var Hi,Ri=function(){function n(){this.dirty=!0,this._results=[],this.changes=new Ho,this.length=0}return n.prototype.map=function(n){return this._results.map(n)},n.prototype.filter=function(n){return this._results.filter(n)},n.prototype.find=function(n){return this._results.find(n)},n.prototype.reduce=function(n,t){return this._results.reduce(n,t)},n.prototype.forEach=function(n){this._results.forEach(n)},n.prototype.some=function(n){return this._results.some(n)},n.prototype.toArray=function(){return this._results.slice()},n.prototype[In()]=function(){return this._results[In()]()},n.prototype.toString=function(){return this._results.toString()},n.prototype.reset=function(n){this._results=function n(t){return t.reduce(function(t,e){var r=Array.isArray(e)?n(e):e;return t.concat(r)},[])}(n),this.dirty=!1,this.length=this._results.length,this.last=this._results[this.length-1],this.first=this._results[0]},n.prototype.notifyOnChanges=function(){this.changes.emit(this)},n.prototype.setDirty=function(){this.dirty=!0},n.prototype.destroy=function(){this.changes.complete(),this.changes.unsubscribe()},n}(),ji=function(){function n(){}return n.__NG_ELEMENT_ID__=function(){return Li(n,Qr)},n}(),Li=Ee,zi=function(){function n(){}return n.__NG_ELEMENT_ID__=function(){return Fi()},n}(),Fi=function(){for(var n=[],t=0;t-1}(r)||"root"===o.providedIn&&r._def.isRoot))){var c=n._providers.length;return n._def.providersByKey[t.tokenKey]={flags:5120,value:u.factory,deps:[],index:c,token:t.token},n._providers[c]=fu,n._providers[c]=yu(n,n._def.providersByKey[t.tokenKey])}return 4&t.flags?e:n._parent.get(t.token,e)}finally{Fn(i)}}function yu(n,t){var e;switch(201347067&t.flags){case 512:e=function(n,t,e){var r=e.length;switch(r){case 0:return new t;case 1:return new t(vu(n,e[0]));case 2:return new t(vu(n,e[0]),vu(n,e[1]));case 3:return new t(vu(n,e[0]),vu(n,e[1]),vu(n,e[2]));default:for(var o=new Array(r),i=0;i=e.length)&&(t=e.length-1),t<0)return null;var r=e[t];return r.viewContainerParent=null,Cu(e,t),Cl.dirtyParentQueries(r),_u(r),r}function bu(n,t,e){var r=t?Fl(t,t.def.lastRenderRootNode):n.renderElement,o=e.renderer.parentNode(r),i=e.renderer.nextSibling(r);Wl(e,2,o,i,void 0)}function _u(n){Wl(n,3,null,null,void 0)}function wu(n,t,e){t>=n.length?n.push(e):n.splice(t,0,e)}function Cu(n,t){t>=n.length-1?n.pop():n.splice(t,1)}var Eu=new Object;function xu(n,t,e,r,o,i){return new Ou(n,t,e,r,o,i)}var Ou=function(n){function t(t,e,r,o,i,l){var u=n.call(this)||this;return u.selector=t,u.componentType=e,u._inputs=o,u._outputs=i,u.ngContentSelectors=l,u.viewDefFactory=r,u}return o(t,n),Object.defineProperty(t.prototype,"inputs",{get:function(){var n=[],t=this._inputs;for(var e in t)n.push({propName:e,templateName:t[e]});return n},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"outputs",{get:function(){var n=[];for(var t in this._outputs)n.push({propName:t,templateName:this._outputs[t]});return n},enumerable:!0,configurable:!0}),t.prototype.create=function(n,t,e,r){if(!r)throw new Error("ngModule should be provided");var o=Ql(this.viewDefFactory),i=o.nodes[0].element.componentProvider.nodeIndex,l=Cl.createRootView(n,t||[],e,o,r,Eu),u=bl(l,i).instance;return e&&l.renderer.setAttribute(ml(l,0).renderElement,"ng-version",to.full),new ku(l,new Iu(l),u)},t}(Fr),ku=function(n){function t(t,e,r){var o=n.call(this)||this;return o._view=t,o._viewRef=e,o._component=r,o._elDef=o._view.def.nodes[0],o.hostView=e,o.changeDetectorRef=e,o.instance=r,o}return o(t,n),Object.defineProperty(t.prototype,"location",{get:function(){return new Qr(ml(this._view,this._elDef.nodeIndex).renderElement)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"injector",{get:function(){return new Mu(this._view,this._elDef)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"componentType",{get:function(){return this._component.constructor},enumerable:!0,configurable:!0}),t.prototype.destroy=function(){this._viewRef.destroy()},t.prototype.onDestroy=function(n){this._viewRef.onDestroy(n)},t}(zr);function Su(n,t,e){return new Au(n,t,e)}var Au=function(){function n(n,t,e){this._view=n,this._elDef=t,this._data=e,this._embeddedViews=[]}return Object.defineProperty(n.prototype,"element",{get:function(){return new Qr(this._data.renderElement)},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"injector",{get:function(){return new Mu(this._view,this._elDef)},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"parentInjector",{get:function(){for(var n=this._view,t=this._elDef.parent;!t&&n;)t=zl(n),n=n.parent;return n?new Mu(n,t):new Mu(this._view,null)},enumerable:!0,configurable:!0}),n.prototype.clear=function(){for(var n=this._embeddedViews.length-1;n>=0;n--){var t=mu(this._data,n);Cl.destroyView(t)}},n.prototype.get=function(n){var t=this._embeddedViews[n];if(t){var e=new Iu(t);return e.attachToViewContainerRef(this),e}return null},Object.defineProperty(n.prototype,"length",{get:function(){return this._embeddedViews.length},enumerable:!0,configurable:!0}),n.prototype.createEmbeddedView=function(n,t,e){var r=n.createEmbeddedView(t||{});return this.insert(r,e),r},n.prototype.createComponent=function(n,t,e,r,o){var i=e||this.parentInjector;o||n instanceof $r||(o=i.get(qe));var l=n.create(i,r,void 0,o);return this.insert(l.hostView,t),l},n.prototype.insert=function(n,t){if(n.destroyed)throw new Error("Cannot insert a destroyed View in a ViewContainer!");var e,r,o,i,l=n;return i=(e=this._data).viewContainer._embeddedViews,null==(r=t)&&(r=i.length),(o=l._view).viewContainerParent=this._view,wu(i,r,o),function(n,t){var e=Ll(t);if(e&&e!==n&&!(16&t.state)){t.state|=16;var r=e.template._projectedViews;r||(r=e.template._projectedViews=[]),r.push(t),function(n,e){if(!(4&e.flags)){t.parent.def.nodeFlags|=4,e.flags|=4;for(var r=e.parent;r;)r.childFlags|=4,r=r.parent}}(0,t.parentNodeDef)}}(e,o),Cl.dirtyParentQueries(o),bu(e,r>0?i[r-1]:null,o),l.attachToViewContainerRef(this),n},n.prototype.move=function(n,t){if(n.destroyed)throw new Error("Cannot move a destroyed View in a ViewContainer!");var e,r,o,i,l,u=this._embeddedViews.indexOf(n._view);return o=t,l=(i=(e=this._data).viewContainer._embeddedViews)[r=u],Cu(i,r),null==o&&(o=i.length),wu(i,o,l),Cl.dirtyParentQueries(l),_u(l),bu(e,o>0?i[o-1]:null,l),n},n.prototype.indexOf=function(n){return this._embeddedViews.indexOf(n._view)},n.prototype.remove=function(n){var t=mu(this._data,n);t&&Cl.destroyView(t)},n.prototype.detach=function(n){var t=mu(this._data,n);return t?new Iu(t):null},n}();function Tu(n){return new Iu(n)}var Iu=function(){function n(n){this._view=n,this._viewContainerRef=null,this._appRef=null}return Object.defineProperty(n.prototype,"rootNodes",{get:function(){return Wl(this._view,0,void 0,void 0,n=[]),n;var n},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"context",{get:function(){return this._view.context},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"destroyed",{get:function(){return 0!=(128&this._view.state)},enumerable:!0,configurable:!0}),n.prototype.markForCheck=function(){Hl(this._view)},n.prototype.detach=function(){this._view.state&=-5},n.prototype.detectChanges=function(){var n=this._view.root.rendererFactory;n.begin&&n.begin();try{Cl.checkAndUpdateView(this._view)}finally{n.end&&n.end()}},n.prototype.checkNoChanges=function(){Cl.checkNoChangesView(this._view)},n.prototype.reattach=function(){this._view.state|=4},n.prototype.onDestroy=function(n){this._view.disposables||(this._view.disposables=[]),this._view.disposables.push(n)},n.prototype.destroy=function(){this._appRef?this._appRef.detachView(this):this._viewContainerRef&&this._viewContainerRef.detach(this._viewContainerRef.indexOf(this)),Cl.destroyView(this._view)},n.prototype.detachFromAppRef=function(){this._appRef=null,_u(this._view),Cl.dirtyParentQueries(this._view)},n.prototype.attachToAppRef=function(n){if(this._viewContainerRef)throw new Error("This view is already attached to a ViewContainer!");this._appRef=n},n.prototype.attachToViewContainerRef=function(n){if(this._appRef)throw new Error("This view is already attached directly to the ApplicationRef!");this._viewContainerRef=n},n}();function Pu(n,t){return new Nu(n,t)}var Nu=function(n){function t(t,e){var r=n.call(this)||this;return r._parentView=t,r._def=e,r}return o(t,n),t.prototype.createEmbeddedView=function(n){return new Iu(Cl.createEmbeddedView(this._parentView,this._def,this._def.element.template,n))},Object.defineProperty(t.prototype,"elementRef",{get:function(){return new Qr(ml(this._parentView,this._def.nodeIndex).renderElement)},enumerable:!0,configurable:!0}),t}(Ro);function Du(n,t){return new Mu(n,t)}var Mu=function(){function n(n,t){this.view=n,this.elDef=t}return n.prototype.get=function(n,t){return void 0===t&&(t=Ne.THROW_IF_NOT_FOUND),Cl.resolveDep(this.view,this.elDef,!!this.elDef&&0!=(33554432&this.elDef.flags),{flags:0,token:n,tokenKey:Al(n)},t)},n}();function Vu(n,t){var e=n.def.nodes[t];if(1&e.flags){var r=ml(n,e.nodeIndex);return e.element.template?r.template:r.renderElement}if(2&e.flags)return yl(n,e.nodeIndex).renderText;if(20240&e.flags)return bl(n,e.nodeIndex).instance;throw new Error("Illegal state: read nodeValue for node index "+t)}function Hu(n){return new Ru(n.renderer)}var Ru=function(){function n(n){this.delegate=n}return n.prototype.selectRootElement=function(n){return this.delegate.selectRootElement(n)},n.prototype.createElement=function(n,t){var e=a(tu(t),2),r=this.delegate.createElement(e[1],e[0]);return n&&this.delegate.appendChild(n,r),r},n.prototype.createViewRoot=function(n){return n},n.prototype.createTemplateAnchor=function(n){var t=this.delegate.createComment("");return n&&this.delegate.appendChild(n,t),t},n.prototype.createText=function(n,t){var e=this.delegate.createText(t);return n&&this.delegate.appendChild(n,e),e},n.prototype.projectNodes=function(n,t){for(var e=0;e0,t.provider.value,t.provider.deps);if(t.outputs.length)for(var r=0;r0,r=t.provider;switch(201347067&t.flags){case 512:return es(n,t.parent,e,r.value,r.deps);case 1024:return function(n,t,e,r,o){var i=o.length;switch(i){case 0:return r();case 1:return r(os(n,t,e,o[0]));case 2:return r(os(n,t,e,o[0]),os(n,t,e,o[1]));case 3:return r(os(n,t,e,o[0]),os(n,t,e,o[1]),os(n,t,e,o[2]));default:for(var l=Array(i),u=0;u0)a=g,_s(g)||(c=g);else for(;a&&d===a.nodeIndex+a.childCount;){var m=a.parent;m&&(m.childFlags|=a.childFlags,m.childMatchedQueries|=a.childMatchedQueries),c=(a=m)&&_s(a)?a.renderParent:a}}return{factory:null,nodeFlags:l,rootNodeFlags:u,nodeMatchedQueries:s,flags:n,nodes:t,updateDirectives:e||kl,updateRenderer:r||kl,handleEvent:function(n,e,r,o){return t[e].element.handleEvent(n,r,o)},bindingCount:o,outputCount:i,lastRenderRootNode:p}}function _s(n){return 0!=(1&n.flags)&&null===n.element.name}function ws(n,t,e){var r=t.element&&t.element.template;if(r){if(!r.lastRenderRootNode)throw new Error("Illegal State: Embedded templates without nodes are not allowed!");if(r.lastRenderRootNode&&16777216&r.lastRenderRootNode.flags)throw new Error("Illegal State: Last root node of a template can't have embedded views, at index "+t.nodeIndex+"!")}if(20224&t.flags&&0==(1&(n?n.flags:0)))throw new Error("Illegal State: StaticProvider/Directive nodes need to be children of elements or anchors, at index "+t.nodeIndex+"!");if(t.query){if(67108864&t.flags&&(!n||0==(16384&n.flags)))throw new Error("Illegal State: Content Query nodes need to be children of directives, at index "+t.nodeIndex+"!");if(134217728&t.flags&&n)throw new Error("Illegal State: View Query nodes have to be top level nodes, at index "+t.nodeIndex+"!")}if(t.childCount){var o=n?n.nodeIndex+n.childCount:e-1;if(t.nodeIndex<=o&&t.nodeIndex+t.childCount>o)throw new Error("Illegal State: childCount of node leads outside of parent, at index "+t.nodeIndex+"!")}}function Cs(n,t,e,r){var o=Os(n.root,n.renderer,n,t,e);return ks(o,n.component,r),Ss(o),o}function Es(n,t,e){var r=Os(n,n.renderer,null,null,t);return ks(r,e,e),Ss(r),r}function xs(n,t,e,r){var o,i=t.element.componentRendererType;return o=i?n.root.rendererFactory.createRenderer(r,i):n.root.renderer,Os(n.root,o,n,t.element.componentProvider,e)}function Os(n,t,e,r,o){var i=new Array(o.nodes.length),l=o.outputCount?new Array(o.outputCount):null;return{def:o,parent:e,viewContainerParent:null,parentNodeDef:r,context:null,component:null,nodes:i,state:13,root:n,renderer:t,oldValues:new Array(o.bindingCount),disposables:l,initIndex:-1}}function ks(n,t,e){n.component=t,n.context=e}function Ss(n){var t;Bl(n)&&(t=ml(n.parent,n.parentNodeDef.parent.nodeIndex).renderElement);for(var e=n.def,r=n.nodes,o=0;o0&&cu(n,t,0,e)&&(p=!0),h>1&&cu(n,t,1,r)&&(p=!0),h>2&&cu(n,t,2,o)&&(p=!0),h>3&&cu(n,t,3,i)&&(p=!0),h>4&&cu(n,t,4,l)&&(p=!0),h>5&&cu(n,t,5,u)&&(p=!0),h>6&&cu(n,t,6,s)&&(p=!0),h>7&&cu(n,t,7,a)&&(p=!0),h>8&&cu(n,t,8,c)&&(p=!0),h>9&&cu(n,t,9,f)&&(p=!0),p}(n,t,e,r,o,i,l,u,s,a,c,f);case 2:return function(n,t,e,r,o,i,l,u,s,a,c,f){var h=!1,p=t.bindings,d=p.length;if(d>0&&Ml(n,t,0,e)&&(h=!0),d>1&&Ml(n,t,1,r)&&(h=!0),d>2&&Ml(n,t,2,o)&&(h=!0),d>3&&Ml(n,t,3,i)&&(h=!0),d>4&&Ml(n,t,4,l)&&(h=!0),d>5&&Ml(n,t,5,u)&&(h=!0),d>6&&Ml(n,t,6,s)&&(h=!0),d>7&&Ml(n,t,7,a)&&(h=!0),d>8&&Ml(n,t,8,c)&&(h=!0),d>9&&Ml(n,t,9,f)&&(h=!0),h){var g=t.text.prefix;d>0&&(g+=ms(e,p[0])),d>1&&(g+=ms(r,p[1])),d>2&&(g+=ms(o,p[2])),d>3&&(g+=ms(i,p[3])),d>4&&(g+=ms(l,p[4])),d>5&&(g+=ms(u,p[5])),d>6&&(g+=ms(s,p[6])),d>7&&(g+=ms(a,p[7])),d>8&&(g+=ms(c,p[8])),d>9&&(g+=ms(f,p[9]));var v=yl(n,t.nodeIndex).renderText;n.renderer.setValue(v,g)}return h}(n,t,e,r,o,i,l,u,s,a,c,f);case 16384:return function(n,t,e,r,o,i,l,u,s,a,c,f){var h=bl(n,t.nodeIndex),p=h.instance,d=!1,g=void 0,v=t.bindings.length;return v>0&&Dl(n,t,0,e)&&(d=!0,g=ls(n,h,t,0,e,g)),v>1&&Dl(n,t,1,r)&&(d=!0,g=ls(n,h,t,1,r,g)),v>2&&Dl(n,t,2,o)&&(d=!0,g=ls(n,h,t,2,o,g)),v>3&&Dl(n,t,3,i)&&(d=!0,g=ls(n,h,t,3,i,g)),v>4&&Dl(n,t,4,l)&&(d=!0,g=ls(n,h,t,4,l,g)),v>5&&Dl(n,t,5,u)&&(d=!0,g=ls(n,h,t,5,u,g)),v>6&&Dl(n,t,6,s)&&(d=!0,g=ls(n,h,t,6,s,g)),v>7&&Dl(n,t,7,a)&&(d=!0,g=ls(n,h,t,7,a,g)),v>8&&Dl(n,t,8,c)&&(d=!0,g=ls(n,h,t,8,c,g)),v>9&&Dl(n,t,9,f)&&(d=!0,g=ls(n,h,t,9,f,g)),g&&p.ngOnChanges(g),65536&t.flags&&vl(n,256,t.nodeIndex)&&p.ngOnInit(),262144&t.flags&&p.ngDoCheck(),d}(n,t,e,r,o,i,l,u,s,a,c,f);case 32:case 64:case 128:return function(n,t,e,r,o,i,l,u,s,a,c,f){var h=t.bindings,p=!1,d=h.length;if(d>0&&Ml(n,t,0,e)&&(p=!0),d>1&&Ml(n,t,1,r)&&(p=!0),d>2&&Ml(n,t,2,o)&&(p=!0),d>3&&Ml(n,t,3,i)&&(p=!0),d>4&&Ml(n,t,4,l)&&(p=!0),d>5&&Ml(n,t,5,u)&&(p=!0),d>6&&Ml(n,t,6,s)&&(p=!0),d>7&&Ml(n,t,7,a)&&(p=!0),d>8&&Ml(n,t,8,c)&&(p=!0),d>9&&Ml(n,t,9,f)&&(p=!0),p){var g=_l(n,t.nodeIndex),v=void 0;switch(201347067&t.flags){case 32:v=new Array(h.length),d>0&&(v[0]=e),d>1&&(v[1]=r),d>2&&(v[2]=o),d>3&&(v[3]=i),d>4&&(v[4]=l),d>5&&(v[5]=u),d>6&&(v[6]=s),d>7&&(v[7]=a),d>8&&(v[8]=c),d>9&&(v[9]=f);break;case 64:v={},d>0&&(v[h[0].name]=e),d>1&&(v[h[1].name]=r),d>2&&(v[h[2].name]=o),d>3&&(v[h[3].name]=i),d>4&&(v[h[4].name]=l),d>5&&(v[h[5].name]=u),d>6&&(v[h[6].name]=s),d>7&&(v[h[7].name]=a),d>8&&(v[h[8].name]=c),d>9&&(v[h[9].name]=f);break;case 128:var y=e;switch(d){case 1:v=y.transform(e);break;case 2:v=y.transform(r);break;case 3:v=y.transform(r,o);break;case 4:v=y.transform(r,o,i);break;case 5:v=y.transform(r,o,i,l);break;case 6:v=y.transform(r,o,i,l,u);break;case 7:v=y.transform(r,o,i,l,u,s);break;case 8:v=y.transform(r,o,i,l,u,s,a);break;case 9:v=y.transform(r,o,i,l,u,s,a,c);break;case 10:v=y.transform(r,o,i,l,u,s,a,c,f)}}g.value=v}return p}(n,t,e,r,o,i,l,u,s,a,c,f);default:throw"unreachable"}}(n,t,r,o,i,l,u,s,a,f,h,p):function(n,t,e){switch(201347067&t.flags){case 1:return function(n,t,e){for(var r=!1,o=0;o0&&Vl(n,t,0,e),h>1&&Vl(n,t,1,r),h>2&&Vl(n,t,2,o),h>3&&Vl(n,t,3,i),h>4&&Vl(n,t,4,l),h>5&&Vl(n,t,5,u),h>6&&Vl(n,t,6,s),h>7&&Vl(n,t,7,a),h>8&&Vl(n,t,8,c),h>9&&Vl(n,t,9,f)}(n,t,r,o,i,l,u,s,a,c,f,h):function(n,t,e){for(var r=0;r0){var i=new Set(n.modules);Ws.forEach(function(t,r){if(i.has(bn(r).providedIn)){var o={token:r,flags:t.flags|(e?4096:0),deps:Zl(t.deps),value:t.value,index:n.providers.length};n.providers.push(o),n.providersByKey[Al(r)]=o}})}}(n=n.factory(function(){return kl})),n):n}(r))}var Qs=new Map,Ws=new Map,Ks=new Map;function Js(n){var t;Qs.set(n.token,n),"function"==typeof n.token&&(t=bn(n.token))&&"function"==typeof t.providedIn&&Ws.set(n.token,n)}function Ys(n,t){var e=Ql(t.viewDefFactory),r=Ql(e.nodes[0].element.componentView);Ks.set(n,r)}function Xs(){Qs.clear(),Ws.clear(),Ks.clear()}function na(n){if(0===Qs.size)return n;var t=function(n){for(var t=[],e=null,r=0;r=this.currentHistoricCoverage.lcq)return!1}else if("branchCoverageIncreaseOnly"===t){var r=this.branchCoverage;if(isNaN(r)||r<=this.currentHistoricCoverage.bcq)return!1}else if("branchCoverageDecreaseOnly"===t&&(r=this.branchCoverage,isNaN(r)||r>=this.currentHistoricCoverage.bcq))return!1;return!0},t.prototype.updateCurrentHistoricCoverage=function(n){if(this.currentHistoricCoverage=null,""!==n)for(var t=0;t-1&&null===e,r}return o(t,n),t.prototype.visible=function(n,t){if(""!==n&&this.name.toLowerCase().indexOf(n.toLowerCase())>-1)return!0;for(var e=0;et&&(r[o].collapsed=n.settings.collapseStates[t]),t++,e(r[o].subElements)};e(this.codeElements)},n}(),La=new I(function(n){return n.complete()}),za=function(n){function t(t,e){var r=n.call(this,t)||this;r.sources=e,r.completed=0,r.haveValues=0;var o=e.length;r.values=new Array(o);for(var i=0;i0},t.prototype.tagName=function(n){return n.tagName},t.prototype.attributeMap=function(n){for(var t=new Map,e=n.attributes,r=0;r0;l||(l=n[i]=[]);var s=$c(t)?Zone.root:Zone.current;if(0===l.length)l.push({zone:s,handler:o});else{for(var a=!1,c=0;c-1},t}(kc),tf=["alt","control","meta","shift"],ef={alt:function(n){return n.altKey},control:function(n){return n.ctrlKey},meta:function(n){return n.metaKey},shift:function(n){return n.shiftKey}},rf=function(n){function t(t){return n.call(this,t)||this}var e;return o(t,n),e=t,t.prototype.supports=function(n){return null!=e.parseEventName(n)},t.prototype.addEventListener=function(n,t,r){var o=e.parseEventName(t),i=e.eventCallback(o.fullKey,r,this.manager.getZone());return this.manager.getZone().runOutsideAngular(function(){return uc().onAndCancel(n,o.domEventName,i)})},t.parseEventName=function(n){var t=n.toLowerCase().split("."),r=t.shift();if(0===t.length||"keydown"!==r&&"keyup"!==r)return null;var o=e._normalizeKey(t.pop()),i="";if(tf.forEach(function(n){var e=t.indexOf(n);e>-1&&(t.splice(e,1),i+=n+".")}),i+=o,0!=t.length||0===o.length)return null;var l={};return l.domEventName=r,l.fullKey=i,l},t.getEventFullKey=function(n){var t="",e=uc().getEventKey(n);return" "===(e=e.toLowerCase())?e="space":"."===e&&(e="dot"),tf.forEach(function(r){r!=e&&(0,ef[r])(n)&&(t+=r+".")}),t+=e},t.eventCallback=function(n,t,r){return function(o){e.getEventFullKey(o)===n&&r.runGuarded(function(){return t(o)})}},t._normalizeKey=function(n){switch(n){case"esc":return"escape";default:return n}},t}(kc),of=function(){return function(){}}(),lf=function(n){function t(t){var e=n.call(this)||this;return e._doc=t,e}return o(t,n),t.prototype.sanitize=function(n,t){if(null==t)return null;switch(n){case Lo.NONE:return t;case Lo.HTML:return t instanceof sf?t.changingThisBreaksApplicationSecurity:(this.checkNotSafeValue(t,"HTML"),function(n,t){var e=null;try{_o=_o||new po(n);var r=t?String(t):"";e=_o.getInertBodyElement(r);var o=5,i=r;do{if(0===o)throw new Error("Failed to sanitize html because the input is unstable");o--,r=i,i=e.innerHTML,e=_o.getInertBodyElement(r)}while(r!==i);var l=new To,u=l.sanitizeChildren(Do(e)||e);return ho()&&l.sanitizedSomething&&console.warn("WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss)."),u}finally{if(e)for(var s=Do(e)||e;s.firstChild;)s.removeChild(s.firstChild)}}(this._doc,String(t)));case Lo.STYLE:return t instanceof af?t.changingThisBreaksApplicationSecurity:(this.checkNotSafeValue(t,"Style"),function(n){if(!(n=String(n).trim()))return"";var t=n.match(Bo);return t&&yo(t[1])===t[1]||n.match(Fo)&&function(n){for(var t=!0,e=!0,r=0;rn?{max:{max:n,actual:t.value}}:null}},n.required=function(n){return mf(n.value)?{required:!0}:null},n.requiredTrue=function(n){return!0===n.value?null:{required:!0}},n.email=function(n){return mf(n.value)?null:bf.test(n.value)?null:{email:!0}},n.minLength=function(n){return function(t){if(mf(t.value))return null;var e=t.value?t.value.length:0;return en?{maxlength:{requiredLength:n,actualLength:e}}:null}},n.pattern=function(t){return t?("string"==typeof t?(r="","^"!==t.charAt(0)&&(r+="^"),r+=t,"$"!==t.charAt(t.length-1)&&(r+="$"),e=new RegExp(r)):(r=t.toString(),e=t),function(n){if(mf(n.value))return null;var t=n.value;return e.test(t)?null:{pattern:{requiredPattern:r,actualValue:t}}}):n.nullValidator;var e,r},n.nullValidator=function(n){return null},n.compose=function(n){if(!n)return null;var t=n.filter(wf);return 0==t.length?null:function(n){return Ef(function(n,e){return t.map(function(t){return t(n)})}(n))}},n.composeAsync=function(n){if(!n)return null;var t=n.filter(wf);return 0==t.length?null:function(n){return function n(){for(var t,e=[],r=0;r=0;--t)if(this._accessors[t][1]===n)return void this._accessors.splice(t,1)},n.prototype.select=function(n){var t=this;this._accessors.forEach(function(e){t._isSameGroup(e,n)&&e[1]!==n&&e[1].fireUncheck(n.value)})},n.prototype._isSameGroup=function(n,t){return!!n[0].control&&n[0]._parent===t._control._parent&&n[1].name===t.name},n}(),Mf=function(){function n(n,t,e,r){this._renderer=n,this._elementRef=t,this._registry=e,this._injector=r,this.onChange=function(){},this.onTouched=function(){}}return n.prototype.ngOnInit=function(){this._control=this._injector.get(Nf),this._checkName(),this._registry.add(this._control,this)},n.prototype.ngOnDestroy=function(){this._registry.remove(this)},n.prototype.writeValue=function(n){this._state=n===this.value,this._renderer.setProperty(this._elementRef.nativeElement,"checked",this._state)},n.prototype.registerOnChange=function(n){var t=this;this._fn=n,this.onChange=function(){n(t.value),t._registry.select(t)}},n.prototype.fireUncheck=function(n){this.writeValue(n)},n.prototype.registerOnTouched=function(n){this.onTouched=n},n.prototype.setDisabledState=function(n){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",n)},n.prototype._checkName=function(){this.name&&this.formControlName&&this.name!==this.formControlName&&this._throwNameError(),!this.name&&this.formControlName&&(this.name=this.formControlName)},n.prototype._throwNameError=function(){throw new Error('\n If you define both a name and a formControlName attribute on your radio button, their values\n must match. Ex: \n ')},n}(),Vf=function(){function n(n,t){this._renderer=n,this._elementRef=t,this.onChange=function(n){},this.onTouched=function(){}}return n.prototype.writeValue=function(n){this._renderer.setProperty(this._elementRef.nativeElement,"value",parseFloat(n))},n.prototype.registerOnChange=function(n){this.onChange=function(t){n(""==t?null:parseFloat(t))}},n.prototype.registerOnTouched=function(n){this.onTouched=n},n.prototype.setDisabledState=function(n){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",n)},n}(),Hf='\n

\n
\n \n
\n
\n\n In your class:\n\n this.myGroup = new FormGroup({\n person: new FormGroup({ firstName: new FormControl() })\n });',Rf='\n
\n
\n \n
\n
';function jf(n,t){return null==n?""+t:(t&&"object"==typeof t&&(t="Object"),(n+": "+t).slice(0,50))}var Lf=function(){function n(n,t){this._renderer=n,this._elementRef=t,this._optionMap=new Map,this._idCounter=0,this.onChange=function(n){},this.onTouched=function(){},this._compareWith=Nn}return Object.defineProperty(n.prototype,"compareWith",{set:function(n){if("function"!=typeof n)throw new Error("compareWith must be a function, but received "+JSON.stringify(n));this._compareWith=n},enumerable:!0,configurable:!0}),n.prototype.writeValue=function(n){this.value=n;var t=this._getOptionId(n);null==t&&this._renderer.setProperty(this._elementRef.nativeElement,"selectedIndex",-1);var e=jf(t,n);this._renderer.setProperty(this._elementRef.nativeElement,"value",e)},n.prototype.registerOnChange=function(n){var t=this;this.onChange=function(e){t.value=t._getOptionValue(e),n(t.value)}},n.prototype.registerOnTouched=function(n){this.onTouched=n},n.prototype.setDisabledState=function(n){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",n)},n.prototype._registerOption=function(){return(this._idCounter++).toString()},n.prototype._getOptionId=function(n){var t,e;try{for(var r=s(Array.from(this._optionMap.keys())),o=r.next();!o.done;o=r.next()){var i=o.value;if(this._compareWith(this._optionMap.get(i),n))return i}}catch(l){t={error:l}}finally{try{o&&!o.done&&(e=r.return)&&e.call(r)}finally{if(t)throw t.error}}return null},n.prototype._getOptionValue=function(n){var t=function(n){return n.split(":")[0]}(n);return this._optionMap.has(t)?this._optionMap.get(t):n},n}(),zf=function(){function n(n,t,e){this._element=n,this._renderer=t,this._select=e,this._select&&(this.id=this._select._registerOption())}return Object.defineProperty(n.prototype,"ngValue",{set:function(n){null!=this._select&&(this._select._optionMap.set(this.id,n),this._setElementValue(jf(this.id,n)),this._select.writeValue(this._select.value))},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"value",{set:function(n){this._setElementValue(n),this._select&&this._select.writeValue(this._select.value)},enumerable:!0,configurable:!0}),n.prototype._setElementValue=function(n){this._renderer.setProperty(this._element.nativeElement,"value",n)},n.prototype.ngOnDestroy=function(){this._select&&(this._select._optionMap.delete(this.id),this._select.writeValue(this._select.value))},n}();function Ff(n,t){return null==n?""+t:("string"==typeof t&&(t="'"+t+"'"),t&&"object"==typeof t&&(t="Object"),(n+": "+t).slice(0,50))}var Bf=function(){function n(n,t){this._renderer=n,this._elementRef=t,this._optionMap=new Map,this._idCounter=0,this.onChange=function(n){},this.onTouched=function(){},this._compareWith=Nn}return Object.defineProperty(n.prototype,"compareWith",{set:function(n){if("function"!=typeof n)throw new Error("compareWith must be a function, but received "+JSON.stringify(n));this._compareWith=n},enumerable:!0,configurable:!0}),n.prototype.writeValue=function(n){var t,e=this;if(this.value=n,Array.isArray(n)){var r=n.map(function(n){return e._getOptionId(n)});t=function(n,t){n._setSelected(r.indexOf(t.toString())>-1)}}else t=function(n,t){n._setSelected(!1)};this._optionMap.forEach(t)},n.prototype.registerOnChange=function(n){var t=this;this.onChange=function(e){var r=[];if(e.hasOwnProperty("selectedOptions"))for(var o=e.selectedOptions,i=0;i1?"path: '"+n.path.join(" -> ")+"'":n.path[0]?"name: '"+n.path+"'":"unspecified name attribute",new Error(t+" "+e)}function Qf(n){return null!=n?_f.compose(n.map(Af)):null}function Wf(n){return null!=n?_f.composeAsync(n.map(Tf)):null}var Kf=[Of,Vf,If,Lf,Bf,Mf],Jf=function(n){function t(){return null!==n&&n.apply(this,arguments)||this}return o(t,n),t.prototype.ngOnInit=function(){this._checkParentType(),this.formDirective.addFormGroup(this)},t.prototype.ngOnDestroy=function(){this.formDirective&&this.formDirective.removeFormGroup(this)},Object.defineProperty(t.prototype,"control",{get:function(){return this.formDirective.getFormGroup(this)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"path",{get:function(){return Gf(this.name,this._parent)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"formDirective",{get:function(){return this._parent?this._parent.formDirective:null},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"validator",{get:function(){return Qf(this._validators)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"asyncValidator",{get:function(){return Wf(this._asyncValidators)},enumerable:!0,configurable:!0}),t.prototype._checkParentType=function(){},t}(yf),Yf=function(n){function t(t){return n.call(this,t)||this}return o(t,n),t}(function(){function n(n){this._cd=n}return Object.defineProperty(n.prototype,"ngClassUntouched",{get:function(){return!!this._cd.control&&this._cd.control.untouched},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassTouched",{get:function(){return!!this._cd.control&&this._cd.control.touched},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassPristine",{get:function(){return!!this._cd.control&&this._cd.control.pristine},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassDirty",{get:function(){return!!this._cd.control&&this._cd.control.dirty},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassValid",{get:function(){return!!this._cd.control&&this._cd.control.valid},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassInvalid",{get:function(){return!!this._cd.control&&this._cd.control.invalid},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassPending",{get:function(){return!!this._cd.control&&this._cd.control.pending},enumerable:!0,configurable:!0}),n}());function Xf(n){var t=th(n)?n.validators:n;return Array.isArray(t)?Qf(t):t||null}function nh(n,t){var e=th(t)?t.asyncValidators:n;return Array.isArray(e)?Wf(e):e||null}function th(n){return null!=n&&!Array.isArray(n)&&"object"==typeof n}var eh=function(){function n(n,t){this.validator=n,this.asyncValidator=t,this._onCollectionChange=function(){},this.pristine=!0,this.touched=!1,this._onDisabledChange=[]}return Object.defineProperty(n.prototype,"parent",{get:function(){return this._parent},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"valid",{get:function(){return"VALID"===this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"invalid",{get:function(){return"INVALID"===this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"pending",{get:function(){return"PENDING"==this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"disabled",{get:function(){return"DISABLED"===this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"enabled",{get:function(){return"DISABLED"!==this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"dirty",{get:function(){return!this.pristine},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"untouched",{get:function(){return!this.touched},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"updateOn",{get:function(){return this._updateOn?this._updateOn:this.parent?this.parent.updateOn:"change"},enumerable:!0,configurable:!0}),n.prototype.setValidators=function(n){this.validator=Xf(n)},n.prototype.setAsyncValidators=function(n){this.asyncValidator=nh(n)},n.prototype.clearValidators=function(){this.validator=null},n.prototype.clearAsyncValidators=function(){this.asyncValidator=null},n.prototype.markAsTouched=function(n){void 0===n&&(n={}),this.touched=!0,this._parent&&!n.onlySelf&&this._parent.markAsTouched(n)},n.prototype.markAsUntouched=function(n){void 0===n&&(n={}),this.touched=!1,this._pendingTouched=!1,this._forEachChild(function(n){n.markAsUntouched({onlySelf:!0})}),this._parent&&!n.onlySelf&&this._parent._updateTouched(n)},n.prototype.markAsDirty=function(n){void 0===n&&(n={}),this.pristine=!1,this._parent&&!n.onlySelf&&this._parent.markAsDirty(n)},n.prototype.markAsPristine=function(n){void 0===n&&(n={}),this.pristine=!0,this._pendingDirty=!1,this._forEachChild(function(n){n.markAsPristine({onlySelf:!0})}),this._parent&&!n.onlySelf&&this._parent._updatePristine(n)},n.prototype.markAsPending=function(n){void 0===n&&(n={}),this.status="PENDING",!1!==n.emitEvent&&this.statusChanges.emit(this.status),this._parent&&!n.onlySelf&&this._parent.markAsPending(n)},n.prototype.disable=function(n){void 0===n&&(n={}),this.status="DISABLED",this.errors=null,this._forEachChild(function(t){t.disable(i({},n,{onlySelf:!0}))}),this._updateValue(),!1!==n.emitEvent&&(this.valueChanges.emit(this.value),this.statusChanges.emit(this.status)),this._updateAncestors(n),this._onDisabledChange.forEach(function(n){return n(!0)})},n.prototype.enable=function(n){void 0===n&&(n={}),this.status="VALID",this._forEachChild(function(t){t.enable(i({},n,{onlySelf:!0}))}),this.updateValueAndValidity({onlySelf:!0,emitEvent:n.emitEvent}),this._updateAncestors(n),this._onDisabledChange.forEach(function(n){return n(!1)})},n.prototype._updateAncestors=function(n){this._parent&&!n.onlySelf&&(this._parent.updateValueAndValidity(n),this._parent._updatePristine(),this._parent._updateTouched())},n.prototype.setParent=function(n){this._parent=n},n.prototype.updateValueAndValidity=function(n){void 0===n&&(n={}),this._setInitialStatus(),this._updateValue(),this.enabled&&(this._cancelExistingSubscription(),this.errors=this._runValidator(),this.status=this._calculateStatus(),"VALID"!==this.status&&"PENDING"!==this.status||this._runAsyncValidator(n.emitEvent)),!1!==n.emitEvent&&(this.valueChanges.emit(this.value),this.statusChanges.emit(this.status)),this._parent&&!n.onlySelf&&this._parent.updateValueAndValidity(n)},n.prototype._updateTreeValidity=function(n){void 0===n&&(n={emitEvent:!0}),this._forEachChild(function(t){return t._updateTreeValidity(n)}),this.updateValueAndValidity({onlySelf:!0,emitEvent:n.emitEvent})},n.prototype._setInitialStatus=function(){this.status=this._allControlsDisabled()?"DISABLED":"VALID"},n.prototype._runValidator=function(){return this.validator?this.validator(this):null},n.prototype._runAsyncValidator=function(n){var t=this;if(this.asyncValidator){this.status="PENDING";var e=Cf(this.asyncValidator(this));this._asyncValidationSubscription=e.subscribe(function(e){return t.setErrors(e,{emitEvent:n})})}},n.prototype._cancelExistingSubscription=function(){this._asyncValidationSubscription&&this._asyncValidationSubscription.unsubscribe()},n.prototype.setErrors=function(n,t){void 0===t&&(t={}),this.errors=n,this._updateControlsErrors(!1!==t.emitEvent)},n.prototype.get=function(n){return function(n,t,e){return null==t?null:(t instanceof Array||(t=t.split(".")),t instanceof Array&&0===t.length?null:t.reduce(function(n,t){return n instanceof oh?n.controls.hasOwnProperty(t)?n.controls[t]:null:n instanceof ih&&n.at(t)||null},n))}(this,n)},n.prototype.getError=function(n,t){var e=t?this.get(t):this;return e&&e.errors?e.errors[n]:null},n.prototype.hasError=function(n,t){return!!this.getError(n,t)},Object.defineProperty(n.prototype,"root",{get:function(){for(var n=this;n._parent;)n=n._parent;return n},enumerable:!0,configurable:!0}),n.prototype._updateControlsErrors=function(n){this.status=this._calculateStatus(),n&&this.statusChanges.emit(this.status),this._parent&&this._parent._updateControlsErrors(n)},n.prototype._initObservables=function(){this.valueChanges=new Ho,this.statusChanges=new Ho},n.prototype._calculateStatus=function(){return this._allControlsDisabled()?"DISABLED":this.errors?"INVALID":this._anyControlsHaveStatus("PENDING")?"PENDING":this._anyControlsHaveStatus("INVALID")?"INVALID":"VALID"},n.prototype._anyControlsHaveStatus=function(n){return this._anyControls(function(t){return t.status===n})},n.prototype._anyControlsDirty=function(){return this._anyControls(function(n){return n.dirty})},n.prototype._anyControlsTouched=function(){return this._anyControls(function(n){return n.touched})},n.prototype._updatePristine=function(n){void 0===n&&(n={}),this.pristine=!this._anyControlsDirty(),this._parent&&!n.onlySelf&&this._parent._updatePristine(n)},n.prototype._updateTouched=function(n){void 0===n&&(n={}),this.touched=this._anyControlsTouched(),this._parent&&!n.onlySelf&&this._parent._updateTouched(n)},n.prototype._isBoxedValue=function(n){return"object"==typeof n&&null!==n&&2===Object.keys(n).length&&"value"in n&&"disabled"in n},n.prototype._registerOnCollectionChange=function(n){this._onCollectionChange=n},n.prototype._setUpdateStrategy=function(n){th(n)&&null!=n.updateOn&&(this._updateOn=n.updateOn)},n}(),rh=function(n){function t(t,e,r){void 0===t&&(t=null);var o=n.call(this,Xf(e),nh(r,e))||this;return o._onChange=[],o._applyFormState(t),o._setUpdateStrategy(e),o.updateValueAndValidity({onlySelf:!0,emitEvent:!1}),o._initObservables(),o}return o(t,n),t.prototype.setValue=function(n,t){var e=this;void 0===t&&(t={}),this.value=this._pendingValue=n,this._onChange.length&&!1!==t.emitModelToViewChange&&this._onChange.forEach(function(n){return n(e.value,!1!==t.emitViewToModelChange)}),this.updateValueAndValidity(t)},t.prototype.patchValue=function(n,t){void 0===t&&(t={}),this.setValue(n,t)},t.prototype.reset=function(n,t){void 0===n&&(n=null),void 0===t&&(t={}),this._applyFormState(n),this.markAsPristine(t),this.markAsUntouched(t),this.setValue(this.value,t),this._pendingChange=!1},t.prototype._updateValue=function(){},t.prototype._anyControls=function(n){return!1},t.prototype._allControlsDisabled=function(){return this.disabled},t.prototype.registerOnChange=function(n){this._onChange.push(n)},t.prototype._clearChangeFns=function(){this._onChange=[],this._onDisabledChange=[],this._onCollectionChange=function(){}},t.prototype.registerOnDisabledChange=function(n){this._onDisabledChange.push(n)},t.prototype._forEachChild=function(n){},t.prototype._syncPendingControls=function(){return!("submit"!==this.updateOn||(this._pendingDirty&&this.markAsDirty(),this._pendingTouched&&this.markAsTouched(),!this._pendingChange)||(this.setValue(this._pendingValue,{onlySelf:!0,emitModelToViewChange:!1}),0))},t.prototype._applyFormState=function(n){this._isBoxedValue(n)?(this.value=this._pendingValue=n.value,n.disabled?this.disable({onlySelf:!0,emitEvent:!1}):this.enable({onlySelf:!0,emitEvent:!1})):this.value=this._pendingValue=n},t}(eh),oh=function(n){function t(t,e,r){var o=n.call(this,Xf(e),nh(r,e))||this;return o.controls=t,o._initObservables(),o._setUpdateStrategy(e),o._setUpControls(),o.updateValueAndValidity({onlySelf:!0,emitEvent:!1}),o}return o(t,n),t.prototype.registerControl=function(n,t){return this.controls[n]?this.controls[n]:(this.controls[n]=t,t.setParent(this),t._registerOnCollectionChange(this._onCollectionChange),t)},t.prototype.addControl=function(n,t){this.registerControl(n,t),this.updateValueAndValidity(),this._onCollectionChange()},t.prototype.removeControl=function(n){this.controls[n]&&this.controls[n]._registerOnCollectionChange(function(){}),delete this.controls[n],this.updateValueAndValidity(),this._onCollectionChange()},t.prototype.setControl=function(n,t){this.controls[n]&&this.controls[n]._registerOnCollectionChange(function(){}),delete this.controls[n],t&&this.registerControl(n,t),this.updateValueAndValidity(),this._onCollectionChange()},t.prototype.contains=function(n){return this.controls.hasOwnProperty(n)&&this.controls[n].enabled},t.prototype.setValue=function(n,t){var e=this;void 0===t&&(t={}),this._checkAllValuesPresent(n),Object.keys(n).forEach(function(r){e._throwIfControlMissing(r),e.controls[r].setValue(n[r],{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)},t.prototype.patchValue=function(n,t){var e=this;void 0===t&&(t={}),Object.keys(n).forEach(function(r){e.controls[r]&&e.controls[r].patchValue(n[r],{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)},t.prototype.reset=function(n,t){void 0===n&&(n={}),void 0===t&&(t={}),this._forEachChild(function(e,r){e.reset(n[r],{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t),this._updatePristine(t),this._updateTouched(t)},t.prototype.getRawValue=function(){return this._reduceChildren({},function(n,t,e){return n[e]=t instanceof rh?t.value:t.getRawValue(),n})},t.prototype._syncPendingControls=function(){var n=this._reduceChildren(!1,function(n,t){return!!t._syncPendingControls()||n});return n&&this.updateValueAndValidity({onlySelf:!0}),n},t.prototype._throwIfControlMissing=function(n){if(!Object.keys(this.controls).length)throw new Error("\n There are no form controls registered with this group yet. If you're using ngModel,\n you may want to check next tick (e.g. use setTimeout).\n ");if(!this.controls[n])throw new Error("Cannot find form control with name: "+n+".")},t.prototype._forEachChild=function(n){var t=this;Object.keys(this.controls).forEach(function(e){return n(t.controls[e],e)})},t.prototype._setUpControls=function(){var n=this;this._forEachChild(function(t){t.setParent(n),t._registerOnCollectionChange(n._onCollectionChange)})},t.prototype._updateValue=function(){this.value=this._reduceValue()},t.prototype._anyControls=function(n){var t=this,e=!1;return this._forEachChild(function(r,o){e=e||t.contains(o)&&n(r)}),e},t.prototype._reduceValue=function(){var n=this;return this._reduceChildren({},function(t,e,r){return(e.enabled||n.disabled)&&(t[r]=e.value),t})},t.prototype._reduceChildren=function(n,t){var e=n;return this._forEachChild(function(n,r){e=t(e,n,r)}),e},t.prototype._allControlsDisabled=function(){var n,t;try{for(var e=s(Object.keys(this.controls)),r=e.next();!r.done;r=e.next())if(this.controls[r.value].enabled)return!1}catch(o){n={error:o}}finally{try{r&&!r.done&&(t=e.return)&&t.call(e)}finally{if(n)throw n.error}}return Object.keys(this.controls).length>0||this.disabled},t.prototype._checkAllValuesPresent=function(n){this._forEachChild(function(t,e){if(void 0===n[e])throw new Error("Must supply a value for form control with name: '"+e+"'.")})},t}(eh),ih=function(n){function t(t,e,r){var o=n.call(this,Xf(e),nh(r,e))||this;return o.controls=t,o._initObservables(),o._setUpdateStrategy(e),o._setUpControls(),o.updateValueAndValidity({onlySelf:!0,emitEvent:!1}),o}return o(t,n),t.prototype.at=function(n){return this.controls[n]},t.prototype.push=function(n){this.controls.push(n),this._registerControl(n),this.updateValueAndValidity(),this._onCollectionChange()},t.prototype.insert=function(n,t){this.controls.splice(n,0,t),this._registerControl(t),this.updateValueAndValidity()},t.prototype.removeAt=function(n){this.controls[n]&&this.controls[n]._registerOnCollectionChange(function(){}),this.controls.splice(n,1),this.updateValueAndValidity()},t.prototype.setControl=function(n,t){this.controls[n]&&this.controls[n]._registerOnCollectionChange(function(){}),this.controls.splice(n,1),t&&(this.controls.splice(n,0,t),this._registerControl(t)),this.updateValueAndValidity(),this._onCollectionChange()},Object.defineProperty(t.prototype,"length",{get:function(){return this.controls.length},enumerable:!0,configurable:!0}),t.prototype.setValue=function(n,t){var e=this;void 0===t&&(t={}),this._checkAllValuesPresent(n),n.forEach(function(n,r){e._throwIfControlMissing(r),e.at(r).setValue(n,{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)},t.prototype.patchValue=function(n,t){var e=this;void 0===t&&(t={}),n.forEach(function(n,r){e.at(r)&&e.at(r).patchValue(n,{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)},t.prototype.reset=function(n,t){void 0===n&&(n=[]),void 0===t&&(t={}),this._forEachChild(function(e,r){e.reset(n[r],{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t),this._updatePristine(t),this._updateTouched(t)},t.prototype.getRawValue=function(){return this.controls.map(function(n){return n instanceof rh?n.value:n.getRawValue()})},t.prototype._syncPendingControls=function(){var n=this.controls.reduce(function(n,t){return!!t._syncPendingControls()||n},!1);return n&&this.updateValueAndValidity({onlySelf:!0}),n},t.prototype._throwIfControlMissing=function(n){if(!this.controls.length)throw new Error("\n There are no form controls registered with this array yet. If you're using ngModel,\n you may want to check next tick (e.g. use setTimeout).\n ");if(!this.at(n))throw new Error("Cannot find form control at index "+n)},t.prototype._forEachChild=function(n){this.controls.forEach(function(t,e){n(t,e)})},t.prototype._updateValue=function(){var n=this;this.value=this.controls.filter(function(t){return t.enabled||n.disabled}).map(function(n){return n.value})},t.prototype._anyControls=function(n){return this.controls.some(function(t){return t.enabled&&n(t)})},t.prototype._setUpControls=function(){var n=this;this._forEachChild(function(t){return n._registerControl(t)})},t.prototype._checkAllValuesPresent=function(n){this._forEachChild(function(t,e){if(void 0===n[e])throw new Error("Must supply a value for form control at index: "+e+".")})},t.prototype._allControlsDisabled=function(){var n,t;try{for(var e=s(this.controls),r=e.next();!r.done;r=e.next())if(r.value.enabled)return!1}catch(o){n={error:o}}finally{try{r&&!r.done&&(t=e.return)&&t.call(e)}finally{if(n)throw n.error}}return this.controls.length>0||this.disabled},t.prototype._registerControl=function(n){n.setParent(this),n._registerOnCollectionChange(this._onCollectionChange)},t}(eh),lh=Promise.resolve(null),uh=function(n){function t(t,e){var r=n.call(this)||this;return r.submitted=!1,r._directives=[],r.ngSubmit=new Ho,r.form=new oh({},Qf(t),Wf(e)),r}return o(t,n),t.prototype.ngAfterViewInit=function(){this._setUpdateStrategy()},Object.defineProperty(t.prototype,"formDirective",{get:function(){return this},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"control",{get:function(){return this.form},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"path",{get:function(){return[]},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"controls",{get:function(){return this.form.controls},enumerable:!0,configurable:!0}),t.prototype.addControl=function(n){var t=this;lh.then(function(){var e=t._findContainer(n.path);n.control=e.registerControl(n.name,n.control),Zf(n.control,n),n.control.updateValueAndValidity({emitEvent:!1}),t._directives.push(n)})},t.prototype.getControl=function(n){return this.form.get(n.path)},t.prototype.removeControl=function(n){var t=this;lh.then(function(){var e,r,o=t._findContainer(n.path);o&&o.removeControl(n.name),(r=(e=t._directives).indexOf(n))>-1&&e.splice(r,1)})},t.prototype.addFormGroup=function(n){var t=this;lh.then(function(){var e=t._findContainer(n.path),r=new oh({});(function(n,t){null==n&&$f(t,"Cannot find control with"),n.validator=_f.compose([n.validator,t.validator]),n.asyncValidator=_f.composeAsync([n.asyncValidator,t.asyncValidator])})(r,n),e.registerControl(n.name,r),r.updateValueAndValidity({emitEvent:!1})})},t.prototype.removeFormGroup=function(n){var t=this;lh.then(function(){var e=t._findContainer(n.path);e&&e.removeControl(n.name)})},t.prototype.getFormGroup=function(n){return this.form.get(n.path)},t.prototype.updateModel=function(n,t){var e=this;lh.then(function(){e.form.get(n.path).setValue(t)})},t.prototype.setValue=function(n){this.control.setValue(n)},t.prototype.onSubmit=function(n){return this.submitted=!0,t=this._directives,this.form._syncPendingControls(),t.forEach(function(n){var t=n.control;"submit"===t.updateOn&&t._pendingChange&&(n.viewToModelUpdate(t._pendingValue),t._pendingChange=!1)}),this.ngSubmit.emit(n),!1;var t},t.prototype.onReset=function(){this.resetForm()},t.prototype.resetForm=function(n){void 0===n&&(n=void 0),this.form.reset(n),this.submitted=!1},t.prototype._setUpdateStrategy=function(){this.options&&null!=this.options.updateOn&&(this.form._updateOn=this.options.updateOn)},t.prototype._findContainer=function(n){return n.pop(),n.length?this.form.get(n):this.form},t}(yf),sh=function(){function n(){}return n.modelParentException=function(){throw new Error('\n ngModel cannot be used to register form controls with a parent formGroup directive. Try using\n formGroup\'s partner directive "formControlName" instead. Example:\n\n \n
\n \n
\n\n In your class:\n\n this.myGroup = new FormGroup({\n firstName: new FormControl()\n });\n\n Or, if you\'d like to avoid registering this form control, indicate that it\'s standalone in ngModelOptions:\n\n Example:\n\n \n
\n \n \n
\n ')},n.formGroupNameException=function(){throw new Error("\n ngModel cannot be used to register form controls with a parent formGroupName or formArrayName directive.\n\n Option 1: Use formControlName instead of ngModel (reactive strategy):\n\n "+Hf+"\n\n Option 2: Update ngModel's parent be ngModelGroup (template-driven strategy):\n\n "+Rf)},n.missingNameException=function(){throw new Error('If ngModel is used within a form tag, either the name attribute must be set or the form\n control must be defined as \'standalone\' in ngModelOptions.\n\n Example 1: \n Example 2: ')},n.modelGroupParentException=function(){throw new Error("\n ngModelGroup cannot be used with a parent formGroup directive.\n\n Option 1: Use formGroupName instead of ngModelGroup (reactive strategy):\n\n "+Hf+"\n\n Option 2: Use a regular form tag instead of the formGroup directive (template-driven strategy):\n\n "+Rf)},n.ngFormWarning=function(){console.warn("\n It looks like you're using 'ngForm'.\n\n Support for using the 'ngForm' element selector has been deprecated in Angular v6 and will be removed\n in Angular v9.\n\n Use 'ng-form' instead.\n\n Before:\n \n\n After:\n \n ")},n}(),ah=new wn("NgFormSelectorWarning"),ch=function(n){function t(t,e,r){var o=n.call(this)||this;return o._parent=t,o._validators=e,o._asyncValidators=r,o}var e;return o(t,n),e=t,t.prototype._checkParentType=function(){this._parent instanceof e||this._parent instanceof uh||sh.modelGroupParentException()},t}(Jf),fh=Promise.resolve(null),hh=function(n){function t(t,e,r,o){var i=n.call(this)||this;return i.control=new rh,i._registered=!1,i.update=new Ho,i._parent=t,i._rawValidators=e||[],i._rawAsyncValidators=r||[],i.valueAccessor=function(n,t){if(!t)return null;Array.isArray(t)||$f(n,"Value accessor was not provided as an array for form control with");var e=void 0,r=void 0,o=void 0;return t.forEach(function(t){var i;t.constructor===Sf?e=t:(i=t,Kf.some(function(n){return i.constructor===n})?(r&&$f(n,"More than one built-in value accessor matches form control with"),r=t):(o&&$f(n,"More than one custom value accessor matches form control with"),o=t))}),o||r||e||($f(n,"No valid value accessor for form control with"),null)}(i,o),i}return o(t,n),t.prototype.ngOnChanges=function(n){this._checkForErrors(),this._registered||this._setUpControl(),"isDisabled"in n&&this._updateDisabled(n),function(n,t){if(!n.hasOwnProperty("model"))return!1;var e=n.model;return!!e.isFirstChange()||!Nn(t,e.currentValue)}(n,this.viewModel)&&(this._updateValue(this.model),this.viewModel=this.model)},t.prototype.ngOnDestroy=function(){this.formDirective&&this.formDirective.removeControl(this)},Object.defineProperty(t.prototype,"path",{get:function(){return this._parent?Gf(this.name,this._parent):[this.name]},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"formDirective",{get:function(){return this._parent?this._parent.formDirective:null},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"validator",{get:function(){return Qf(this._rawValidators)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"asyncValidator",{get:function(){return Wf(this._rawAsyncValidators)},enumerable:!0,configurable:!0}),t.prototype.viewToModelUpdate=function(n){this.viewModel=n,this.update.emit(n)},t.prototype._setUpControl=function(){this._setUpdateStrategy(),this._isStandalone()?this._setUpStandalone():this.formDirective.addControl(this),this._registered=!0},t.prototype._setUpdateStrategy=function(){this.options&&null!=this.options.updateOn&&(this.control._updateOn=this.options.updateOn)},t.prototype._isStandalone=function(){return!this._parent||!(!this.options||!this.options.standalone)},t.prototype._setUpStandalone=function(){Zf(this.control,this),this.control.updateValueAndValidity({emitEvent:!1})},t.prototype._checkForErrors=function(){this._isStandalone()||this._checkParentType(),this._checkName()},t.prototype._checkParentType=function(){!(this._parent instanceof ch)&&this._parent instanceof Jf?sh.formGroupNameException():this._parent instanceof ch||this._parent instanceof uh||sh.modelParentException()},t.prototype._checkName=function(){this.options&&this.options.name&&(this.name=this.options.name),this._isStandalone()||this.name||sh.missingNameException()},t.prototype._updateValue=function(n){var t=this;fh.then(function(){t.control.setValue(n,{emitViewToModelChange:!1})})},t.prototype._updateDisabled=function(n){var t=this,e=n.isDisabled.currentValue,r=""===e||e&&"false"!==e;fh.then(function(){r&&!t.control.disabled?t.control.disable():!r&&t.control.disabled&&t.control.enable()})},t}(Nf),ph=function(){return function(){}}(),dh=function(){function n(){}var t;return t=n,n.withConfig=function(n){return{ngModule:t,providers:[{provide:ah,useValue:n.warnOnDeprecatedNgFormSelector}]}},n}(),gh=Pl({encapsulation:2,styles:[],data:{}});function vh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,["",""]))],function(n,t){n(t,1,0,t.context.$implicit),n(t,2,0,t.context.$implicit)},function(n,t){n(t,3,0,t.context.$implicit)})}function yh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","20"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(-1,null,["20"]))],function(n,t){n(t,1,0,"20"),n(t,2,0,"20")},null)}function mh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","50"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(-1,null,["50"]))],function(n,t){n(t,1,0,"50"),n(t,2,0,"50")},null)}function bh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","100"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(-1,null,["100"]))],function(n,t){n(t,1,0,"100"),n(t,2,0,"100")},null)}function _h(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,["",""]))],function(n,t){var e=t.component;n(t,1,0,e.totalNumberOfRiskHotspots),n(t,2,0,e.totalNumberOfRiskHotspots)},function(n,t){n(t,3,0,t.component.translations.all)})}function wh(n){return bs(0,[(n()(),lu(0,0,null,null,17,"select",[],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"change"],[null,"blur"]],function(n,t,e){var r=!0,o=n.component;return"change"===t&&(r=!1!==Vu(n,1).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,1).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.numberOfRiskHotspots=e)&&r),r},null,null)),Qu(1,16384,null,0,Lf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n){return[n]},[Lf]),Qu(3,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(5,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(6,0,null,null,3,"option",[["value","10"]],null,null,null,null,null)),Qu(7,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(8,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(-1,null,["10"])),(n()(),iu(16777216,null,null,1,null,yh)),Qu(11,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,mh)),Qu(13,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,bh)),Qu(15,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,_h)),Qu(17,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component;n(t,3,0,e.settings.numberOfRiskHotspots),n(t,7,0,"10"),n(t,8,0,"10"),n(t,11,0,e.totalNumberOfRiskHotspots>10),n(t,13,0,e.totalNumberOfRiskHotspots>20),n(t,15,0,e.totalNumberOfRiskHotspots>50),n(t,17,0,e.totalNumberOfRiskHotspots>100)},function(n,t){n(t,0,0,Vu(t,5).ngClassUntouched,Vu(t,5).ngClassTouched,Vu(t,5).ngClassPristine,Vu(t,5).ngClassDirty,Vu(t,5).ngClassValid,Vu(t,5).ngClassInvalid,Vu(t,5).ngClassPending)})}function Ch(n){return bs(0,[(n()(),lu(0,0,null,null,0,"col",[["class","column105"]],null,null,null,null,null))],null,null)}function Eh(n){return bs(0,[(n()(),lu(0,0,null,null,7,"th",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting(""+n.context.index,e)&&r),r},null,null)),(n()(),lu(2,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(3,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(4,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(5,null,["",""])),(n()(),lu(6,0,null,null,1,"a",[],[[8,"href",4]],null,null,null,null)),(n()(),lu(7,0,null,null,0,"i",[["class","icon-info-circled"]],null,null,null,null,null))],function(n,t){var e=t.component,r=n(t,4,0,e.settings.sortBy===""+t.context.index&&"desc"===e.settings.sortOrder,e.settings.sortBy===""+t.context.index&&"asc"===e.settings.sortOrder,e.settings.sortBy!==""+t.context.index);n(t,3,0,"icon-down-dir",r)},function(n,t){n(t,5,0,t.context.$implicit.name),n(t,6,0,ru(1,"",t.context.$implicit.explanationUrl,""))})}function xh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"td",[["class","right"]],null,null,null,null,null)),Qu(1,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(2,{lightred:0,lightgreen:1}),(n()(),vs(3,null,["",""]))],function(n,t){var e=n(t,2,0,t.context.$implicit.exceeded,!t.context.$implicit.exceeded);n(t,1,0,"right",e)},function(n,t){n(t,3,0,t.context.$implicit.value)})}function Oh(n){return bs(0,[(n()(),lu(0,0,null,null,10,"tr",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,1,"td",[],null,null,null,null,null)),(n()(),vs(2,null,["",""])),(n()(),lu(3,0,null,null,2,"td",[],null,null,null,null,null)),(n()(),lu(4,0,null,null,1,"a",[],[[8,"href",4]],null,null,null,null)),(n()(),vs(5,null,["",""])),(n()(),lu(6,0,null,null,2,"td",[],[[8,"title",0]],null,null,null,null)),(n()(),lu(7,0,null,null,1,"a",[],[[8,"href",4]],null,null,null,null)),(n()(),vs(8,null,[" "," "])),(n()(),iu(16777216,null,null,1,null,xh)),Qu(10,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null)],function(n,t){n(t,10,0,t.context.$implicit.metrics)},function(n,t){n(t,2,0,t.context.$implicit.assembly),n(t,4,0,t.context.$implicit.reportPath),n(t,5,0,t.context.$implicit.class),n(t,6,0,t.context.$implicit.methodName),n(t,7,0,t.context.$implicit.reportPath+"#file"+t.context.$implicit.fileIndex+"_line"+t.context.$implicit.line),n(t,8,0,t.context.$implicit.methodShortName)})}function kh(n){return bs(0,[(n()(),lu(0,0,null,null,62,"div",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,28,"div",[["class","customizebox"]],null,null,null,null,null)),(n()(),lu(2,0,null,null,12,"div",[],null,null,null,null,null)),(n()(),lu(3,0,null,null,11,"select",[["name","assembly"]],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"change"],[null,"blur"]],function(n,t,e){var r=!0,o=n.component;return"change"===t&&(r=!1!==Vu(n,4).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,4).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.assembly=e)&&r),"ngModelChange"===t&&(r=!1!==o.updateRiskHotpots()&&r),r},null,null)),Qu(4,16384,null,0,Lf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n){return[n]},[Lf]),Qu(6,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{name:[0,"name"],model:[1,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(8,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(9,0,null,null,3,"option",[["value",""]],null,null,null,null,null)),Qu(10,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(11,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(12,null,["",""])),(n()(),iu(16777216,null,null,1,null,vh)),Qu(14,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),lu(15,0,null,null,4,"div",[["class","center"]],null,null,null,null,null)),(n()(),lu(16,0,null,null,1,"span",[],null,null,null,null,null)),(n()(),vs(17,null,["",""])),(n()(),iu(16777216,null,null,1,null,wh)),Qu(19,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(20,0,null,null,0,"div",[["class","center"]],null,null,null,null,null)),(n()(),lu(21,0,null,null,8,"div",[["class","right"]],null,null,null,null,null)),(n()(),lu(22,0,null,null,1,"span",[],null,null,null,null,null)),(n()(),vs(23,null,[""," "])),(n()(),lu(24,0,null,null,5,"input",[["type","text"]],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"input"],[null,"blur"],[null,"compositionstart"],[null,"compositionend"]],function(n,t,e){var r=!0,o=n.component;return"input"===t&&(r=!1!==Vu(n,25)._handleInput(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,25).onTouched()&&r),"compositionstart"===t&&(r=!1!==Vu(n,25)._compositionStart()&&r),"compositionend"===t&&(r=!1!==Vu(n,25)._compositionEnd(e.target.value)&&r),"ngModelChange"===t&&(r=!1!==(o.settings.filter=e)&&r),"ngModelChange"===t&&(r=!1!==o.updateRiskHotpots()&&r),r},null,null)),Qu(25,16384,null,0,Sf,[Xr,Qr,[2,kf]],null,null),Wu(1024,null,xf,function(n){return[n]},[Sf]),Qu(27,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(29,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(30,0,null,null,32,"table",[["class","overview table-fixed stripped"]],null,null,null,null,null)),(n()(),lu(31,0,null,null,5,"colgroup",[],null,null,null,null,null)),(n()(),lu(32,0,null,null,0,"col",[],null,null,null,null,null)),(n()(),lu(33,0,null,null,0,"col",[],null,null,null,null,null)),(n()(),lu(34,0,null,null,0,"col",[],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Ch)),Qu(36,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),lu(37,0,null,null,21,"thead",[],null,null,null,null,null)),(n()(),lu(38,0,null,null,20,"tr",[],null,null,null,null,null)),(n()(),lu(39,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(40,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("assembly",e)&&r),r},null,null)),(n()(),lu(41,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(42,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(43,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(44,null,["",""])),(n()(),lu(45,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(46,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("class",e)&&r),r},null,null)),(n()(),lu(47,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(48,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(49,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(50,null,["",""])),(n()(),lu(51,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(52,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("method",e)&&r),r},null,null)),(n()(),lu(53,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(54,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(55,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(56,null,["",""])),(n()(),iu(16777216,null,null,1,null,Eh)),Qu(58,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),lu(59,0,null,null,3,"tbody",[],null,null,null,null,null)),(n()(),iu(16777216,null,null,2,null,Oh)),Qu(61,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(t=0,e=ec,r=[],Ku(-1,t|=16,null,0,e,e,r))],function(n,t){var e=t.component;n(t,6,0,"assembly",e.settings.assembly),n(t,10,0,""),n(t,11,0,""),n(t,14,0,e.assemblies),n(t,19,0,e.totalNumberOfRiskHotspots>10),n(t,27,0,e.settings.filter),n(t,36,0,e.riskHotspotMetrics);var r=n(t,43,0,"assembly"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"assembly"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"assembly"!==e.settings.sortBy);n(t,42,0,"icon-down-dir",r);var o=n(t,49,0,"class"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"class"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"class"!==e.settings.sortBy);n(t,48,0,"icon-down-dir",o);var i=n(t,55,0,"method"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"method"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"method"!==e.settings.sortBy);n(t,54,0,"icon-down-dir",i),n(t,58,0,e.riskHotspotMetrics),n(t,61,0,function(n,t,e,r){if(_t.isWrapped(r)){r=_t.unwrap(r);var o=n.def.nodes[61].bindingIndex+0,i=_t.unwrap(n.oldValues[o]);n.oldValues[o]=new _t(i)}return r}(t,0,0,Vu(t,62).transform(e.riskHotspots,0,e.settings.numberOfRiskHotspots)))},function(n,t){var e=t.component;n(t,3,0,Vu(t,8).ngClassUntouched,Vu(t,8).ngClassTouched,Vu(t,8).ngClassPristine,Vu(t,8).ngClassDirty,Vu(t,8).ngClassValid,Vu(t,8).ngClassInvalid,Vu(t,8).ngClassPending),n(t,12,0,e.translations.assembly),n(t,17,0,e.translations.top),n(t,23,0,e.translations.filter),n(t,24,0,Vu(t,29).ngClassUntouched,Vu(t,29).ngClassTouched,Vu(t,29).ngClassPristine,Vu(t,29).ngClassDirty,Vu(t,29).ngClassValid,Vu(t,29).ngClassInvalid,Vu(t,29).ngClassPending),n(t,44,0,e.translations.assembly),n(t,50,0,e.translations.class),n(t,56,0,e.translations.method)});var t,e,r}function Sh(n){return bs(0,[(n()(),iu(16777216,null,null,1,null,kh)),Qu(1,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){n(t,1,0,t.component.totalNumberOfRiskHotspots>0)},null)}function Ah(n){return bs(0,[(n()(),lu(0,0,null,null,1,"risk-hotspots",[],null,[["window","beforeunload"]],function(n,t,e){var r=!0;return"window:beforeunload"===t&&(r=!1!==Vu(n,1).onDonBeforeUnlodad()&&r),r},Sh,gh)),Qu(1,114688,null,0,Na,[Ta],null,null)],function(n,t){n(t,1,0)},null)}var Th=xu("risk-hotspots",Na,Ah,{},{},[]),Ih=function(){function n(){this.grayVisible=!0,this.greenVisible=!1,this.redVisible=!1,this.greenClass="",this.redClass="",this._percentage=NaN}return Object.defineProperty(n.prototype,"percentage",{get:function(){return this._percentage},set:function(n){this._percentage=n,this.grayVisible=isNaN(n),this.greenVisible=!isNaN(n)&&Math.round(n)>0,this.redVisible=!isNaN(n)&&100-Math.round(n)>0,this.greenClass="covered"+Math.round(n),this.redClass="covered"+(100-Math.round(n))},enumerable:!0,configurable:!0}),n}(),Ph=Pl({encapsulation:2,styles:[],data:{}});function Nh(n){return bs(0,[(n()(),lu(0,0,null,null,0,"td",[["class","gray covered100"]],null,null,null,null,null))],null,null)}function Dh(n){return bs(0,[(n()(),lu(0,0,null,null,0,"td",[],[[8,"className",0]],null,null,null,null))],null,function(n,t){n(t,0,0,ru(1,"green ",t.component.greenClass,""))})}function Mh(n){return bs(0,[(n()(),lu(0,0,null,null,0,"td",[],[[8,"className",0]],null,null,null,null))],null,function(n,t){n(t,0,0,ru(1,"red ",t.component.redClass,""))})}function Vh(n){return bs(2,[(n()(),lu(0,0,null,null,6,"table",[["class","coverage"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Nh)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Dh)),Qu(4,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Mh)),Qu(6,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component;n(t,2,0,e.grayVisible),n(t,4,0,e.greenVisible),n(t,6,0,e.redVisible)},null)}var Hh=function(){return function(){this.element=null,this.collapsed=!1,this.branchCoverageAvailable=!1}}(),Rh=Pl({encapsulation:2,styles:[],data:{}});function jh(n){return bs(0,[(n()(),lu(0,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(1,null,["",""]))],null,function(n,t){n(t,1,0,t.component.element.branchCoveragePercentage)})}function Lh(n){return bs(0,[(n()(),lu(0,0,null,null,2,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(1,0,null,null,1,"coverage-bar",[],null,null,null,Vh,Ph)),Qu(2,49152,null,0,Ih,[],{percentage:[0,"percentage"]},null)],function(n,t){n(t,2,0,t.component.element.branchCoverage)},null)}function zh(n){return bs(2,[(n()(),lu(0,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.element.toggleCollapse(e)&&r),r},null,null)),(n()(),lu(2,0,null,null,2,"i",[],null,null,null,null,null)),Qu(3,278528,null,0,Wa,[ol,il,Qr,Xr],{ngClass:[0,"ngClass"]},null),gs(4,{"icon-plus":0,"icon-minus":1}),(n()(),vs(5,null,[" ",""])),(n()(),lu(6,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(7,null,["",""])),(n()(),lu(8,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(9,null,["",""])),(n()(),lu(10,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(11,null,["",""])),(n()(),lu(12,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(13,null,["",""])),(n()(),lu(14,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(15,null,["",""])),(n()(),lu(16,0,null,null,2,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(17,0,null,null,1,"coverage-bar",[],null,null,null,Vh,Ph)),Qu(18,49152,null,0,Ih,[],{percentage:[0,"percentage"]},null),(n()(),iu(16777216,null,null,1,null,jh)),Qu(20,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Lh)),Qu(22,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component,r=n(t,4,0,e.element.collapsed,!e.element.collapsed);n(t,3,0,r),n(t,18,0,e.element.coverage),n(t,20,0,e.branchCoverageAvailable),n(t,22,0,e.branchCoverageAvailable)},function(n,t){var e=t.component;n(t,5,0,e.element.name),n(t,7,0,e.element.coveredLines),n(t,9,0,e.element.uncoveredLines),n(t,11,0,e.element.coverableLines),n(t,13,0,e.element.totalLines),n(t,15,0,e.element.coveragePercentage)})}var Fh=function(){function n(){this.path=null,this._historicCoverages=[]}return Object.defineProperty(n.prototype,"historicCoverages",{get:function(){return this._historicCoverages},set:function(n){if(this._historicCoverages=n,n.length>1){for(var t="",e=0;et?"lightgreen":n1),n(t,4,0,null!==e.clazz.currentHistoricCoverage),n(t,6,0,null===e.clazz.currentHistoricCoverage)},null)}function ap(n){return bs(0,[(n()(),lu(0,0,null,null,2,"td",[["class","right"]],null,null,null,null,null)),(n()(),lu(1,0,null,null,1,"coverage-bar",[],null,null,null,Vh,Ph)),Qu(2,49152,null,0,Ih,[],{percentage:[0,"percentage"]},null)],function(n,t){n(t,2,0,t.component.clazz.branchCoverage)},null)}function cp(n){return bs(2,[(n()(),lu(0,0,null,null,4,"td",[],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,qh)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,$h)),Qu(4,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(5,0,null,null,4,"td",[["class","right"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Qh)),Qu(7,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Wh)),Qu(9,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(10,0,null,null,4,"td",[["class","right"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Kh)),Qu(12,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Jh)),Qu(14,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(15,0,null,null,4,"td",[["class","right"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Yh)),Qu(17,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Xh)),Qu(19,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(20,0,null,null,4,"td",[["class","right"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,np)),Qu(22,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,tp)),Qu(24,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(25,0,null,null,6,"td",[["class","right"]],[[8,"title",0]],null,null,null,null)),(n()(),iu(16777216,null,null,1,null,ep)),Qu(27,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,rp)),Qu(29,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,op)),Qu(31,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(32,0,null,null,2,"td",[["class","right"]],null,null,null,null,null)),(n()(),lu(33,0,null,null,1,"coverage-bar",[],null,null,null,Vh,Ph)),Qu(34,49152,null,0,Ih,[],{percentage:[0,"percentage"]},null),(n()(),iu(16777216,null,null,1,null,sp)),Qu(36,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,ap)),Qu(38,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component;n(t,2,0,""!==e.clazz.reportPath),n(t,4,0,""===e.clazz.reportPath),n(t,7,0,null!==e.clazz.currentHistoricCoverage),n(t,9,0,null===e.clazz.currentHistoricCoverage),n(t,12,0,null!==e.clazz.currentHistoricCoverage),n(t,14,0,null===e.clazz.currentHistoricCoverage),n(t,17,0,null!==e.clazz.currentHistoricCoverage),n(t,19,0,null===e.clazz.currentHistoricCoverage),n(t,22,0,null!==e.clazz.currentHistoricCoverage),n(t,24,0,null===e.clazz.currentHistoricCoverage),n(t,27,0,e.clazz.lineCoverageHistory.length>1),n(t,29,0,null!==e.clazz.currentHistoricCoverage),n(t,31,0,null===e.clazz.currentHistoricCoverage),n(t,34,0,e.clazz.coverage),n(t,36,0,e.branchCoverageAvailable),n(t,38,0,e.branchCoverageAvailable)},function(n,t){n(t,25,0,t.component.clazz.coverageType)})}var fp=Pl({encapsulation:2,styles:[],data:{}});function hp(n){return bs(0,[(n()(),lu(0,0,null,null,1,null,null,null,null,null,null,null)),(n()(),vs(1,null,["",""]))],null,function(n,t){n(t,1,0,t.component.translations.noGrouping)})}function pp(n){return bs(0,[(n()(),lu(0,0,null,null,1,null,null,null,null,null,null,null)),(n()(),vs(1,null,["",""]))],null,function(n,t){n(t,1,0,t.component.translations.byAssembly)})}function dp(n){return bs(0,[(n()(),lu(0,0,null,null,1,null,null,null,null,null,null,null)),(n()(),vs(1,null,["",""]))],null,function(n,t){var e=t.component;n(t,1,0,e.translations.byNamespace+" "+e.settings.grouping)})}function gp(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,["",""]))],function(n,t){n(t,1,0,t.context.$implicit),n(t,2,0,t.context.$implicit)},function(n,t){n(t,3,0,t.context.$implicit)})}function vp(n){return bs(0,[(n()(),lu(0,0,null,null,0,"br",[],null,null,null,null,null))],null,null)}function yp(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","branchCoverageIncreaseOnly"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,[" "," "]))],function(n,t){n(t,1,0,"branchCoverageIncreaseOnly"),n(t,2,0,"branchCoverageIncreaseOnly")},function(n,t){n(t,3,0,t.component.translations.branchCoverageIncreaseOnly)})}function mp(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","branchCoverageDecreaseOnly"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,[" "," "]))],function(n,t){n(t,1,0,"branchCoverageDecreaseOnly"),n(t,2,0,"branchCoverageDecreaseOnly")},function(n,t){n(t,3,0,t.component.translations.branchCoverageDecreaseOnly)})}function bp(n){return bs(0,[(n()(),lu(0,0,null,null,26,"div",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,25,"select",[],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"change"],[null,"blur"]],function(n,t,e){var r=!0,o=n.component;return"change"===t&&(r=!1!==Vu(n,2).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,2).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.historyComparisionType=e)&&r),r},null,null)),Qu(2,16384,null,0,Lf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n){return[n]},[Lf]),Qu(4,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(6,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(7,0,null,null,3,"option",[["value",""]],null,null,null,null,null)),Qu(8,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(9,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(10,null,["",""])),(n()(),lu(11,0,null,null,3,"option",[["value","allChanges"]],null,null,null,null,null)),Qu(12,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(13,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(14,null,["",""])),(n()(),lu(15,0,null,null,3,"option",[["value","lineCoverageIncreaseOnly"]],null,null,null,null,null)),Qu(16,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(17,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(18,null,["",""])),(n()(),lu(19,0,null,null,3,"option",[["value","lineCoverageDecreaseOnly"]],null,null,null,null,null)),Qu(20,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(21,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(22,null,["",""])),(n()(),iu(16777216,null,null,1,null,yp)),Qu(24,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,mp)),Qu(26,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component;n(t,4,0,e.settings.historyComparisionType),n(t,8,0,""),n(t,9,0,""),n(t,12,0,"allChanges"),n(t,13,0,"allChanges"),n(t,16,0,"lineCoverageIncreaseOnly"),n(t,17,0,"lineCoverageIncreaseOnly"),n(t,20,0,"lineCoverageDecreaseOnly"),n(t,21,0,"lineCoverageDecreaseOnly"),n(t,24,0,e.branchCoverageAvailable),n(t,26,0,e.branchCoverageAvailable)},function(n,t){var e=t.component;n(t,1,0,Vu(t,6).ngClassUntouched,Vu(t,6).ngClassTouched,Vu(t,6).ngClassPristine,Vu(t,6).ngClassDirty,Vu(t,6).ngClassValid,Vu(t,6).ngClassInvalid,Vu(t,6).ngClassPending),n(t,10,0,e.translations.filter),n(t,14,0,e.translations.allChanges),n(t,18,0,e.translations.lineCoverageIncreaseOnly),n(t,22,0,e.translations.lineCoverageDecreaseOnly)})}function _p(n){return bs(0,[(n()(),lu(0,0,null,null,18,null,null,null,null,null,null,null)),(n()(),lu(1,0,null,null,13,"div",[],null,null,null,null,null)),(n()(),vs(2,null,[" "," "])),(n()(),lu(3,0,null,null,11,"select",[],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"change"],[null,"blur"]],function(n,t,e){var r=!0,o=n.component;return"change"===t&&(r=!1!==Vu(n,4).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,4).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.historyComparisionDate=e)&&r),"ngModelChange"===t&&(r=!1!==o.updateCurrentHistoricCoverage()&&r),r},null,null)),Qu(4,16384,null,0,Lf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n){return[n]},[Lf]),Qu(6,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(8,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(9,0,null,null,3,"option",[["value",""]],null,null,null,null,null)),Qu(10,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(11,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(12,null,["",""])),(n()(),iu(16777216,null,null,1,null,gp)),Qu(14,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),iu(16777216,null,null,1,null,vp)),Qu(16,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,bp)),Qu(18,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component;n(t,6,0,e.settings.historyComparisionDate),n(t,10,0,""),n(t,11,0,""),n(t,14,0,e.historicCoverageExecutionTimes),n(t,16,0,""!==e.settings.historyComparisionDate),n(t,18,0,""!==e.settings.historyComparisionDate)},function(n,t){var e=t.component;n(t,2,0,e.translations.compareHistory),n(t,3,0,Vu(t,8).ngClassUntouched,Vu(t,8).ngClassTouched,Vu(t,8).ngClassPristine,Vu(t,8).ngClassDirty,Vu(t,8).ngClassValid,Vu(t,8).ngClassInvalid,Vu(t,8).ngClassPending),n(t,12,0,e.translations.date)})}function wp(n){return bs(0,[(n()(),lu(0,0,null,null,0,"col",[["class","column98"]],null,null,null,null,null))],null,null)}function Cp(n){return bs(0,[(n()(),lu(0,0,null,null,0,"col",[["class","column112"]],null,null,null,null,null))],null,null)}function Ep(n){return bs(0,[(n()(),lu(0,0,null,null,5,"th",[["class","center"],["colspan","2"]],null,null,null,null,null)),(n()(),lu(1,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("coverage",e)&&r),r},null,null)),(n()(),lu(2,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(3,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(4,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(5,null,["",""]))],function(n,t){var e=t.component,r=n(t,4,0,"coverage"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"coverage"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"coverage"!==e.settings.sortBy);n(t,3,0,"icon-down-dir",r)},function(n,t){n(t,5,0,t.component.translations.coverage)})}function xp(n){return bs(0,[(n()(),lu(0,0,null,null,5,"th",[["class","center"],["colspan","2"]],null,null,null,null,null)),(n()(),lu(1,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("branchcoverage",e)&&r),r},null,null)),(n()(),lu(2,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(3,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(4,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(5,null,["",""]))],function(n,t){var e=t.component,r=n(t,4,0,"branchcoverage"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"branchcoverage"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"branchcoverage"!==e.settings.sortBy);n(t,3,0,"icon-down-dir",r)},function(n,t){n(t,5,0,t.component.translations.branchCoverage)})}function Op(n){return bs(0,[(n()(),lu(0,0,null,null,1,"tr",[["codeelement-row",""]],null,null,null,zh,Rh)),Qu(1,49152,null,0,Hh,[],{element:[0,"element"],collapsed:[1,"collapsed"],branchCoverageAvailable:[2,"branchCoverageAvailable"]},null)],function(n,t){n(t,1,0,t.parent.context.$implicit,t.parent.context.$implicit.collapsed,t.component.branchCoverageAvailable)},null)}function kp(n){return bs(0,[(n()(),lu(0,0,null,null,1,"tr",[["class-row",""]],null,null,null,cp,Zh)),Qu(1,49152,null,0,Gh,[],{clazz:[0,"clazz"],translations:[1,"translations"],branchCoverageAvailable:[2,"branchCoverageAvailable"],historyComparisionDate:[3,"historyComparisionDate"]},null)],function(n,t){var e=t.component;n(t,1,0,t.parent.context.$implicit,e.translations,e.branchCoverageAvailable,e.settings.historyComparisionDate)},null)}function Sp(n){return bs(0,[(n()(),lu(0,0,null,null,2,null,null,null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,kp)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component,r=!t.parent.context.$implicit.collapsed&&t.context.$implicit.visible(e.settings.filter,e.settings.historyComparisionType);n(t,2,0,r)},null)}function Ap(n){return bs(0,[(n()(),lu(0,0,null,null,1,"tr",[["class","namespace"],["class-row",""]],null,null,null,cp,Zh)),Qu(1,49152,null,0,Gh,[],{clazz:[0,"clazz"],translations:[1,"translations"],branchCoverageAvailable:[2,"branchCoverageAvailable"],historyComparisionDate:[3,"historyComparisionDate"]},null)],function(n,t){var e=t.component;n(t,1,0,t.parent.context.$implicit,e.translations,e.branchCoverageAvailable,e.settings.historyComparisionDate)},null)}function Tp(n){return bs(0,[(n()(),lu(0,0,null,null,2,null,null,null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Ap)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component,r=!t.parent.parent.context.$implicit.collapsed&&t.context.$implicit.visible(e.settings.filter,e.settings.historyComparisionType);n(t,2,0,r)},null)}function Ip(n){return bs(0,[(n()(),lu(0,0,null,null,4,null,null,null,null,null,null,null)),(n()(),lu(1,0,null,null,1,"tr",[["class","namespace"],["codeelement-row",""]],null,null,null,zh,Rh)),Qu(2,49152,null,0,Hh,[],{element:[0,"element"],collapsed:[1,"collapsed"],branchCoverageAvailable:[2,"branchCoverageAvailable"]},null),(n()(),iu(16777216,null,null,1,null,Tp)),Qu(4,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),iu(0,null,null,0))],function(n,t){n(t,2,0,t.parent.context.$implicit,t.parent.context.$implicit.collapsed,t.component.branchCoverageAvailable),n(t,4,0,t.parent.context.$implicit.classes)},null)}function Pp(n){return bs(0,[(n()(),lu(0,0,null,null,2,null,null,null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Ip)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component,r=!t.parent.context.$implicit.collapsed&&t.context.$implicit.visible(e.settings.filter,e.settings.historyComparisionType);n(t,2,0,r)},null)}function Np(n){return bs(0,[(n()(),lu(0,0,null,null,6,null,null,null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Op)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Sp)),Qu(4,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),iu(16777216,null,null,1,null,Pp)),Qu(6,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component,r=t.context.$implicit.visible(e.settings.filter,e.settings.historyComparisionType);n(t,2,0,r),n(t,4,0,t.context.$implicit.classes),n(t,6,0,t.context.$implicit.subElements)},null)}function Dp(n){return bs(0,[(n()(),lu(0,0,null,null,87,"div",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,34,"div",[["class","customizebox"]],null,null,null,null,null)),(n()(),lu(2,0,null,null,5,"div",[],null,null,null,null,null)),(n()(),lu(3,0,null,null,1,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.collapseAll(e)&&r),r},null,null)),(n()(),vs(4,null,["",""])),(n()(),vs(-1,null,[" | "])),(n()(),lu(6,0,null,null,1,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.expandAll(e)&&r),r},null,null)),(n()(),vs(7,null,["",""])),(n()(),lu(8,0,null,null,15,"div",[["class","center"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,hp)),Qu(10,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,pp)),Qu(12,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,dp)),Qu(14,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(15,0,null,null,0,"br",[],null,null,null,null,null)),(n()(),vs(16,null,[" "," "])),(n()(),lu(17,0,null,null,6,"input",[["min","-1"],["step","1"],["type","range"]],[[8,"max",0],[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"input"],[null,"blur"],[null,"compositionstart"],[null,"compositionend"],[null,"change"]],function(n,t,e){var r=!0,o=n.component;return"input"===t&&(r=!1!==Vu(n,18)._handleInput(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,18).onTouched()&&r),"compositionstart"===t&&(r=!1!==Vu(n,18)._compositionStart()&&r),"compositionend"===t&&(r=!1!==Vu(n,18)._compositionEnd(e.target.value)&&r),"change"===t&&(r=!1!==Vu(n,19).onChange(e.target.value)&&r),"input"===t&&(r=!1!==Vu(n,19).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,19).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.grouping=e)&&r),"ngModelChange"===t&&(r=!1!==o.updateCoverageInfo()&&r),r},null,null)),Qu(18,16384,null,0,Sf,[Xr,Qr,[2,kf]],null,null),Qu(19,16384,null,0,Vf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n,t){return[n,t]},[Sf,Vf]),Qu(21,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(23,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(24,0,null,null,2,"div",[["class","center"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,_p)),Qu(26,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(27,0,null,null,8,"div",[["class","right"]],null,null,null,null,null)),(n()(),lu(28,0,null,null,1,"span",[],null,null,null,null,null)),(n()(),vs(29,null,[""," "])),(n()(),lu(30,0,null,null,5,"input",[["type","text"]],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"input"],[null,"blur"],[null,"compositionstart"],[null,"compositionend"]],function(n,t,e){var r=!0,o=n.component;return"input"===t&&(r=!1!==Vu(n,31)._handleInput(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,31).onTouched()&&r),"compositionstart"===t&&(r=!1!==Vu(n,31)._compositionStart()&&r),"compositionend"===t&&(r=!1!==Vu(n,31)._compositionEnd(e.target.value)&&r),"ngModelChange"===t&&(r=!1!==(o.settings.filter=e)&&r),r},null,null)),Qu(31,16384,null,0,Sf,[Xr,Qr,[2,kf]],null,null),Wu(1024,null,xf,function(n){return[n]},[Sf]),Qu(33,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(35,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(36,0,null,null,51,"table",[["class","overview table-fixed stripped"]],null,null,null,null,null)),(n()(),lu(37,0,null,null,11,"colgroup",[],null,null,null,null,null)),(n()(),lu(38,0,null,null,0,"col",[],null,null,null,null,null)),(n()(),lu(39,0,null,null,0,"col",[["class","column90"]],null,null,null,null,null)),(n()(),lu(40,0,null,null,0,"col",[["class","column105"]],null,null,null,null,null)),(n()(),lu(41,0,null,null,0,"col",[["class","column100"]],null,null,null,null,null)),(n()(),lu(42,0,null,null,0,"col",[["class","column70"]],null,null,null,null,null)),(n()(),lu(43,0,null,null,0,"col",[["class","column98"]],null,null,null,null,null)),(n()(),lu(44,0,null,null,0,"col",[["class","column112"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,wp)),Qu(46,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Cp)),Qu(48,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(49,0,null,null,35,"thead",[],null,null,null,null,null)),(n()(),lu(50,0,null,null,34,"tr",[],null,null,null,null,null)),(n()(),lu(51,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(52,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("name",e)&&r),r},null,null)),(n()(),lu(53,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(54,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(55,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(56,null,["",""])),(n()(),lu(57,0,null,null,5,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(58,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("covered",e)&&r),r},null,null)),(n()(),lu(59,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(60,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(61,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(62,null,["",""])),(n()(),lu(63,0,null,null,5,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(64,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("uncovered",e)&&r),r},null,null)),(n()(),lu(65,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(66,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(67,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(68,null,["",""])),(n()(),lu(69,0,null,null,5,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(70,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("coverable",e)&&r),r},null,null)),(n()(),lu(71,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(72,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(73,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(74,null,["",""])),(n()(),lu(75,0,null,null,5,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(76,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("total",e)&&r),r},null,null)),(n()(),lu(77,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(78,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(79,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(80,null,["",""])),(n()(),iu(16777216,null,null,1,null,Ep)),Qu(82,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,xp)),Qu(84,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(85,0,null,null,2,"tbody",[],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Np)),Qu(87,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null)],function(n,t){var e=t.component;n(t,10,0,-1===e.settings.grouping),n(t,12,0,0===e.settings.grouping),n(t,14,0,e.settings.grouping>0),n(t,21,0,e.settings.grouping),n(t,26,0,e.historicCoverageExecutionTimes.length>0),n(t,33,0,e.settings.filter),n(t,46,0,e.branchCoverageAvailable),n(t,48,0,e.branchCoverageAvailable);var r=n(t,55,0,"name"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"name"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"name"!==e.settings.sortBy);n(t,54,0,"icon-down-dir",r);var o=n(t,61,0,"covered"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"covered"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"covered"!==e.settings.sortBy);n(t,60,0,"icon-down-dir",o);var i=n(t,67,0,"uncovered"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"uncovered"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"uncovered"!==e.settings.sortBy);n(t,66,0,"icon-down-dir",i);var l=n(t,73,0,"coverable"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"coverable"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"coverable"!==e.settings.sortBy);n(t,72,0,"icon-down-dir",l);var u=n(t,79,0,"total"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"total"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"total"!==e.settings.sortBy);n(t,78,0,"icon-down-dir",u),n(t,82,0,e.branchCoverageAvailable),n(t,84,0,e.branchCoverageAvailable),n(t,87,0,e.codeElements)},function(n,t){var e=t.component;n(t,4,0,e.translations.collapseAll),n(t,7,0,e.translations.expandAll),n(t,16,0,e.translations.grouping),n(t,17,0,e.settings.groupingMaximum,Vu(t,23).ngClassUntouched,Vu(t,23).ngClassTouched,Vu(t,23).ngClassPristine,Vu(t,23).ngClassDirty,Vu(t,23).ngClassValid,Vu(t,23).ngClassInvalid,Vu(t,23).ngClassPending),n(t,29,0,e.translations.filter),n(t,30,0,Vu(t,35).ngClassUntouched,Vu(t,35).ngClassTouched,Vu(t,35).ngClassPristine,Vu(t,35).ngClassDirty,Vu(t,35).ngClassValid,Vu(t,35).ngClassInvalid,Vu(t,35).ngClassPending),n(t,56,0,e.translations.name),n(t,62,0,e.translations.covered),n(t,68,0,e.translations.uncovered),n(t,74,0,e.translations.coverable),n(t,80,0,e.translations.total)})}function Mp(n){return bs(0,[(n()(),iu(16777216,null,null,1,null,Dp)),Qu(1,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){n(t,1,0,t.component.codeElements.length>0)},null)}function Vp(n){return bs(0,[(n()(),lu(0,0,null,null,1,"coverage-info",[],null,[["window","beforeunload"]],function(n,t,e){var r=!0;return"window:beforeunload"===t&&(r=!1!==Vu(n,1).onDonBeforeUnlodad()&&r),r},Mp,fp)),Qu(1,114688,null,0,ja,[Ta],null,null)],function(n,t){n(t,1,0)},null)}var Hp=xu("coverage-info",ja,Vp,{},{},[]),Rp=ka(Aa,[Na,ja],function(n){return function(n){for(var t={},e=[],r=!1,o=0;odiv { width: 25%; display: inline-block; } +.customizebox div.right input { font-size: 0.8em; width: 150px; } +#namespaceslider { width: 200px; display: inline-block; margin-left: 8px; } + +.percentagebarundefined { + border-left: 2px solid #fff; + padding-left: 3px; +} +.percentagebar0 { + border-left: 2px solid #c10909; + padding-left: 3px; +} +.percentagebar10 { + border-left: 2px solid; + border-image: linear-gradient(to bottom, #c10909 90%, #0aad0a 90%, #0aad0a 100%) 1; + padding-left: 3px; +} +.percentagebar20 { + border-left: 2px solid; + border-image: linear-gradient(to bottom, #c10909 80%, #0aad0a 80%, #0aad0a 100%) 1; + padding-left: 3px; +} +.percentagebar30 { + border-left: 2px solid; + border-image: linear-gradient(to bottom, #c10909 70%, #0aad0a 70%, #0aad0a 100%) 1; + padding-left: 3px; +} +.percentagebar40 { + border-left: 2px solid; + border-image: linear-gradient(to bottom, #c10909 60%, #0aad0a 60%, #0aad0a 100%) 1; + padding-left: 3px; +} +.percentagebar50 { + border-left: 2px solid; + border-image: linear-gradient(to bottom, #c10909 50%, #0aad0a 50%, #0aad0a 100%) 1; + padding-left: 3px; +} +.percentagebar60 { + border-left: 2px solid; + border-image: linear-gradient(to bottom, #c10909 40%, #0aad0a 40%, #0aad0a 100%) 1; + padding-left: 3px; +} +.percentagebar70 { + border-left: 2px solid; + border-image: linear-gradient(to bottom, #c10909 30%, #0aad0a 30%, #0aad0a 100%) 1; + padding-left: 3px; +} +.percentagebar80 { + border-left: 2px solid; + border-image: linear-gradient(to bottom, #c10909 20%, #0aad0a 20%, #0aad0a 100%) 1; + padding-left: 3px; +} +.percentagebar90 { + border-left: 2px solid; + border-image: linear-gradient(to bottom, #c10909 10%, #0aad0a 10%, #0aad0a 100%) 1; + padding-left: 3px; +} +.percentagebar100 { + border-left: 2px solid #0aad0a; + padding-left: 3px; +} + +.hidden, .ng-hide { display: none; } +.right { text-align: right; } +.center { text-align: center; } +.rightmargin { padding-right: 8px; } +.leftmargin { padding-left: 5px; } +.green { background-color: #0aad0a; } +.lightgreen { background-color: #dcf4dc; } +.red { background-color: #c10909; } +.lightred { background-color: #f7dede; } +.orange { background-color: #FFA500; } +.lightorange { background-color: #FFEFD5; } +.gray { background-color: #dcdcdc; } +.lightgray { color: #888888; } +.lightgraybg { background-color: #dadada; } + +.toggleZoom { text-align:right; } + +.ct-chart { position: relative; } +.ct-chart .ct-line { stroke-width: 2px !important; } +.ct-chart .ct-point { stroke-width: 6px !important; transition: stroke-width .2s; } +.ct-chart .ct-point:hover { stroke-width: 10px !important; } +.ct-chart .ct-series.ct-series-a .ct-line, .ct-chart .ct-series.ct-series-a .ct-point { stroke: #c00 !important;} +.ct-chart .ct-series.ct-series-b .ct-line, .ct-chart .ct-series.ct-series-b .ct-point { stroke: #1c2298 !important;} + +.tinylinecoveragechart, .tinybranchcoveragechart { background-color: #fff; margin-left: -3px; float: left; border: solid 1px #c1c1c1; width: 30px; height: 18px; } +.historiccoverageoffset { margin-top: 7px; } + +.tinylinecoveragechart .ct-line, .tinybranchcoveragechart .ct-line { stroke-width: 1px !important; } +.tinybranchcoveragechart .ct-series.ct-series-a .ct-line { stroke: #1c2298 !important; } + +.linecoverage { background-color: #c00; width: 10px; height: 8px; border: 1px solid #000; display: inline-block; } +.branchcoverage { background-color: #1c2298; width: 10px; height: 8px; border: 1px solid #000; display: inline-block; } + +.tooltip { position: absolute; display: none; padding: 5px; background: #F4C63D;color: #453D3F; pointer-events: none; z-index: 1; } +.tooltip:after { content: ""; position: absolute; top: 100%; left: 50%; width: 0; height: 0; margin-left: -15px; border: 15px solid transparent; border-top-color: #F4C63D; } + +.column1324 { max-width: 1324px; } +.column674 { max-width: 674px; } +.column60 { width: 60px; } +.column70 { width: 70px; } +.column90 { width: 90px; } +.column98 { width: 98px; } +.column100 { width: 100px; } +.column105 { width: 105px; } +.column112 { width: 112px; } +.column135 { width: 135px; } +.column150 { width: 150px; } + +.covered0 { width: 0px; } +.covered1 { width: 1px; } +.covered2 { width: 2px; } +.covered3 { width: 3px; } +.covered4 { width: 4px; } +.covered5 { width: 5px; } +.covered6 { width: 6px; } +.covered7 { width: 7px; } +.covered8 { width: 8px; } +.covered9 { width: 9px; } +.covered10 { width: 10px; } +.covered11 { width: 11px; } +.covered12 { width: 12px; } +.covered13 { width: 13px; } +.covered14 { width: 14px; } +.covered15 { width: 15px; } +.covered16 { width: 16px; } +.covered17 { width: 17px; } +.covered18 { width: 18px; } +.covered19 { width: 19px; } +.covered20 { width: 20px; } +.covered21 { width: 21px; } +.covered22 { width: 22px; } +.covered23 { width: 23px; } +.covered24 { width: 24px; } +.covered25 { width: 25px; } +.covered26 { width: 26px; } +.covered27 { width: 27px; } +.covered28 { width: 28px; } +.covered29 { width: 29px; } +.covered30 { width: 30px; } +.covered31 { width: 31px; } +.covered32 { width: 32px; } +.covered33 { width: 33px; } +.covered34 { width: 34px; } +.covered35 { width: 35px; } +.covered36 { width: 36px; } +.covered37 { width: 37px; } +.covered38 { width: 38px; } +.covered39 { width: 39px; } +.covered40 { width: 40px; } +.covered41 { width: 41px; } +.covered42 { width: 42px; } +.covered43 { width: 43px; } +.covered44 { width: 44px; } +.covered45 { width: 45px; } +.covered46 { width: 46px; } +.covered47 { width: 47px; } +.covered48 { width: 48px; } +.covered49 { width: 49px; } +.covered50 { width: 50px; } +.covered51 { width: 51px; } +.covered52 { width: 52px; } +.covered53 { width: 53px; } +.covered54 { width: 54px; } +.covered55 { width: 55px; } +.covered56 { width: 56px; } +.covered57 { width: 57px; } +.covered58 { width: 58px; } +.covered59 { width: 59px; } +.covered60 { width: 60px; } +.covered61 { width: 61px; } +.covered62 { width: 62px; } +.covered63 { width: 63px; } +.covered64 { width: 64px; } +.covered65 { width: 65px; } +.covered66 { width: 66px; } +.covered67 { width: 67px; } +.covered68 { width: 68px; } +.covered69 { width: 69px; } +.covered70 { width: 70px; } +.covered71 { width: 71px; } +.covered72 { width: 72px; } +.covered73 { width: 73px; } +.covered74 { width: 74px; } +.covered75 { width: 75px; } +.covered76 { width: 76px; } +.covered77 { width: 77px; } +.covered78 { width: 78px; } +.covered79 { width: 79px; } +.covered80 { width: 80px; } +.covered81 { width: 81px; } +.covered82 { width: 82px; } +.covered83 { width: 83px; } +.covered84 { width: 84px; } +.covered85 { width: 85px; } +.covered86 { width: 86px; } +.covered87 { width: 87px; } +.covered88 { width: 88px; } +.covered89 { width: 89px; } +.covered90 { width: 90px; } +.covered91 { width: 91px; } +.covered92 { width: 92px; } +.covered93 { width: 93px; } +.covered94 { width: 94px; } +.covered95 { width: 95px; } +.covered96 { width: 96px; } +.covered97 { width: 97px; } +.covered98 { width: 98px; } +.covered99 { width: 99px; } +.covered100 { width: 100px; } + + @media print { + html, body { background-color: #fff; } + .container { max-width: 100%; width: 100%; padding: 0; } + .overview colgroup col:first-child { width: 300px; } +} + +.icon-up-dir_active { + background-image: url(icon_up-dir.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiNjMDAiIGQ9Ik0xNDA4IDEyMTZxMCAyNi0xOSA0NXQtNDUgMTloLTg5NnEtMjYgMC00NS0xOXQtMTktNDUgMTktNDVsNDQ4LTQ0OHExOS0xOSA0NS0xOXQ0NSAxOWw0NDggNDQ4cTE5IDE5IDE5IDQ1eiIvPjwvc3ZnPg==); + background-repeat: no-repeat; + background-size: contain; + padding-left: 15px; + height: 0.9em; + display: inline-block; + position: relative; + top: 3px; +} +.icon-down-dir_active { + background-image: url(icon_up-dir_active.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiNjMDAiIGQ9Ik0xNDA4IDcwNHEwIDI2LTE5IDQ1bC00NDggNDQ4cS0xOSAxOS00NSAxOXQtNDUtMTlsLTQ0OC00NDhxLTE5LTE5LTE5LTQ1dDE5LTQ1IDQ1LTE5aDg5NnEyNiAwIDQ1IDE5dDE5IDQ1eiIvPjwvc3ZnPg==); + background-repeat: no-repeat; + background-size: contain; + padding-left: 15px; + height: 0.9em; + display: inline-block; + position: relative; + top: 3px; +} +.icon-down-dir { + background-image: url(icon_down-dir_active.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNDA4IDcwNHEwIDI2LTE5IDQ1bC00NDggNDQ4cS0xOSAxOS00NSAxOXQtNDUtMTlsLTQ0OC00NDhxLTE5LTE5LTE5LTQ1dDE5LTQ1IDQ1LTE5aDg5NnEyNiAwIDQ1IDE5dDE5IDQ1eiIvPjwvc3ZnPg==); + background-repeat: no-repeat; + background-size: contain; + padding-left: 15px; + height: 0.9em; + display: inline-block; + position: relative; + top: 3px; +} +.icon-info-circled { + background-image: url(icon_info-circled.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxjaXJjbGUgY3g9Ijg5NiIgY3k9Ijg5NiIgcj0iNzUwIiBmaWxsPSIjZmZmIiAvPjxwYXRoIGZpbGw9IiMyOEE1RkYiIGQ9Ik0xMTUyIDEzNzZ2LTE2MHEwLTE0LTktMjN0LTIzLTloLTk2di01MTJxMC0xNC05LTIzdC0yMy05aC0zMjBxLTE0IDAtMjMgOXQtOSAyM3YxNjBxMCAxNCA5IDIzdDIzIDloOTZ2MzIwaC05NnEtMTQgMC0yMyA5dC05IDIzdjE2MHEwIDE0IDkgMjN0MjMgOWg0NDhxMTQgMCAyMy05dDktMjN6bS0xMjgtODk2di0xNjBxMC0xNC05LTIzdC0yMy05aC0xOTJxLTE0IDAtMjMgOXQtOSAyM3YxNjBxMCAxNCA5IDIzdDIzIDloMTkycTE0IDAgMjMtOXQ5LTIzem02NDAgNDE2cTAgMjA5LTEwMyAzODUuNXQtMjc5LjUgMjc5LjUtMzg1LjUgMTAzLTM4NS41LTEwMy0yNzkuNS0yNzkuNS0xMDMtMzg1LjUgMTAzLTM4NS41IDI3OS41LTI3OS41IDM4NS41LTEwMyAzODUuNSAxMDMgMjc5LjUgMjc5LjUgMTAzIDM4NS41eiIvPjwvc3ZnPg==); + background-repeat: no-repeat; + background-size: contain; + padding-left: 15px; + height: 0.9em; + display: inline-block; +} +.icon-plus { + background-image: url(icon_plus.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNjAwIDczNnYxOTJxMCA0MC0yOCA2OHQtNjggMjhoLTQxNnY0MTZxMCA0MC0yOCA2OHQtNjggMjhoLTE5MnEtNDAgMC02OC0yOHQtMjgtNjh2LTQxNmgtNDE2cS00MCAwLTY4LTI4dC0yOC02OHYtMTkycTAtNDAgMjgtNjh0NjgtMjhoNDE2di00MTZxMC00MCAyOC02OHQ2OC0yOGgxOTJxNDAgMCA2OCAyOHQyOCA2OHY0MTZoNDE2cTQwIDAgNjggMjh0MjggNjh6Ii8+PC9zdmc+); + background-repeat: no-repeat; + background-size: contain; + padding-left: 15px; + height: 0.9em; + display: inline-block; + position: relative; + top: 3px; +} +.icon-minus { + background-image: url(icon_minus.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiNjMDAiIGQ9Ik0xNjAwIDczNnYxOTJxMCA0MC0yOCA2OHQtNjggMjhoLTEyMTZxLTQwIDAtNjgtMjh0LTI4LTY4di0xOTJxMC00MCAyOC02OHQ2OC0yOGgxMjE2cTQwIDAgNjggMjh0MjggNjh6Ii8+PC9zdmc+); + background-repeat: no-repeat; + background-size: contain; + padding-left: 15px; + height: 0.9em; + display: inline-block; + position: relative; + top: 3px; +} +.icon-wrench { + background-image: url(icon_wrench.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxNzkyIiBoZWlnaHQ9IjE3OTIiIHN0eWxlPSJmaWxsOiNlNWU1ZTUiIC8+PHBhdGggZD0iTTQ0OCAxNDcycTAtMjYtMTktNDV0LTQ1LTE5LTQ1IDE5LTE5IDQ1IDE5IDQ1IDQ1IDE5IDQ1LTE5IDE5LTQ1em02NDQtNDIwbC02ODIgNjgycS0zNyAzNy05MCAzNy01MiAwLTkxLTM3bC0xMDYtMTA4cS0zOC0zNi0zOC05MCAwLTUzIDM4LTkxbDY4MS02ODFxMzkgOTggMTE0LjUgMTczLjV0MTczLjUgMTE0LjV6bTYzNC00MzVxMCAzOS0yMyAxMDYtNDcgMTM0LTE2NC41IDIxNy41dC0yNTguNSA4My41cS0xODUgMC0zMTYuNS0xMzEuNXQtMTMxLjUtMzE2LjUgMTMxLjUtMzE2LjUgMzE2LjUtMTMxLjVxNTggMCAxMjEuNSAxNi41dDEwNy41IDQ2LjVxMTYgMTEgMTYgMjh0LTE2IDI4bC0yOTMgMTY5djIyNGwxOTMgMTA3cTUtMyA3OS00OC41dDEzNS41LTgxIDcwLjUtMzUuNXExNSAwIDIzLjUgMTB0OC41IDI1eiIvPjwvc3ZnPg==); + background-repeat: no-repeat; + background-size: contain; + padding-left: 20px; + height: 0.9em; + display: inline-block; +} +.icon-fork { + background-image: url(icon_fork.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxNzkyIiBoZWlnaHQ9IjE3OTIiIHN0eWxlPSJmaWxsOiNmZmYiIC8+PHBhdGggZD0iTTY3MiAxNDcycTAtNDAtMjgtNjh0LTY4LTI4LTY4IDI4LTI4IDY4IDI4IDY4IDY4IDI4IDY4LTI4IDI4LTY4em0wLTExNTJxMC00MC0yOC02OHQtNjgtMjgtNjggMjgtMjggNjggMjggNjggNjggMjggNjgtMjggMjgtNjh6bTY0MCAxMjhxMC00MC0yOC02OHQtNjgtMjgtNjggMjgtMjggNjggMjggNjggNjggMjggNjgtMjggMjgtNjh6bTk2IDBxMCA1Mi0yNiA5Ni41dC03MCA2OS41cS0yIDI4Ny0yMjYgNDE0LTY3IDM4LTIwMyA4MS0xMjggNDAtMTY5LjUgNzF0LTQxLjUgMTAwdjI2cTQ0IDI1IDcwIDY5LjV0MjYgOTYuNXEwIDgwLTU2IDEzNnQtMTM2IDU2LTEzNi01Ni01Ni0xMzZxMC01MiAyNi05Ni41dDcwLTY5LjV2LTgyMHEtNDQtMjUtNzAtNjkuNXQtMjYtOTYuNXEwLTgwIDU2LTEzNnQxMzYtNTYgMTM2IDU2IDU2IDEzNnEwIDUyLTI2IDk2LjV0LTcwIDY5LjV2NDk3cTU0LTI2IDE1NC01NyA1NS0xNyA4Ny41LTI5LjV0NzAuNS0zMSA1OS0zOS41IDQwLjUtNTEgMjgtNjkuNSA4LjUtOTEuNXEtNDQtMjUtNzAtNjkuNXQtMjYtOTYuNXEwLTgwIDU2LTEzNnQxMzYtNTYgMTM2IDU2IDU2IDEzNnoiLz48L3N2Zz4=); + background-repeat: no-repeat; + background-size: contain; + padding-left: 20px; + height: 0.9em; + display: inline-block; +} +.icon-cube { + background-image: url(icon_cube.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxNzkyIiBoZWlnaHQ9IjE3OTIiIHN0eWxlPSJmaWxsOiNlNWU1ZTUiIC8+PHBhdGggZD0iTTg5NiAxNjI5bDY0MC0zNDl2LTYzNmwtNjQwIDIzM3Y3NTJ6bS02NC04NjVsNjk4LTI1NC02OTgtMjU0LTY5OCAyNTR6bTgzMi0yNTJ2NzY4cTAgMzUtMTggNjV0LTQ5IDQ3bC03MDQgMzg0cS0yOCAxNi02MSAxNnQtNjEtMTZsLTcwNC0zODRxLTMxLTE3LTQ5LTQ3dC0xOC02NXYtNzY4cTAtNDAgMjMtNzN0NjEtNDdsNzA0LTI1NnEyMi04IDQ0LTh0NDQgOGw3MDQgMjU2cTM4IDE0IDYxIDQ3dDIzIDczeiIvPjwvc3ZnPg==); + background-repeat: no-repeat; + background-size: contain; + padding-left: 20px; + height: 0.9em; + display: inline-block; +} +.icon-search-plus { + background-image: url(icon_search-plus.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiM2ZjZmNmYiIGQ9Ik0xMDg4IDgwMHY2NHEwIDEzLTkuNSAyMi41dC0yMi41IDkuNWgtMjI0djIyNHEwIDEzLTkuNSAyMi41dC0yMi41IDkuNWgtNjRxLTEzIDAtMjIuNS05LjV0LTkuNS0yMi41di0yMjRoLTIyNHEtMTMgMC0yMi41LTkuNXQtOS41LTIyLjV2LTY0cTAtMTMgOS41LTIyLjV0MjIuNS05LjVoMjI0di0yMjRxMC0xMyA5LjUtMjIuNXQyMi41LTkuNWg2NHExMyAwIDIyLjUgOS41dDkuNSAyMi41djIyNGgyMjRxMTMgMCAyMi41IDkuNXQ5LjUgMjIuNXptMTI4IDMycTAtMTg1LTEzMS41LTMxNi41dC0zMTYuNS0xMzEuNS0zMTYuNSAxMzEuNS0xMzEuNSAzMTYuNSAxMzEuNSAzMTYuNSAzMTYuNSAxMzEuNSAzMTYuNS0xMzEuNSAxMzEuNS0zMTYuNXptNTEyIDgzMnEwIDUzLTM3LjUgOTAuNXQtOTAuNSAzNy41cS01NCAwLTkwLTM4bC0zNDMtMzQycS0xNzkgMTI0LTM5OSAxMjQtMTQzIDAtMjczLjUtNTUuNXQtMjI1LTE1MC0xNTAtMjI1LTU1LjUtMjczLjUgNTUuNS0yNzMuNSAxNTAtMjI1IDIyNS0xNTAgMjczLjUtNTUuNSAyNzMuNSA1NS41IDIyNSAxNTAgMTUwIDIyNSA1NS41IDI3My41cTAgMjIwLTEyNCAzOTlsMzQzIDM0M3EzNyAzNyAzNyA5MHoiLz48L3N2Zz4=); + background-repeat: no-repeat; + background-size: contain; + padding-left: 20px; + height: 0.9em; + display: inline-block; +} +.icon-search-minus { + background-image: url(icon_search-minus.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiM2ZjZmNmYiIGQ9Ik0xMDg4IDgwMHY2NHEwIDEzLTkuNSAyMi41dC0yMi41IDkuNWgtNTc2cS0xMyAwLTIyLjUtOS41dC05LjUtMjIuNXYtNjRxMC0xMyA5LjUtMjIuNXQyMi41LTkuNWg1NzZxMTMgMCAyMi41IDkuNXQ5LjUgMjIuNXptMTI4IDMycTAtMTg1LTEzMS41LTMxNi41dC0zMTYuNS0xMzEuNS0zMTYuNSAxMzEuNS0xMzEuNSAzMTYuNSAxMzEuNSAzMTYuNSAzMTYuNSAxMzEuNSAzMTYuNS0xMzEuNSAxMzEuNS0zMTYuNXptNTEyIDgzMnEwIDUzLTM3LjUgOTAuNXQtOTAuNSAzNy41cS01NCAwLTkwLTM4bC0zNDMtMzQycS0xNzkgMTI0LTM5OSAxMjQtMTQzIDAtMjczLjUtNTUuNXQtMjI1LTE1MC0xNTAtMjI1LTU1LjUtMjczLjUgNTUuNS0yNzMuNSAxNTAtMjI1IDIyNS0xNTAgMjczLjUtNTUuNSAyNzMuNSA1NS41IDIyNSAxNTAgMTUwIDIyNSA1NS41IDI3My41cTAgMjIwLTEyNCAzOTlsMzQzIDM0M3EzNyAzNyAzNyA5MHoiLz48L3N2Zz4=); + background-repeat: no-repeat; + background-size: contain; + padding-left: 20px; + height: 0.9em; + display: inline-block; +} + +.ct-double-octave:after,.ct-major-eleventh:after,.ct-major-second:after,.ct-major-seventh:after,.ct-major-sixth:after,.ct-major-tenth:after,.ct-major-third:after,.ct-major-twelfth:after,.ct-minor-second:after,.ct-minor-seventh:after,.ct-minor-sixth:after,.ct-minor-third:after,.ct-octave:after,.ct-perfect-fifth:after,.ct-perfect-fourth:after,.ct-square:after{content:"";clear:both}.ct-label{fill:rgba(0,0,0,.4);color:rgba(0,0,0,.4);font-size:.75rem;line-height:1}.ct-grid-background,.ct-line{fill:none}.ct-chart-bar .ct-label,.ct-chart-line .ct-label{display:block;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}.ct-chart-donut .ct-label,.ct-chart-pie .ct-label{dominant-baseline:central}.ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label.ct-vertical.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-end;-webkit-justify-content:flex-end;-ms-flex-pack:flex-end;justify-content:flex-end;text-align:right;text-anchor:end}.ct-label.ct-vertical.ct-end{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar .ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;text-anchor:start}.ct-chart-bar .ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-start{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:flex-end;-webkit-justify-content:flex-end;-ms-flex-pack:flex-end;justify-content:flex-end;text-align:right;text-anchor:end}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-end{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:end}.ct-grid{stroke:rgba(0,0,0,.2);stroke-width:1px;stroke-dasharray:2px}.ct-point{stroke-width:10px;stroke-linecap:round}.ct-line{stroke-width:4px}.ct-area{stroke:none;fill-opacity:.1}.ct-bar{fill:none;stroke-width:10px}.ct-slice-donut{fill:none;stroke-width:60px}.ct-series-a .ct-bar,.ct-series-a .ct-line,.ct-series-a .ct-point,.ct-series-a .ct-slice-donut{stroke:#d70206}.ct-series-a .ct-area,.ct-series-a .ct-slice-donut-solid,.ct-series-a .ct-slice-pie{fill:#d70206}.ct-series-b .ct-bar,.ct-series-b .ct-line,.ct-series-b .ct-point,.ct-series-b .ct-slice-donut{stroke:#f05b4f}.ct-series-b .ct-area,.ct-series-b .ct-slice-donut-solid,.ct-series-b .ct-slice-pie{fill:#f05b4f}.ct-series-c .ct-bar,.ct-series-c .ct-line,.ct-series-c .ct-point,.ct-series-c .ct-slice-donut{stroke:#f4c63d}.ct-series-c .ct-area,.ct-series-c .ct-slice-donut-solid,.ct-series-c .ct-slice-pie{fill:#f4c63d}.ct-series-d .ct-bar,.ct-series-d .ct-line,.ct-series-d .ct-point,.ct-series-d .ct-slice-donut{stroke:#d17905}.ct-series-d .ct-area,.ct-series-d .ct-slice-donut-solid,.ct-series-d .ct-slice-pie{fill:#d17905}.ct-series-e .ct-bar,.ct-series-e .ct-line,.ct-series-e .ct-point,.ct-series-e .ct-slice-donut{stroke:#453d3f}.ct-series-e .ct-area,.ct-series-e .ct-slice-donut-solid,.ct-series-e .ct-slice-pie{fill:#453d3f}.ct-series-f .ct-bar,.ct-series-f .ct-line,.ct-series-f .ct-point,.ct-series-f .ct-slice-donut{stroke:#59922b}.ct-series-f .ct-area,.ct-series-f .ct-slice-donut-solid,.ct-series-f .ct-slice-pie{fill:#59922b}.ct-series-g .ct-bar,.ct-series-g .ct-line,.ct-series-g .ct-point,.ct-series-g .ct-slice-donut{stroke:#0544d3}.ct-series-g .ct-area,.ct-series-g .ct-slice-donut-solid,.ct-series-g .ct-slice-pie{fill:#0544d3}.ct-series-h .ct-bar,.ct-series-h .ct-line,.ct-series-h .ct-point,.ct-series-h .ct-slice-donut{stroke:#6b0392}.ct-series-h .ct-area,.ct-series-h .ct-slice-donut-solid,.ct-series-h .ct-slice-pie{fill:#6b0392}.ct-series-i .ct-bar,.ct-series-i .ct-line,.ct-series-i .ct-point,.ct-series-i .ct-slice-donut{stroke:#f05b4f}.ct-series-i .ct-area,.ct-series-i .ct-slice-donut-solid,.ct-series-i .ct-slice-pie{fill:#f05b4f}.ct-series-j .ct-bar,.ct-series-j .ct-line,.ct-series-j .ct-point,.ct-series-j .ct-slice-donut{stroke:#dda458}.ct-series-j .ct-area,.ct-series-j .ct-slice-donut-solid,.ct-series-j .ct-slice-pie{fill:#dda458}.ct-series-k .ct-bar,.ct-series-k .ct-line,.ct-series-k .ct-point,.ct-series-k .ct-slice-donut{stroke:#eacf7d}.ct-series-k .ct-area,.ct-series-k .ct-slice-donut-solid,.ct-series-k .ct-slice-pie{fill:#eacf7d}.ct-series-l .ct-bar,.ct-series-l .ct-line,.ct-series-l .ct-point,.ct-series-l .ct-slice-donut{stroke:#86797d}.ct-series-l .ct-area,.ct-series-l .ct-slice-donut-solid,.ct-series-l .ct-slice-pie{fill:#86797d}.ct-series-m .ct-bar,.ct-series-m .ct-line,.ct-series-m .ct-point,.ct-series-m .ct-slice-donut{stroke:#b2c326}.ct-series-m .ct-area,.ct-series-m .ct-slice-donut-solid,.ct-series-m .ct-slice-pie{fill:#b2c326}.ct-series-n .ct-bar,.ct-series-n .ct-line,.ct-series-n .ct-point,.ct-series-n .ct-slice-donut{stroke:#6188e2}.ct-series-n .ct-area,.ct-series-n .ct-slice-donut-solid,.ct-series-n .ct-slice-pie{fill:#6188e2}.ct-series-o .ct-bar,.ct-series-o .ct-line,.ct-series-o .ct-point,.ct-series-o .ct-slice-donut{stroke:#a748ca}.ct-series-o .ct-area,.ct-series-o .ct-slice-donut-solid,.ct-series-o .ct-slice-pie{fill:#a748ca}.ct-square{display:block;position:relative;width:100%}.ct-square:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:100%}.ct-square:after{display:table}.ct-square>svg{display:block;position:absolute;top:0;left:0}.ct-minor-second{display:block;position:relative;width:100%}.ct-minor-second:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:93.75%}.ct-minor-second:after{display:table}.ct-minor-second>svg{display:block;position:absolute;top:0;left:0}.ct-major-second{display:block;position:relative;width:100%}.ct-major-second:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:88.8888888889%}.ct-major-second:after{display:table}.ct-major-second>svg{display:block;position:absolute;top:0;left:0}.ct-minor-third{display:block;position:relative;width:100%}.ct-minor-third:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:83.3333333333%}.ct-minor-third:after{display:table}.ct-minor-third>svg{display:block;position:absolute;top:0;left:0}.ct-major-third{display:block;position:relative;width:100%}.ct-major-third:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:80%}.ct-major-third:after{display:table}.ct-major-third>svg{display:block;position:absolute;top:0;left:0}.ct-perfect-fourth{display:block;position:relative;width:100%}.ct-perfect-fourth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:75%}.ct-perfect-fourth:after{display:table}.ct-perfect-fourth>svg{display:block;position:absolute;top:0;left:0}.ct-perfect-fifth{display:block;position:relative;width:100%}.ct-perfect-fifth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:66.6666666667%}.ct-perfect-fifth:after{display:table}.ct-perfect-fifth>svg{display:block;position:absolute;top:0;left:0}.ct-minor-sixth{display:block;position:relative;width:100%}.ct-minor-sixth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:62.5%}.ct-minor-sixth:after{display:table}.ct-minor-sixth>svg{display:block;position:absolute;top:0;left:0}.ct-golden-section{display:block;position:relative;width:100%}.ct-golden-section:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:61.804697157%}.ct-golden-section:after{content:"";display:table;clear:both}.ct-golden-section>svg{display:block;position:absolute;top:0;left:0}.ct-major-sixth{display:block;position:relative;width:100%}.ct-major-sixth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:60%}.ct-major-sixth:after{display:table}.ct-major-sixth>svg{display:block;position:absolute;top:0;left:0}.ct-minor-seventh{display:block;position:relative;width:100%}.ct-minor-seventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:56.25%}.ct-minor-seventh:after{display:table}.ct-minor-seventh>svg{display:block;position:absolute;top:0;left:0}.ct-major-seventh{display:block;position:relative;width:100%}.ct-major-seventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:53.3333333333%}.ct-major-seventh:after{display:table}.ct-major-seventh>svg{display:block;position:absolute;top:0;left:0}.ct-octave{display:block;position:relative;width:100%}.ct-octave:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:50%}.ct-octave:after{display:table}.ct-octave>svg{display:block;position:absolute;top:0;left:0}.ct-major-tenth{display:block;position:relative;width:100%}.ct-major-tenth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:40%}.ct-major-tenth:after{display:table}.ct-major-tenth>svg{display:block;position:absolute;top:0;left:0}.ct-major-eleventh{display:block;position:relative;width:100%}.ct-major-eleventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:37.5%}.ct-major-eleventh:after{display:table}.ct-major-eleventh>svg{display:block;position:absolute;top:0;left:0}.ct-major-twelfth{display:block;position:relative;width:100%}.ct-major-twelfth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:33.3333333333%}.ct-major-twelfth:after{display:table}.ct-major-twelfth>svg{display:block;position:absolute;top:0;left:0}.ct-double-octave{display:block;position:relative;width:100%}.ct-double-octave:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:25%}.ct-double-octave:after{display:table}.ct-double-octave>svg{display:block;position:absolute;top:0;left:0} \ No newline at end of file diff --git a/Host/ConsoleAppNet5/ConsoleAppNet5.csproj b/Configuration/NetCore/Lab.Config/ConsoleApp1/ConsoleApp1.csproj similarity index 100% rename from Host/ConsoleAppNet5/ConsoleAppNet5.csproj rename to Configuration/NetCore/Lab.Config/ConsoleApp1/ConsoleApp1.csproj diff --git a/Host/NetFx48/Program.cs b/Configuration/NetCore/Lab.Config/ConsoleApp1/Program.cs similarity index 51% rename from Host/NetFx48/Program.cs rename to Configuration/NetCore/Lab.Config/ConsoleApp1/Program.cs index 9aeab0ca..be1b5acd 100644 --- a/Host/NetFx48/Program.cs +++ b/Configuration/NetCore/Lab.Config/ConsoleApp1/Program.cs @@ -1,10 +1,10 @@ using System; -namespace NetFx48 +namespace ConsoleApp1 { - internal class Program + class Program { - private static void Main(string[] args) + static void Main(string[] args) { Console.WriteLine("Hello World!"); } diff --git a/Configuration/NetCore/Lab.Config/ConsoleApp1/Properties/launchSettings.json b/Configuration/NetCore/Lab.Config/ConsoleApp1/Properties/launchSettings.json new file mode 100644 index 00000000..053b519b --- /dev/null +++ b/Configuration/NetCore/Lab.Config/ConsoleApp1/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "ConsoleApp1": { + "commandName": "Project", + "commandLineArgs": "-" + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/Properties/launchSettings.json b/Configuration/NetCore/Lab.Config/NetFx48/Properties/launchSettings.json new file mode 100644 index 00000000..e4d12871 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "NetFx48": { + "commandName": "Project", + "commandLineArgs": "--AppId=1234567890" + } + } +} \ No newline at end of file diff --git a/Host/NetFx48/NetFx48.csproj b/Coravel/Lab.CoravelScheduler/ConsoleApp1/ConsoleApp1.csproj similarity index 59% rename from Host/NetFx48/NetFx48.csproj rename to Coravel/Lab.CoravelScheduler/ConsoleApp1/ConsoleApp1.csproj index bd4231b7..9590466a 100644 --- a/Host/NetFx48/NetFx48.csproj +++ b/Coravel/Lab.CoravelScheduler/ConsoleApp1/ConsoleApp1.csproj @@ -4,8 +4,5 @@ Exe net5.0 - - - diff --git a/Host/ConsoleAppNet5/Program.cs b/Coravel/Lab.CoravelScheduler/ConsoleApp1/Program.cs similarity index 50% rename from Host/ConsoleAppNet5/Program.cs rename to Coravel/Lab.CoravelScheduler/ConsoleApp1/Program.cs index 5c83e5b0..be1b5acd 100644 --- a/Host/ConsoleAppNet5/Program.cs +++ b/Coravel/Lab.CoravelScheduler/ConsoleApp1/Program.cs @@ -1,10 +1,10 @@ using System; -namespace ConsoleAppNet5 +namespace ConsoleApp1 { - internal class Program + class Program { - private static void Main(string[] args) + static void Main(string[] args) { Console.WriteLine("Hello World!"); } diff --git a/Coravel/Lab.CoravelScheduler/WebApi.NetFx48/WebApi.NetFx48.csproj.DotSettings b/Coravel/Lab.CoravelScheduler/WebApi.NetFx48/WebApi.NetFx48.csproj.DotSettings new file mode 100644 index 00000000..37e9881c --- /dev/null +++ b/Coravel/Lab.CoravelScheduler/WebApi.NetFx48/WebApi.NetFx48.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Host/ConsoleAppNet48/AppHost.cs b/Host/ConsoleAppNet48/AppHost.cs deleted file mode 100644 index 8e713810..00000000 --- a/Host/ConsoleAppNet48/AppHost.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace ConsoleAppNet48 -{ - public class AppHost : IHostedService - { - private readonly ILogger logger; - private readonly IHostApplicationLifetime appLifetime; - - public AppHost(ILogger logger, IHostApplicationLifetime appLifetime) - { - this.logger = logger; - this.appLifetime = appLifetime; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - this.logger.LogWarning("App running at: {time}", DateTimeOffset.Now); - - await Task.Yield(); - - this.appLifetime.StopApplication(); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - this.logger.LogWarning("App stopped at: {time}", DateTimeOffset.Now); - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/Host/ConsoleAppNet48/ConsoleAppNet48.csproj b/Host/ConsoleAppNet48/ConsoleAppNet48.csproj deleted file mode 100644 index 50724ce9..00000000 --- a/Host/ConsoleAppNet48/ConsoleAppNet48.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - net48 - - - - - - - diff --git a/Host/ConsoleAppNet48/LabBackgroundService.cs b/Host/ConsoleAppNet48/LabBackgroundService.cs deleted file mode 100644 index ccca500a..00000000 --- a/Host/ConsoleAppNet48/LabBackgroundService.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace ConsoleAppNet48 -{ - public class LabBackgroundService : BackgroundService - { - private readonly ILogger _logger; - - public LabBackgroundService(ILogger logger) - { - this._logger = logger; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - while (!stoppingToken.IsCancellationRequested) - { - this._logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); - await Task.Delay(1000, stoppingToken); - } - } - } -} \ No newline at end of file diff --git a/Host/ConsoleAppNet48/LabHostedService.cs b/Host/ConsoleAppNet48/LabHostedService.cs deleted file mode 100644 index b580eb10..00000000 --- a/Host/ConsoleAppNet48/LabHostedService.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace ConsoleAppNet48 -{ - public class LabHostedService : IHostedService - { - private readonly ILogger _logger; - - public LabHostedService(ILogger logger, - IHostApplicationLifetime lifetime) - { - this._logger = logger; - - lifetime.ApplicationStarted.Register(this.OnStarted); - lifetime.ApplicationStopping.Register(this.OnStopping); - lifetime.ApplicationStopped.Register(this.OnStopped); - } - - public Task StartAsync(CancellationToken cancellationToken) - { - this._logger.LogInformation("1. StartAsync has been called."); - - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - this._logger.LogInformation("4. StopAsync has been called."); - - return Task.CompletedTask; - } - - private void OnStarted() - { - this._logger.LogInformation("2. OnStarted has been called."); - } - - private void OnStopped() - { - this._logger.LogInformation("5. OnStopped has been called."); - } - - private void OnStopping() - { - this._logger.LogInformation("3. OnStopping has been called."); - } - } -} \ No newline at end of file diff --git a/Host/ConsoleAppNet48/Program.cs b/Host/ConsoleAppNet48/Program.cs deleted file mode 100644 index ee76ac29..00000000 --- a/Host/ConsoleAppNet48/Program.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace ConsoleAppNet48 -{ - public class Program - { - public static IHostBuilder CreateHostBuilder(string[] args) - { - return Host.CreateDefaultBuilder(args) - .ConfigureServices((hostContext, services) => - { - services.AddHostedService(); - }); - } - - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - } -} \ No newline at end of file diff --git a/Host/ConsoleAppNetFx48/AppHost.cs b/Host/Lab.MsHost/ConsoleAppNetFx48/AppHost.cs similarity index 100% rename from Host/ConsoleAppNetFx48/AppHost.cs rename to Host/Lab.MsHost/ConsoleAppNetFx48/AppHost.cs diff --git a/Host/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj b/Host/Lab.MsHost/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj similarity index 100% rename from Host/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj rename to Host/Lab.MsHost/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj diff --git a/Host/ConsoleAppNetFx48/LabBackgroundService.cs b/Host/Lab.MsHost/ConsoleAppNetFx48/LabBackgroundService.cs similarity index 100% rename from Host/ConsoleAppNetFx48/LabBackgroundService.cs rename to Host/Lab.MsHost/ConsoleAppNetFx48/LabBackgroundService.cs diff --git a/Host/ConsoleAppNetFx48/LabHostedService.cs b/Host/Lab.MsHost/ConsoleAppNetFx48/LabHostedService.cs similarity index 100% rename from Host/ConsoleAppNetFx48/LabHostedService.cs rename to Host/Lab.MsHost/ConsoleAppNetFx48/LabHostedService.cs diff --git a/Host/ConsoleAppNetFx48/Program.cs b/Host/Lab.MsHost/ConsoleAppNetFx48/Program.cs similarity index 100% rename from Host/ConsoleAppNetFx48/Program.cs rename to Host/Lab.MsHost/ConsoleAppNetFx48/Program.cs diff --git a/Host/Lab.MsHost.sln b/Host/Lab.MsHost/Lab.MsHost.sln similarity index 100% rename from Host/Lab.MsHost.sln rename to Host/Lab.MsHost/Lab.MsHost.sln diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj b/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj new file mode 100644 index 00000000..1a8ca194 --- /dev/null +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj @@ -0,0 +1,17 @@ + + + + net48 + bin + bin\ConsoleAppNetFx48.xml + dotnet-ConsoleAppNetFx48-525DDA0C-18EF-4AE3-A405-A9653AA2D910 + + + + + + + + + + diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/DoThing.cs b/Host/Lab.WorkerService/ConsoleAppNetFx48/DoThing.cs new file mode 100644 index 00000000..a5347b9f --- /dev/null +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/DoThing.cs @@ -0,0 +1,38 @@ +// using System; +// using System.Timers; +// using NLog; +// +// namespace ConsoleAppNetFx48 +// { +// public class DoThing +// { +// private static readonly ILogger s_logger; +// private readonly Timer _timer; +// +// static DoThing() +// { +// if (s_logger == null) +// { +// s_logger = LogManager.GetCurrentClassLogger(); +// } +// } +// +// public DoThing() +// { +// this._timer = new Timer(1000) {AutoReset = true}; +// this._timer.Elapsed += (sender, eventArgs) => Console.WriteLine($"Now Time:{DateTime.Now}"); +// } +// +// public void Start() +// { +// this._timer.Start(); +// s_logger.Trace("Timer Start"); +// } +// +// public void Stop() +// { +// this._timer.Stop(); +// s_logger.Trace("Timer Stop"); +// } +// } +// } \ No newline at end of file diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/Program.cs b/Host/Lab.WorkerService/ConsoleAppNetFx48/Program.cs new file mode 100644 index 00000000..cc743c08 --- /dev/null +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/Program.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace ConsoleAppNetFx48 +{ + public class Program + { + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseWindowsService() + .ConfigureServices((hostContext, services) => + { + services.AddHostedService(); + }); + + public static void Main(string[] args) + { + // HostFactory.Run(x => + // { + // x.Service(s => + // { + // s.ConstructUsing(name => new DoThing()); + // s.WhenStarted(tc => tc.Start()); + // s.WhenStopped(tc => tc.Stop()); + // }); + // x.UseNLog(); + // x.RunAsLocalSystem(); + // var assemblyName = Assembly.GetEntryAssembly().GetName().Name; + // x.SetDescription("Sample Topshelf Host"); + // x.SetDisplayName(assemblyName); + // x.SetServiceName(assemblyName); + // }); + + var hostBuilder = CreateHostBuilder(args); + hostBuilder.Build().Run(); + } + } +} \ No newline at end of file diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/Properties/launchSettings.json b/Host/Lab.WorkerService/ConsoleAppNetFx48/Properties/launchSettings.json new file mode 100644 index 00000000..b1932b1c --- /dev/null +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "ConsoleAppNetFx48": { + "commandName": "Project", + "dotnetRunMessages": "true", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Host/NetFx48/LabBackgroundService.cs b/Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs similarity index 70% rename from Host/NetFx48/LabBackgroundService.cs rename to Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs index 69048bdd..74b3e236 100644 --- a/Host/NetFx48/LabBackgroundService.cs +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs @@ -1,16 +1,16 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace NetFx48 +namespace ConsoleAppNetFx48 { - public class LabBackgroundService : BackgroundService + public class Worker : BackgroundService { - private readonly ILogger _logger; + private readonly ILogger _logger; - public LabBackgroundService(ILogger logger) + public Worker(ILogger logger) { this._logger = logger; } diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/appsettings.Development.json b/Host/Lab.WorkerService/ConsoleAppNetFx48/appsettings.Development.json new file mode 100644 index 00000000..8983e0fc --- /dev/null +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/appsettings.json b/Host/Lab.WorkerService/ConsoleAppNetFx48/appsettings.json new file mode 100644 index 00000000..8983e0fc --- /dev/null +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/Host/Lab.WorkerService/Lab.WorkerService.sln b/Host/Lab.WorkerService/Lab.WorkerService.sln new file mode 100644 index 00000000..80856a97 --- /dev/null +++ b/Host/Lab.WorkerService/Lab.WorkerService.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleAppNetFx48", "ConsoleAppNetFx48\ConsoleAppNetFx48.csproj", "{6E527E3E-6180-4250-B61D-0B69208737C8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6E527E3E-6180-4250-B61D-0B69208737C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E527E3E-6180-4250-B61D-0B69208737C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E527E3E-6180-4250-B61D-0B69208737C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E527E3E-6180-4250-B61D-0B69208737C8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Host/NetFx48/AppHost.cs b/Host/NetFx48/AppHost.cs deleted file mode 100644 index 898c3be4..00000000 --- a/Host/NetFx48/AppHost.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace NetFx48 -{ - public class AppHost : IHostedService - { - private readonly IHostApplicationLifetime appLifetime; - private readonly ILogger logger; - - public AppHost(ILogger logger, IHostApplicationLifetime appLifetime) - { - this.logger = logger; - this.appLifetime = appLifetime; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - this.logger.LogWarning("App running at: {time}", DateTimeOffset.Now); - - await Task.Yield(); - - this.appLifetime.StopApplication(); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - this.logger.LogWarning("App stopped at: {time}", DateTimeOffset.Now); - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/Host/NetFx48/LabHostedService.cs b/Host/NetFx48/LabHostedService.cs deleted file mode 100644 index 92cec54d..00000000 --- a/Host/NetFx48/LabHostedService.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace NetFx48 -{ - public class LabHostedService : IHostedService - { - private readonly ILogger _logger; - - public LabHostedService(ILogger logger, - IHostApplicationLifetime lifetime) - { - this._logger = logger; - - lifetime.ApplicationStarted.Register(this.OnStarted); - lifetime.ApplicationStopping.Register(this.OnStopping); - lifetime.ApplicationStopped.Register(this.OnStopped); - } - - public Task StartAsync(CancellationToken cancellationToken) - { - this._logger.LogInformation("1. StartAsync has been called."); - - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - this._logger.LogInformation("4. StopAsync has been called."); - - return Task.CompletedTask; - } - - private void OnStarted() - { - this._logger.LogInformation("2. OnStarted has been called."); - } - - private void OnStopped() - { - this._logger.LogInformation("5. OnStopped has been called."); - } - - private void OnStopping() - { - this._logger.LogInformation("3. OnStopping has been called."); - } - } -} \ No newline at end of file diff --git a/ORM/Linq2Db/Lab.Linq2Db/Lab.UnitTest/EntityModel/CopyMe.SqlServer.generated.cs b/ORM/Linq2Db/Lab.Linq2Db/Lab.UnitTest/EntityModel/CopyMe.SqlServer.generated.cs new file mode 100644 index 00000000..092071d2 --- /dev/null +++ b/ORM/Linq2Db/Lab.Linq2Db/Lab.UnitTest/EntityModel/CopyMe.SqlServer.generated.cs @@ -0,0 +1,216 @@ +//--------------------------------------------------------------------------------------------------- +// +// This code was generated by T4Model template for T4 (https://github.com/linq2db/linq2db). +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. +// +//--------------------------------------------------------------------------------------------------- + +#pragma warning disable 1591 + +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +using LinqToDB; +using LinqToDB.Common; +using LinqToDB.Data; +using LinqToDB.DataProvider.SqlServer; +using LinqToDB.Extensions; +using LinqToDB.Mapping; + +namespace Lab.EntityModel +{ + /// + /// Database : LabEmployee2 + /// Data Source : (localdb)\mssqllocaldb + /// Server Version : 13.00.4001 + /// + public partial class LabEmployee2DB : LinqToDB.Data.DataConnection + { + public ITable Employees { get { return this.GetTable(); } } + public ITable Identities { get { return this.GetTable(); } } + public ITable Orders { get { return this.GetTable(); } } + + public LabEmployee2DB() + { + InitDataContext(); + InitMappingSchema(); + } + + public LabEmployee2DB(string configuration) + : base(configuration) + { + InitDataContext(); + InitMappingSchema(); + } + + partial void InitDataContext (); + partial void InitMappingSchema(); + + #region FreeTextTable + + public class FreeTextKey + { + public T Key; + public int Rank; + } + + private static MethodInfo _freeTextTableMethod1 = typeof(LabEmployee2DB).GetMethod("FreeTextTable", new Type[] { typeof(string), typeof(string) }); + + [FreeTextTableExpression] + public ITable> FreeTextTable(string field, string text) + { + return this.GetTable>( + this, + _freeTextTableMethod1, + field, + text); + } + + private static MethodInfo _freeTextTableMethod2 = + typeof(LabEmployee2DB).GetMethods() + .Where(m => m.Name == "FreeTextTable" && m.IsGenericMethod && m.GetParameters().Length == 2) + .Where(m => m.GetParameters()[0].ParameterType.IsGenericTypeEx() && m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>)) + .Where(m => m.GetParameters()[1].ParameterType == typeof(string)) + .Single(); + + [FreeTextTableExpression] + public ITable> FreeTextTable(Expression> fieldSelector, string text) + { + return this.GetTable>( + this, + _freeTextTableMethod2, + fieldSelector, + text); + } + + #endregion + } + + [Table(Schema="dbo", Name="Employee")] + public partial class Employee + { + [PrimaryKey, NotNull ] public Guid Id { get; set; } // uniqueidentifier + [Column, Nullable] public string Name { get; set; } // nvarchar(50) + [Column, Nullable] public int? Age { get; set; } // int + [Identity ] public long SequenceId { get; set; } // bigint + [Column, Nullable] public string Remark { get; set; } // nvarchar(50) + + #region Associations + + /// + /// FK_Identity_Employee_Id_BackReference + /// + [Association(ThisKey="Id", OtherKey="EmployeeId", CanBeNull=true, Relationship=Relationship.OneToOne, IsBackReference=true)] + public Identity IdentityId { get; set; } + + /// + /// FK_Order_Employee_id_BackReference + /// + [Association(ThisKey="Id", OtherKey="EmployeeId", CanBeNull=true, Relationship=Relationship.OneToMany, IsBackReference=true)] + public IEnumerable Orderids { get; set; } + + #endregion + } + + [Table(Schema="dbo", Name="Identity")] + public partial class Identity + { + [Column("Employee_Id"), PrimaryKey, NotNull] public Guid EmployeeId { get; set; } // uniqueidentifier + [Column(), NotNull] public string Account { get; set; } // nvarchar(50) + [Column(), NotNull] public string Password { get; set; } // nvarchar(50) + [Column(), Identity ] public long SequenceId { get; set; } // bigint + [Column(), Nullable ] public string Remark { get; set; } // nvarchar(50) + + #region Associations + + /// + /// FK_Identity_Employee_Id + /// + [Association(ThisKey="EmployeeId", OtherKey="Id", CanBeNull=false, Relationship=Relationship.OneToOne, KeyName="FK_Identity_Employee_Id", BackReferenceName="IdentityId")] + public Employee Employee { get; set; } + + #endregion + } + + [Table(Schema="dbo", Name="Order")] + public partial class Order + { + [Column(), PrimaryKey, NotNull] public Guid Id { get; set; } // uniqueidentifier + [Column("Employee_Id"), Nullable ] public Guid? EmployeeId { get; set; } // uniqueidentifier + [Column(), Nullable ] public DateTime? OrderTime { get; set; } // datetime + [Column(), Nullable ] public string Remark { get; set; } // nvarchar(50) + [Column(), Identity ] public long SequenceId { get; set; } // bigint + + #region Associations + + /// + /// FK_Order_Employee_id + /// + [Association(ThisKey="EmployeeId", OtherKey="Id", CanBeNull=true, Relationship=Relationship.ManyToOne, KeyName="FK_Order_Employee_id", BackReferenceName="Orderids")] + public Employee Employee { get; set; } + + #endregion + } + + public static partial class LabEmployee2DBStoredProcedures + { + #region GetAllEmployee + + public static IEnumerable GetAllEmployee(this DataConnection dataConnection) + { + return dataConnection.QueryProc("[dbo].[GetAllEmployee]"); + } + + #endregion + + #region InsertOrUpdateEmployee + + public static int InsertOrUpdateEmployee(this DataConnection dataConnection, Guid? @Id, string @Name, int? @Age, string @Remark) + { + return dataConnection.ExecuteProc("[dbo].[InsertOrUpdateEmployee]", + new DataParameter("@Id", @Id, DataType.Guid), + new DataParameter("@Name", @Name, DataType.NVarChar), + new DataParameter("@Age", @Age, DataType.Int32), + new DataParameter("@Remark", @Remark, DataType.NVarChar)); + } + + #endregion + + #region InsertOrUpdateEmployee2 + + public static int InsertOrUpdateEmployee2(this DataConnection dataConnection, DataTable @EmployeeType) + { + return dataConnection.ExecuteProc("[dbo].[InsertOrUpdateEmployee2]", + new DataParameter("@EmployeeType", @EmployeeType, DataType.Structured){ DbType = "[dbo].[InsertOrUpdateEmployeeType]" }); + } + + #endregion + } + + public static partial class TableExtensions + { + public static Employee Find(this ITable table, Guid Id) + { + return table.FirstOrDefault(t => + t.Id == Id); + } + + public static Identity Find(this ITable table, Guid EmployeeId) + { + return table.FirstOrDefault(t => + t.EmployeeId == EmployeeId); + } + + public static Order Find(this ITable table, Guid Id) + { + return table.FirstOrDefault(t => + t.Id == Id); + } + } +} + +#pragma warning restore 1591 From d3572e71466e4a03d2d41ebcca0025cdd3b4d003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E5=B0=8F=E7=AB=A0?= Date: Wed, 7 Apr 2021 14:08:56 +0800 Subject: [PATCH 039/424] delete --- .../history/2021-03-26_16-34-47_CoverageHistory.xml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/history/2021-03-26_16-34-47_CoverageHistory.xml diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/history/2021-03-26_16-34-47_CoverageHistory.xml b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/history/2021-03-26_16-34-47_CoverageHistory.xml deleted file mode 100644 index 7531ba24..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/history/2021-03-26_16-34-47_CoverageHistory.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file From 9d393854e9a3ace48371d0046cc04bae39c9456e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E5=B0=8F=E7=AB=A0?= Date: Wed, 7 Apr 2021 14:09:18 +0800 Subject: [PATCH 040/424] delete --- .../report/ClassLibrary1_Calculation.htm | 82 -- .../Lab.OpenCoverDemo/report/Cobertura.xml | 105 -- .../report/UnitTestProject1_UnitTest1.htm | 88 -- .../report/UnitTestProject2_UnitTest1.htm | 75 - .../report/badge_linecoverage.png | Bin 2925 -> 0 bytes .../report/badge_linecoverage.svg | 88 -- .../Lab.OpenCoverDemo/report/class.js | 207 --- .../Lab.OpenCoverDemo/report/coverage.xml | 1270 ----------------- .../Lab.OpenCoverDemo/report/icon_cube.svg | 2 - .../report/icon_down-dir_active.svg | 2 - .../Lab.OpenCoverDemo/report/icon_fork.svg | 2 - .../report/icon_info-circled.svg | 2 - .../Lab.OpenCoverDemo/report/icon_minus.svg | 2 - .../Lab.OpenCoverDemo/report/icon_plus.svg | 2 - .../report/icon_search-minus.svg | 2 - .../report/icon_search-plus.svg | 2 - .../Lab.OpenCoverDemo/report/icon_up-dir.svg | 2 - .../report/icon_up-dir_active.svg | 2 - .../Lab.OpenCoverDemo/report/icon_wrench.svg | 2 - .../Lab.OpenCoverDemo/report/index.htm | 71 - .../Lab.OpenCoverDemo/report/main.js | 274 ---- .../Lab.OpenCoverDemo/report/report.css | 357 ----- 22 files changed, 2639 deletions(-) delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/ClassLibrary1_Calculation.htm delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/Cobertura.xml delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject1_UnitTest1.htm delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject2_UnitTest1.htm delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.png delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/class.js delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/coverage.xml delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_cube.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_down-dir_active.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_fork.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_info-circled.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_minus.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_plus.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-minus.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-plus.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir_active.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_wrench.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/index.htm delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/main.js delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/report.css diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/ClassLibrary1_Calculation.htm b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/ClassLibrary1_Calculation.htm deleted file mode 100644 index 6976b753..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/ClassLibrary1_Calculation.htm +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - -ClassLibrary1.Calculation - Coverage Report - -
-

Summary

- ---- - - - - - - - - - - - - - -
Class:ClassLibrary1.Calculation
Assembly:ClassLibrary1
File(s):E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\ClassLibrary1\Calculation.cs
Covered lines:3
Uncovered lines:6
Coverable lines:9
Total lines:18
Line coverage:33.3% (3 of 9)
Covered branches:0
Total branches:0
Tag:Build.2019.11.11
-

Coverage History

-
- -

Metrics

- - - - - - - -
MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage Crap Score
Add(...)10100%100%1
Sub(...)100%0%2
Sub1(...)100%0%2
-

File(s)

-

E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\ClassLibrary1\Calculation.cs

- - - - - - - - - - - - - - - - - - - - - - -
#LineLine coverage
 1namespace ClassLibrary1
 2{
 3    public class Calculation
 4    {
 5        public int Add(int firstNumber, int secondNumber)
 26        {
 27            return firstNumber + secondNumber;
 28        }
 9        public int Sub(int firstNumber, int secondNumber)
 010        {
 011            return firstNumber + secondNumber;
 012        }
 13        public int Sub1(int firstNumber, int secondNumber)
 014        {
 015            return firstNumber + secondNumber;
 016        }
 17    }
 18}
-
-
- - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/Cobertura.xml b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/Cobertura.xml deleted file mode 100644 index 105f9442..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/Cobertura.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject1_UnitTest1.htm b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject1_UnitTest1.htm deleted file mode 100644 index 1ec7556c..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject1_UnitTest1.htm +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - -UnitTestProject1.UnitTest1 - Coverage Report - -
-

Summary

- ---- - - - - - - - - - - - - - -
Class:UnitTestProject1.UnitTest1
Assembly:UnitTestProject1
File(s):E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\UnitTest1.cs
Covered lines:9
Uncovered lines:1
Coverable lines:10
Total lines:26
Line coverage:90% (9 of 10)
Covered branches:0
Total branches:0
Tag:Build.2019.11.11
-

Coverage History

-
- -

Metrics

- - - - - - -
MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage Crap Score
TestMethod1()10100%100%1
TestMethod2()1080%100%1.01
-

File(s)

-

E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\UnitTest1.cs

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#LineLine coverage
 1using System;
 2using ClassLibrary1;
 3using Microsoft.VisualStudio.TestTools.UnitTesting;
 4
 5namespace UnitTestProject1
 6{
 7    [TestClass]
 8    public class UnitTest1
 9    {
 10        [TestMethod]
 11        public void TestMethod1()
 112        {
 113            var calculation = new Calculation();
 114            var actual = calculation.Add(1, 1);
 115            Assert.AreEqual(2,actual);
 116        }
 17
 18        [TestMethod]
 19        public void TestMethod2()
 120        {
 121            var calculation = new Calculation();
 122            var actual = calculation.Add(1, 1);
 123            Assert.AreEqual(1,actual);
 024        }
 25    }
 26}
-
-
-
-

Methods/Properties

-TestMethod1()
-TestMethod2()
-
-
- - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject2_UnitTest1.htm b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject2_UnitTest1.htm deleted file mode 100644 index 7d13ebe2..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject2_UnitTest1.htm +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - -UnitTestProject2.UnitTest1 - Coverage Report - -
-

Summary

- ---- - - - - - - - - - - - - - -
Class:UnitTestProject2.UnitTest1
Assembly:UnitTestProject2
File(s):E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject2\UnitTest1.cs
Covered lines:3
Uncovered lines:0
Coverable lines:3
Total lines:15
Line coverage:100% (3 of 3)
Covered branches:0
Total branches:0
Tag:Build.2019.11.11
-

Coverage History

-
- -

Metrics

- - - - - -
MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage Crap Score
TestMethod1()10100%100%1
-

File(s)

-

E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject2\UnitTest1.cs

- - - - - - - - - - - - - - - - - - - -
#LineLine coverage
 1using System;
 2using Microsoft.VisualStudio.TestTools.UnitTesting;
 3
 4namespace UnitTestProject2
 5{
 6    [TestClass]
 7    public class UnitTest1
 8    {
 9        [TestMethod]
 10        public void TestMethod1()
 111        {
 112            Assert.AreEqual(1,1);
 113        }
 14    }
 15}
-
-
-
-

Methods/Properties

-TestMethod1()
-
-
- - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.png b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.png deleted file mode 100644 index 1d4585190f78c440e6cda1f35623d1780d05fbd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2925 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 diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.svg deleted file mode 100644 index f3f4a54c..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.svg +++ /dev/null @@ -1,88 +0,0 @@ - - - Code coverage - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Generated by: ReportGenerator 4.1.2.0 - - - - Coverage - Coverage - 68.1%68.1% - - - - - Line coverage - - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/class.js b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/class.js deleted file mode 100644 index b82bca96..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/class.js +++ /dev/null @@ -1,207 +0,0 @@ -/* Chartist.js 0.11.0 - * Copyright © 2017 Gion Kunz - * Free to use under either the WTFPL license or the MIT license. - * https://raw.githubusercontent.com/gionkunz/chartist-js/master/LICENSE-WTFPL - * https://raw.githubusercontent.com/gionkunz/chartist-js/master/LICENSE-MIT - */ - -!function (a, b) { "function" == typeof define && define.amd ? define("Chartist", [], function () { return a.Chartist = b() }) : "object" == typeof module && module.exports ? module.exports = b() : a.Chartist = b() }(this, function () { - var a = { version: "0.11.0" }; return function (a, b, c) { "use strict"; c.namespaces = { svg: "http://www.w3.org/2000/svg", xmlns: "http://www.w3.org/2000/xmlns/", xhtml: "http://www.w3.org/1999/xhtml", xlink: "http://www.w3.org/1999/xlink", ct: "http://gionkunz.github.com/chartist-js/ct" }, c.noop = function (a) { return a }, c.alphaNumerate = function (a) { return String.fromCharCode(97 + a % 26) }, c.extend = function (a) { var b, d, e; for (a = a || {}, b = 1; b < arguments.length; b++) { d = arguments[b]; for (var f in d) e = d[f], "object" != typeof e || null === e || e instanceof Array ? a[f] = e : a[f] = c.extend(a[f], e) } return a }, c.replaceAll = function (a, b, c) { return a.replace(new RegExp(b, "g"), c) }, c.ensureUnit = function (a, b) { return "number" == typeof a && (a += b), a }, c.quantity = function (a) { if ("string" == typeof a) { var b = /^(\d+)\s*(.*)$/g.exec(a); return { value: +b[1], unit: b[2] || void 0 } } return { value: a } }, c.querySelector = function (a) { return a instanceof Node ? a : b.querySelector(a) }, c.times = function (a) { return Array.apply(null, new Array(a)) }, c.sum = function (a, b) { return a + (b ? b : 0) }, c.mapMultiply = function (a) { return function (b) { return b * a } }, c.mapAdd = function (a) { return function (b) { return b + a } }, c.serialMap = function (a, b) { var d = [], e = Math.max.apply(null, a.map(function (a) { return a.length })); return c.times(e).forEach(function (c, e) { var f = a.map(function (a) { return a[e] }); d[e] = b.apply(null, f) }), d }, c.roundWithPrecision = function (a, b) { var d = Math.pow(10, b || c.precision); return Math.round(a * d) / d }, c.precision = 8, c.escapingMap = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }, c.serialize = function (a) { return null === a || void 0 === a ? a : ("number" == typeof a ? a = "" + a : "object" == typeof a && (a = JSON.stringify({ data: a })), Object.keys(c.escapingMap).reduce(function (a, b) { return c.replaceAll(a, b, c.escapingMap[b]) }, a)) }, c.deserialize = function (a) { if ("string" != typeof a) return a; a = Object.keys(c.escapingMap).reduce(function (a, b) { return c.replaceAll(a, c.escapingMap[b], b) }, a); try { a = JSON.parse(a), a = void 0 !== a.data ? a.data : a } catch (b) { } return a }, c.createSvg = function (a, b, d, e) { var f; return b = b || "100%", d = d || "100%", Array.prototype.slice.call(a.querySelectorAll("svg")).filter(function (a) { return a.getAttributeNS(c.namespaces.xmlns, "ct") }).forEach(function (b) { a.removeChild(b) }), f = new c.Svg("svg").attr({ width: b, height: d }).addClass(e), f._node.style.width = b, f._node.style.height = d, a.appendChild(f._node), f }, c.normalizeData = function (a, b, d) { var e, f = { raw: a, normalized: {} }; return f.normalized.series = c.getDataArray({ series: a.series || [] }, b, d), e = f.normalized.series.every(function (a) { return a instanceof Array }) ? Math.max.apply(null, f.normalized.series.map(function (a) { return a.length })) : f.normalized.series.length, f.normalized.labels = (a.labels || []).slice(), Array.prototype.push.apply(f.normalized.labels, c.times(Math.max(0, e - f.normalized.labels.length)).map(function () { return "" })), b && c.reverseData(f.normalized), f }, c.safeHasProperty = function (a, b) { return null !== a && "object" == typeof a && a.hasOwnProperty(b) }, c.isDataHoleValue = function (a) { return null === a || void 0 === a || "number" == typeof a && isNaN(a) }, c.reverseData = function (a) { a.labels.reverse(), a.series.reverse(); for (var b = 0; b < a.series.length; b++)"object" == typeof a.series[b] && void 0 !== a.series[b].data ? a.series[b].data.reverse() : a.series[b] instanceof Array && a.series[b].reverse() }, c.getDataArray = function (a, b, d) { function e(a) { if (c.safeHasProperty(a, "value")) return e(a.value); if (c.safeHasProperty(a, "data")) return e(a.data); if (a instanceof Array) return a.map(e); if (!c.isDataHoleValue(a)) { if (d) { var b = {}; return "string" == typeof d ? b[d] = c.getNumberOrUndefined(a) : b.y = c.getNumberOrUndefined(a), b.x = a.hasOwnProperty("x") ? c.getNumberOrUndefined(a.x) : b.x, b.y = a.hasOwnProperty("y") ? c.getNumberOrUndefined(a.y) : b.y, b } return c.getNumberOrUndefined(a) } } return a.series.map(e) }, c.normalizePadding = function (a, b) { return b = b || 0, "number" == typeof a ? { top: a, right: a, bottom: a, left: a } : { top: "number" == typeof a.top ? a.top : b, right: "number" == typeof a.right ? a.right : b, bottom: "number" == typeof a.bottom ? a.bottom : b, left: "number" == typeof a.left ? a.left : b } }, c.getMetaData = function (a, b) { var c = a.data ? a.data[b] : a[b]; return c ? c.meta : void 0 }, c.orderOfMagnitude = function (a) { return Math.floor(Math.log(Math.abs(a)) / Math.LN10) }, c.projectLength = function (a, b, c) { return b / c.range * a }, c.getAvailableHeight = function (a, b) { return Math.max((c.quantity(b.height).value || a.height()) - (b.chartPadding.top + b.chartPadding.bottom) - b.axisX.offset, 0) }, c.getHighLow = function (a, b, d) { function e(a) { if (void 0 !== a) if (a instanceof Array) for (var b = 0; b < a.length; b++)e(a[b]); else { var c = d ? +a[d] : +a; g && c > f.high && (f.high = c), h && c < f.low && (f.low = c) } } b = c.extend({}, b, d ? b["axis" + d.toUpperCase()] : {}); var f = { high: void 0 === b.high ? -Number.MAX_VALUE : +b.high, low: void 0 === b.low ? Number.MAX_VALUE : +b.low }, g = void 0 === b.high, h = void 0 === b.low; return (g || h) && e(a), (b.referenceValue || 0 === b.referenceValue) && (f.high = Math.max(b.referenceValue, f.high), f.low = Math.min(b.referenceValue, f.low)), f.high <= f.low && (0 === f.low ? f.high = 1 : f.low < 0 ? f.high = 0 : f.high > 0 ? f.low = 0 : (f.high = 1, f.low = 0)), f }, c.isNumeric = function (a) { return null !== a && isFinite(a) }, c.isFalseyButZero = function (a) { return !a && 0 !== a }, c.getNumberOrUndefined = function (a) { return c.isNumeric(a) ? +a : void 0 }, c.isMultiValue = function (a) { return "object" == typeof a && ("x" in a || "y" in a) }, c.getMultiValue = function (a, b) { return c.isMultiValue(a) ? c.getNumberOrUndefined(a[b || "y"]) : c.getNumberOrUndefined(a) }, c.rho = function (a) { function b(a, c) { return a % c === 0 ? c : b(c, a % c) } function c(a) { return a * a + 1 } if (1 === a) return a; var d, e = 2, f = 2; if (a % 2 === 0) return 2; do e = c(e) % a, f = c(c(f)) % a, d = b(Math.abs(e - f), a); while (1 === d); return d }, c.getBounds = function (a, b, d, e) { function f(a, b) { return a === (a += b) && (a *= 1 + (b > 0 ? o : -o)), a } var g, h, i, j = 0, k = { high: b.high, low: b.low }; k.valueRange = k.high - k.low, k.oom = c.orderOfMagnitude(k.valueRange), k.step = Math.pow(10, k.oom), k.min = Math.floor(k.low / k.step) * k.step, k.max = Math.ceil(k.high / k.step) * k.step, k.range = k.max - k.min, k.numberOfSteps = Math.round(k.range / k.step); var l = c.projectLength(a, k.step, k), m = l < d, n = e ? c.rho(k.range) : 0; if (e && c.projectLength(a, 1, k) >= d) k.step = 1; else if (e && n < k.step && c.projectLength(a, n, k) >= d) k.step = n; else for (; ;) { if (m && c.projectLength(a, k.step, k) <= d) k.step *= 2; else { if (m || !(c.projectLength(a, k.step / 2, k) >= d)) break; if (k.step /= 2, e && k.step % 1 !== 0) { k.step *= 2; break } } if (j++ > 1e3) throw new Error("Exceeded maximum number of iterations while optimizing scale step!") } var o = 2.221e-16; for (k.step = Math.max(k.step, o), h = k.min, i = k.max; h + k.step <= k.low;)h = f(h, k.step); for (; i - k.step >= k.high;)i = f(i, -k.step); k.min = h, k.max = i, k.range = k.max - k.min; var p = []; for (g = k.min; g <= k.max; g = f(g, k.step)) { var q = c.roundWithPrecision(g); q !== p[p.length - 1] && p.push(q) } return k.values = p, k }, c.polarToCartesian = function (a, b, c, d) { var e = (d - 90) * Math.PI / 180; return { x: a + c * Math.cos(e), y: b + c * Math.sin(e) } }, c.createChartRect = function (a, b, d) { var e = !(!b.axisX && !b.axisY), f = e ? b.axisY.offset : 0, g = e ? b.axisX.offset : 0, h = a.width() || c.quantity(b.width).value || 0, i = a.height() || c.quantity(b.height).value || 0, j = c.normalizePadding(b.chartPadding, d); h = Math.max(h, f + j.left + j.right), i = Math.max(i, g + j.top + j.bottom); var k = { padding: j, width: function () { return this.x2 - this.x1 }, height: function () { return this.y1 - this.y2 } }; return e ? ("start" === b.axisX.position ? (k.y2 = j.top + g, k.y1 = Math.max(i - j.bottom, k.y2 + 1)) : (k.y2 = j.top, k.y1 = Math.max(i - j.bottom - g, k.y2 + 1)), "start" === b.axisY.position ? (k.x1 = j.left + f, k.x2 = Math.max(h - j.right, k.x1 + 1)) : (k.x1 = j.left, k.x2 = Math.max(h - j.right - f, k.x1 + 1))) : (k.x1 = j.left, k.x2 = Math.max(h - j.right, k.x1 + 1), k.y2 = j.top, k.y1 = Math.max(i - j.bottom, k.y2 + 1)), k }, c.createGrid = function (a, b, d, e, f, g, h, i) { var j = {}; j[d.units.pos + "1"] = a, j[d.units.pos + "2"] = a, j[d.counterUnits.pos + "1"] = e, j[d.counterUnits.pos + "2"] = e + f; var k = g.elem("line", j, h.join(" ")); i.emit("draw", c.extend({ type: "grid", axis: d, index: b, group: g, element: k }, j)) }, c.createGridBackground = function (a, b, c, d) { var e = a.elem("rect", { x: b.x1, y: b.y2, width: b.width(), height: b.height() }, c, !0); d.emit("draw", { type: "gridBackground", group: a, element: e }) }, c.createLabel = function (a, d, e, f, g, h, i, j, k, l, m) { var n, o = {}; if (o[g.units.pos] = a + i[g.units.pos], o[g.counterUnits.pos] = i[g.counterUnits.pos], o[g.units.len] = d, o[g.counterUnits.len] = Math.max(0, h - 10), l) { var p = b.createElement("span"); p.className = k.join(" "), p.setAttribute("xmlns", c.namespaces.xhtml), p.innerText = f[e], p.style[g.units.len] = Math.round(o[g.units.len]) + "px", p.style[g.counterUnits.len] = Math.round(o[g.counterUnits.len]) + "px", n = j.foreignObject(p, c.extend({ style: "overflow: visible;" }, o)) } else n = j.elem("text", o, k.join(" ")).text(f[e]); m.emit("draw", c.extend({ type: "label", axis: g, index: e, group: j, element: n, text: f[e] }, o)) }, c.getSeriesOption = function (a, b, c) { if (a.name && b.series && b.series[a.name]) { var d = b.series[a.name]; return d.hasOwnProperty(c) ? d[c] : b[c] } return b[c] }, c.optionsProvider = function (b, d, e) { function f(b) { var f = h; if (h = c.extend({}, j), d) for (i = 0; i < d.length; i++) { var g = a.matchMedia(d[i][0]); g.matches && (h = c.extend(h, d[i][1])) } e && b && e.emit("optionsChanged", { previousOptions: f, currentOptions: h }) } function g() { k.forEach(function (a) { a.removeListener(f) }) } var h, i, j = c.extend({}, b), k = []; if (!a.matchMedia) throw "window.matchMedia not found! Make sure you're using a polyfill."; if (d) for (i = 0; i < d.length; i++) { var l = a.matchMedia(d[i][0]); l.addListener(f), k.push(l) } return f(), { removeMediaQueryListeners: g, getCurrentOptions: function () { return c.extend({}, h) } } }, c.splitIntoSegments = function (a, b, d) { var e = { increasingX: !1, fillHoles: !1 }; d = c.extend({}, e, d); for (var f = [], g = !0, h = 0; h < a.length; h += 2)void 0 === c.getMultiValue(b[h / 2].value) ? d.fillHoles || (g = !0) : (d.increasingX && h >= 2 && a[h] <= a[h - 2] && (g = !0), g && (f.push({ pathCoordinates: [], valueData: [] }), g = !1), f[f.length - 1].pathCoordinates.push(a[h], a[h + 1]), f[f.length - 1].valueData.push(b[h / 2])); return f } }(window, document, a), function (a, b, c) { "use strict"; c.Interpolation = {}, c.Interpolation.none = function (a) { var b = { fillHoles: !1 }; return a = c.extend({}, b, a), function (b, d) { for (var e = new c.Svg.Path, f = !0, g = 0; g < b.length; g += 2) { var h = b[g], i = b[g + 1], j = d[g / 2]; void 0 !== c.getMultiValue(j.value) ? (f ? e.move(h, i, !1, j) : e.line(h, i, !1, j), f = !1) : a.fillHoles || (f = !0) } return e } }, c.Interpolation.simple = function (a) { var b = { divisor: 2, fillHoles: !1 }; a = c.extend({}, b, a); var d = 1 / Math.max(1, a.divisor); return function (b, e) { for (var f, g, h, i = new c.Svg.Path, j = 0; j < b.length; j += 2) { var k = b[j], l = b[j + 1], m = (k - f) * d, n = e[j / 2]; void 0 !== n.value ? (void 0 === h ? i.move(k, l, !1, n) : i.curve(f + m, g, k - m, l, k, l, !1, n), f = k, g = l, h = n) : a.fillHoles || (f = k = h = void 0) } return i } }, c.Interpolation.cardinal = function (a) { var b = { tension: 1, fillHoles: !1 }; a = c.extend({}, b, a); var d = Math.min(1, Math.max(0, a.tension)), e = 1 - d; return function f(b, g) { var h = c.splitIntoSegments(b, g, { fillHoles: a.fillHoles }); if (h.length) { if (h.length > 1) { var i = []; return h.forEach(function (a) { i.push(f(a.pathCoordinates, a.valueData)) }), c.Svg.Path.join(i) } if (b = h[0].pathCoordinates, g = h[0].valueData, b.length <= 4) return c.Interpolation.none()(b, g); for (var j, k = (new c.Svg.Path).move(b[0], b[1], !1, g[0]), l = 0, m = b.length; m - 2 * !j > l; l += 2) { var n = [{ x: +b[l - 2], y: +b[l - 1] }, { x: +b[l], y: +b[l + 1] }, { x: +b[l + 2], y: +b[l + 3] }, { x: +b[l + 4], y: +b[l + 5] }]; j ? l ? m - 4 === l ? n[3] = { x: +b[0], y: +b[1] } : m - 2 === l && (n[2] = { x: +b[0], y: +b[1] }, n[3] = { x: +b[2], y: +b[3] }) : n[0] = { x: +b[m - 2], y: +b[m - 1] } : m - 4 === l ? n[3] = n[2] : l || (n[0] = { x: +b[l], y: +b[l + 1] }), k.curve(d * (-n[0].x + 6 * n[1].x + n[2].x) / 6 + e * n[2].x, d * (-n[0].y + 6 * n[1].y + n[2].y) / 6 + e * n[2].y, d * (n[1].x + 6 * n[2].x - n[3].x) / 6 + e * n[2].x, d * (n[1].y + 6 * n[2].y - n[3].y) / 6 + e * n[2].y, n[2].x, n[2].y, !1, g[(l + 2) / 2]) } return k } return c.Interpolation.none()([]) } }, c.Interpolation.monotoneCubic = function (a) { var b = { fillHoles: !1 }; return a = c.extend({}, b, a), function d(b, e) { var f = c.splitIntoSegments(b, e, { fillHoles: a.fillHoles, increasingX: !0 }); if (f.length) { if (f.length > 1) { var g = []; return f.forEach(function (a) { g.push(d(a.pathCoordinates, a.valueData)) }), c.Svg.Path.join(g) } if (b = f[0].pathCoordinates, e = f[0].valueData, b.length <= 4) return c.Interpolation.none()(b, e); var h, i, j = [], k = [], l = b.length / 2, m = [], n = [], o = [], p = []; for (h = 0; h < l; h++)j[h] = b[2 * h], k[h] = b[2 * h + 1]; for (h = 0; h < l - 1; h++)o[h] = k[h + 1] - k[h], p[h] = j[h + 1] - j[h], n[h] = o[h] / p[h]; for (m[0] = n[0], m[l - 1] = n[l - 2], h = 1; h < l - 1; h++)0 === n[h] || 0 === n[h - 1] || n[h - 1] > 0 != n[h] > 0 ? m[h] = 0 : (m[h] = 3 * (p[h - 1] + p[h]) / ((2 * p[h] + p[h - 1]) / n[h - 1] + (p[h] + 2 * p[h - 1]) / n[h]), isFinite(m[h]) || (m[h] = 0)); for (i = (new c.Svg.Path).move(j[0], k[0], !1, e[0]), h = 0; h < l - 1; h++)i.curve(j[h] + p[h] / 3, k[h] + m[h] * p[h] / 3, j[h + 1] - p[h] / 3, k[h + 1] - m[h + 1] * p[h] / 3, j[h + 1], k[h + 1], !1, e[h + 1]); return i } return c.Interpolation.none()([]) } }, c.Interpolation.step = function (a) { var b = { postpone: !0, fillHoles: !1 }; return a = c.extend({}, b, a), function (b, d) { for (var e, f, g, h = new c.Svg.Path, i = 0; i < b.length; i += 2) { var j = b[i], k = b[i + 1], l = d[i / 2]; void 0 !== l.value ? (void 0 === g ? h.move(j, k, !1, l) : (a.postpone ? h.line(j, f, !1, g) : h.line(e, k, !1, l), h.line(j, k, !1, l)), e = j, f = k, g = l) : a.fillHoles || (e = f = g = void 0) } return h } } }(window, document, a), function (a, b, c) { "use strict"; c.EventEmitter = function () { function a(a, b) { d[a] = d[a] || [], d[a].push(b) } function b(a, b) { d[a] && (b ? (d[a].splice(d[a].indexOf(b), 1), 0 === d[a].length && delete d[a]) : delete d[a]) } function c(a, b) { d[a] && d[a].forEach(function (a) { a(b) }), d["*"] && d["*"].forEach(function (c) { c(a, b) }) } var d = []; return { addEventHandler: a, removeEventHandler: b, emit: c } } }(window, document, a), function (a, b, c) { "use strict"; function d(a) { var b = []; if (a.length) for (var c = 0; c < a.length; c++)b.push(a[c]); return b } function e(a, b) { var d = b || this.prototype || c.Class, e = Object.create(d); c.Class.cloneDefinitions(e, a); var f = function () { var a, b = e.constructor || function () { }; return a = this === c ? Object.create(e) : this, b.apply(a, Array.prototype.slice.call(arguments, 0)), a }; return f.prototype = e, f["super"] = d, f.extend = this.extend, f } function f() { var a = d(arguments), b = a[0]; return a.splice(1, a.length - 1).forEach(function (a) { Object.getOwnPropertyNames(a).forEach(function (c) { delete b[c], Object.defineProperty(b, c, Object.getOwnPropertyDescriptor(a, c)) }) }), b } c.Class = { extend: e, cloneDefinitions: f } }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d) { return a && (this.data = a || {}, this.data.labels = this.data.labels || [], this.data.series = this.data.series || [], this.eventEmitter.emit("data", { type: "update", data: this.data })), b && (this.options = c.extend({}, d ? this.options : this.defaultOptions, b), this.initializeTimeoutId || (this.optionsProvider.removeMediaQueryListeners(), this.optionsProvider = c.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter))), this.initializeTimeoutId || this.createChart(this.optionsProvider.getCurrentOptions()), this } function e() { return this.initializeTimeoutId ? a.clearTimeout(this.initializeTimeoutId) : (a.removeEventListener("resize", this.resizeListener), this.optionsProvider.removeMediaQueryListeners()), this } function f(a, b) { return this.eventEmitter.addEventHandler(a, b), this } function g(a, b) { return this.eventEmitter.removeEventHandler(a, b), this } function h() { a.addEventListener("resize", this.resizeListener), this.optionsProvider = c.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter), this.eventEmitter.addEventHandler("optionsChanged", function () { this.update() }.bind(this)), this.options.plugins && this.options.plugins.forEach(function (a) { a instanceof Array ? a[0](this, a[1]) : a(this) }.bind(this)), this.eventEmitter.emit("data", { type: "initial", data: this.data }), this.createChart(this.optionsProvider.getCurrentOptions()), this.initializeTimeoutId = void 0 } function i(a, b, d, e, f) { this.container = c.querySelector(a), this.data = b || {}, this.data.labels = this.data.labels || [], this.data.series = this.data.series || [], this.defaultOptions = d, this.options = e, this.responsiveOptions = f, this.eventEmitter = c.EventEmitter(), this.supportsForeignObject = c.Svg.isSupported("Extensibility"), this.supportsAnimations = c.Svg.isSupported("AnimationEventsAttribute"), this.resizeListener = function () { this.update() }.bind(this), this.container && (this.container.__chartist__ && this.container.__chartist__.detach(), this.container.__chartist__ = this), this.initializeTimeoutId = setTimeout(h.bind(this), 0) } c.Base = c.Class.extend({ constructor: i, optionsProvider: void 0, container: void 0, svg: void 0, eventEmitter: void 0, createChart: function () { throw new Error("Base chart type can't be instantiated!") }, update: d, detach: e, on: f, off: g, version: c.version, supportsForeignObject: !1 }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, d, e, f, g) { a instanceof Element ? this._node = a : (this._node = b.createElementNS(c.namespaces.svg, a), "svg" === a && this.attr({ "xmlns:ct": c.namespaces.ct })), d && this.attr(d), e && this.addClass(e), f && (g && f._node.firstChild ? f._node.insertBefore(this._node, f._node.firstChild) : f._node.appendChild(this._node)) } function e(a, b) { return "string" == typeof a ? b ? this._node.getAttributeNS(b, a) : this._node.getAttribute(a) : (Object.keys(a).forEach(function (b) { if (void 0 !== a[b]) if (b.indexOf(":") !== -1) { var d = b.split(":"); this._node.setAttributeNS(c.namespaces[d[0]], b, a[b]) } else this._node.setAttribute(b, a[b]) }.bind(this)), this) } function f(a, b, d, e) { return new c.Svg(a, b, d, this, e) } function g() { return this._node.parentNode instanceof SVGElement ? new c.Svg(this._node.parentNode) : null } function h() { for (var a = this._node; "svg" !== a.nodeName;)a = a.parentNode; return new c.Svg(a) } function i(a) { var b = this._node.querySelector(a); return b ? new c.Svg(b) : null } function j(a) { var b = this._node.querySelectorAll(a); return b.length ? new c.Svg.List(b) : null } function k() { return this._node } function l(a, d, e, f) { if ("string" == typeof a) { var g = b.createElement("div"); g.innerHTML = a, a = g.firstChild } a.setAttribute("xmlns", c.namespaces.xmlns); var h = this.elem("foreignObject", d, e, f); return h._node.appendChild(a), h } function m(a) { return this._node.appendChild(b.createTextNode(a)), this } function n() { for (; this._node.firstChild;)this._node.removeChild(this._node.firstChild); return this } function o() { return this._node.parentNode.removeChild(this._node), this.parent() } function p(a) { return this._node.parentNode.replaceChild(a._node, this._node), a } function q(a, b) { return b && this._node.firstChild ? this._node.insertBefore(a._node, this._node.firstChild) : this._node.appendChild(a._node), this } function r() { return this._node.getAttribute("class") ? this._node.getAttribute("class").trim().split(/\s+/) : [] } function s(a) { return this._node.setAttribute("class", this.classes(this._node).concat(a.trim().split(/\s+/)).filter(function (a, b, c) { return c.indexOf(a) === b }).join(" ")), this } function t(a) { var b = a.trim().split(/\s+/); return this._node.setAttribute("class", this.classes(this._node).filter(function (a) { return b.indexOf(a) === -1 }).join(" ")), this } function u() { return this._node.setAttribute("class", ""), this } function v() { return this._node.getBoundingClientRect().height } function w() { return this._node.getBoundingClientRect().width } function x(a, b, d) { return void 0 === b && (b = !0), Object.keys(a).forEach(function (e) { function f(a, b) { var f, g, h, i = {}; a.easing && (h = a.easing instanceof Array ? a.easing : c.Svg.Easing[a.easing], delete a.easing), a.begin = c.ensureUnit(a.begin, "ms"), a.dur = c.ensureUnit(a.dur, "ms"), h && (a.calcMode = "spline", a.keySplines = h.join(" "), a.keyTimes = "0;1"), b && (a.fill = "freeze", i[e] = a.from, this.attr(i), g = c.quantity(a.begin || 0).value, a.begin = "indefinite"), f = this.elem("animate", c.extend({ attributeName: e }, a)), b && setTimeout(function () { try { f._node.beginElement() } catch (b) { i[e] = a.to, this.attr(i), f.remove() } }.bind(this), g), d && f._node.addEventListener("beginEvent", function () { d.emit("animationBegin", { element: this, animate: f._node, params: a }) }.bind(this)), f._node.addEventListener("endEvent", function () { d && d.emit("animationEnd", { element: this, animate: f._node, params: a }), b && (i[e] = a.to, this.attr(i), f.remove()) }.bind(this)) } a[e] instanceof Array ? a[e].forEach(function (a) { f.bind(this)(a, !1) }.bind(this)) : f.bind(this)(a[e], b) }.bind(this)), this } function y(a) { var b = this; this.svgElements = []; for (var d = 0; d < a.length; d++)this.svgElements.push(new c.Svg(a[d])); Object.keys(c.Svg.prototype).filter(function (a) { return ["constructor", "parent", "querySelector", "querySelectorAll", "replace", "append", "classes", "height", "width"].indexOf(a) === -1 }).forEach(function (a) { b[a] = function () { var d = Array.prototype.slice.call(arguments, 0); return b.svgElements.forEach(function (b) { c.Svg.prototype[a].apply(b, d) }), b } }) } c.Svg = c.Class.extend({ constructor: d, attr: e, elem: f, parent: g, root: h, querySelector: i, querySelectorAll: j, getNode: k, foreignObject: l, text: m, empty: n, remove: o, replace: p, append: q, classes: r, addClass: s, removeClass: t, removeAllClasses: u, height: v, width: w, animate: x }), c.Svg.isSupported = function (a) { return b.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#" + a, "1.1") }; var z = { easeInSine: [.47, 0, .745, .715], easeOutSine: [.39, .575, .565, 1], easeInOutSine: [.445, .05, .55, .95], easeInQuad: [.55, .085, .68, .53], easeOutQuad: [.25, .46, .45, .94], easeInOutQuad: [.455, .03, .515, .955], easeInCubic: [.55, .055, .675, .19], easeOutCubic: [.215, .61, .355, 1], easeInOutCubic: [.645, .045, .355, 1], easeInQuart: [.895, .03, .685, .22], easeOutQuart: [.165, .84, .44, 1], easeInOutQuart: [.77, 0, .175, 1], easeInQuint: [.755, .05, .855, .06], easeOutQuint: [.23, 1, .32, 1], easeInOutQuint: [.86, 0, .07, 1], easeInExpo: [.95, .05, .795, .035], easeOutExpo: [.19, 1, .22, 1], easeInOutExpo: [1, 0, 0, 1], easeInCirc: [.6, .04, .98, .335], easeOutCirc: [.075, .82, .165, 1], easeInOutCirc: [.785, .135, .15, .86], easeInBack: [.6, -.28, .735, .045], easeOutBack: [.175, .885, .32, 1.275], easeInOutBack: [.68, -.55, .265, 1.55] }; c.Svg.Easing = z, c.Svg.List = c.Class.extend({ constructor: y }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e, f, g) { var h = c.extend({ command: f ? a.toLowerCase() : a.toUpperCase() }, b, g ? { data: g } : {}); d.splice(e, 0, h) } function e(a, b) { a.forEach(function (c, d) { u[c.command.toLowerCase()].forEach(function (e, f) { b(c, e, d, f, a) }) }) } function f(a, b) { this.pathElements = [], this.pos = 0, this.close = a, this.options = c.extend({}, v, b) } function g(a) { return void 0 !== a ? (this.pos = Math.max(0, Math.min(this.pathElements.length, a)), this) : this.pos } function h(a) { return this.pathElements.splice(this.pos, a), this } function i(a, b, c, e) { return d("M", { x: +a, y: +b }, this.pathElements, this.pos++, c, e), this } function j(a, b, c, e) { return d("L", { x: +a, y: +b }, this.pathElements, this.pos++, c, e), this } function k(a, b, c, e, f, g, h, i) { return d("C", { x1: +a, y1: +b, x2: +c, y2: +e, x: +f, y: +g }, this.pathElements, this.pos++, h, i), this } function l(a, b, c, e, f, g, h, i, j) { return d("A", { rx: +a, ry: +b, xAr: +c, lAf: +e, sf: +f, x: +g, y: +h }, this.pathElements, this.pos++, i, j), this } function m(a) { var b = a.replace(/([A-Za-z])([0-9])/g, "$1 $2").replace(/([0-9])([A-Za-z])/g, "$1 $2").split(/[\s,]+/).reduce(function (a, b) { return b.match(/[A-Za-z]/) && a.push([]), a[a.length - 1].push(b), a }, []); "Z" === b[b.length - 1][0].toUpperCase() && b.pop(); var d = b.map(function (a) { var b = a.shift(), d = u[b.toLowerCase()]; return c.extend({ command: b }, d.reduce(function (b, c, d) { return b[c] = +a[d], b }, {})) }), e = [this.pos, 0]; return Array.prototype.push.apply(e, d), Array.prototype.splice.apply(this.pathElements, e), this.pos += d.length, this } function n() { var a = Math.pow(10, this.options.accuracy); return this.pathElements.reduce(function (b, c) { var d = u[c.command.toLowerCase()].map(function (b) { return this.options.accuracy ? Math.round(c[b] * a) / a : c[b] }.bind(this)); return b + c.command + d.join(",") }.bind(this), "") + (this.close ? "Z" : "") } function o(a, b) { return e(this.pathElements, function (c, d) { c[d] *= "x" === d[0] ? a : b }), this } function p(a, b) { return e(this.pathElements, function (c, d) { c[d] += "x" === d[0] ? a : b }), this } function q(a) { return e(this.pathElements, function (b, c, d, e, f) { var g = a(b, c, d, e, f); (g || 0 === g) && (b[c] = g) }), this } function r(a) { var b = new c.Svg.Path(a || this.close); return b.pos = this.pos, b.pathElements = this.pathElements.slice().map(function (a) { return c.extend({}, a) }), b.options = c.extend({}, this.options), b } function s(a) { var b = [new c.Svg.Path]; return this.pathElements.forEach(function (d) { d.command === a.toUpperCase() && 0 !== b[b.length - 1].pathElements.length && b.push(new c.Svg.Path), b[b.length - 1].pathElements.push(d) }), b } function t(a, b, d) { for (var e = new c.Svg.Path(b, d), f = 0; f < a.length; f++)for (var g = a[f], h = 0; h < g.pathElements.length; h++)e.pathElements.push(g.pathElements[h]); return e } var u = { m: ["x", "y"], l: ["x", "y"], c: ["x1", "y1", "x2", "y2", "x", "y"], a: ["rx", "ry", "xAr", "lAf", "sf", "x", "y"] }, v = { accuracy: 3 }; c.Svg.Path = c.Class.extend({ constructor: f, position: g, remove: h, move: i, line: j, curve: k, arc: l, scale: o, translate: p, transform: q, parse: m, stringify: n, clone: r, splitByCommand: s }), c.Svg.Path.elementDescriptions = u, c.Svg.Path.join = t }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, c, d) { this.units = a, this.counterUnits = a === f.x ? f.y : f.x, this.chartRect = b, this.axisLength = b[a.rectEnd] - b[a.rectStart], this.gridOffset = b[a.rectOffset], this.ticks = c, this.options = d } function e(a, b, d, e, f) { var g = e["axis" + this.units.pos.toUpperCase()], h = this.ticks.map(this.projectValue.bind(this)), i = this.ticks.map(g.labelInterpolationFnc); h.forEach(function (j, k) { var l, m = { x: 0, y: 0 }; l = h[k + 1] ? h[k + 1] - j : Math.max(this.axisLength - j, 30), c.isFalseyButZero(i[k]) && "" !== i[k] || ("x" === this.units.pos ? (j = this.chartRect.x1 + j, m.x = e.axisX.labelOffset.x, "start" === e.axisX.position ? m.y = this.chartRect.padding.top + e.axisX.labelOffset.y + (d ? 5 : 20) : m.y = this.chartRect.y1 + e.axisX.labelOffset.y + (d ? 5 : 20)) : (j = this.chartRect.y1 - j, m.y = e.axisY.labelOffset.y - (d ? l : 0), "start" === e.axisY.position ? m.x = d ? this.chartRect.padding.left + e.axisY.labelOffset.x : this.chartRect.x1 - 10 : m.x = this.chartRect.x2 + e.axisY.labelOffset.x + 10), g.showGrid && c.createGrid(j, k, this, this.gridOffset, this.chartRect[this.counterUnits.len](), a, [e.classNames.grid, e.classNames[this.units.dir]], f), g.showLabel && c.createLabel(j, l, k, i, this, g.offset, m, b, [e.classNames.label, e.classNames[this.units.dir], "start" === g.position ? e.classNames[g.position] : e.classNames.end], d, f)) }.bind(this)) } var f = { x: { pos: "x", len: "width", dir: "horizontal", rectStart: "x1", rectEnd: "x2", rectOffset: "y2" }, y: { pos: "y", len: "height", dir: "vertical", rectStart: "y2", rectEnd: "y1", rectOffset: "x1" } }; c.Axis = c.Class.extend({ constructor: d, createGridAndLabels: e, projectValue: function (a, b, c) { throw new Error("Base axis can't be instantiated!") } }), c.Axis.units = f }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { var f = e.highLow || c.getHighLow(b, e, a.pos); this.bounds = c.getBounds(d[a.rectEnd] - d[a.rectStart], f, e.scaleMinSpace || 20, e.onlyInteger), this.range = { min: this.bounds.min, max: this.bounds.max }, c.AutoScaleAxis["super"].constructor.call(this, a, d, this.bounds.values, e) } function e(a) { return this.axisLength * (+c.getMultiValue(a, this.units.pos) - this.bounds.min) / this.bounds.range } c.AutoScaleAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { var f = e.highLow || c.getHighLow(b, e, a.pos); this.divisor = e.divisor || 1, this.ticks = e.ticks || c.times(this.divisor).map(function (a, b) { return f.low + (f.high - f.low) / this.divisor * b }.bind(this)), this.ticks.sort(function (a, b) { return a - b }), this.range = { min: f.low, max: f.high }, c.FixedScaleAxis["super"].constructor.call(this, a, d, this.ticks, e), this.stepLength = this.axisLength / this.divisor } function e(a) { return this.axisLength * (+c.getMultiValue(a, this.units.pos) - this.range.min) / (this.range.max - this.range.min) } c.FixedScaleAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { c.StepAxis["super"].constructor.call(this, a, d, e.ticks, e); var f = Math.max(1, e.ticks.length - (e.stretch ? 1 : 0)); this.stepLength = this.axisLength / f } function e(a, b) { return this.stepLength * b } c.StepAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a) { var b = c.normalizeData(this.data, a.reverseData, !0); this.svg = c.createSvg(this.container, a.width, a.height, a.classNames.chart); var d, e, g = this.svg.elem("g").addClass(a.classNames.gridGroup), h = this.svg.elem("g"), i = this.svg.elem("g").addClass(a.classNames.labelGroup), j = c.createChartRect(this.svg, a, f.padding); d = void 0 === a.axisX.type ? new c.StepAxis(c.Axis.units.x, b.normalized.series, j, c.extend({}, a.axisX, { ticks: b.normalized.labels, stretch: a.fullWidth })) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, j, a.axisX), e = void 0 === a.axisY.type ? new c.AutoScaleAxis(c.Axis.units.y, b.normalized.series, j, c.extend({}, a.axisY, { high: c.isNumeric(a.high) ? a.high : a.axisY.high, low: c.isNumeric(a.low) ? a.low : a.axisY.low })) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, j, a.axisY), d.createGridAndLabels(g, i, this.supportsForeignObject, a, this.eventEmitter), e.createGridAndLabels(g, i, this.supportsForeignObject, a, this.eventEmitter), a.showGridBackground && c.createGridBackground(g, j, a.classNames.gridBackground, this.eventEmitter), b.raw.series.forEach(function (f, g) { var i = h.elem("g"); i.attr({ "ct:series-name": f.name, "ct:meta": c.serialize(f.meta) }), i.addClass([a.classNames.series, f.className || a.classNames.series + "-" + c.alphaNumerate(g)].join(" ")); var k = [], l = []; b.normalized.series[g].forEach(function (a, h) { var i = { x: j.x1 + d.projectValue(a, h, b.normalized.series[g]), y: j.y1 - e.projectValue(a, h, b.normalized.series[g]) }; k.push(i.x, i.y), l.push({ value: a, valueIndex: h, meta: c.getMetaData(f, h) }) }.bind(this)); var m = { lineSmooth: c.getSeriesOption(f, a, "lineSmooth"), showPoint: c.getSeriesOption(f, a, "showPoint"), showLine: c.getSeriesOption(f, a, "showLine"), showArea: c.getSeriesOption(f, a, "showArea"), areaBase: c.getSeriesOption(f, a, "areaBase") }, n = "function" == typeof m.lineSmooth ? m.lineSmooth : m.lineSmooth ? c.Interpolation.monotoneCubic() : c.Interpolation.none(), o = n(k, l); if (m.showPoint && o.pathElements.forEach(function (b) { var h = i.elem("line", { x1: b.x, y1: b.y, x2: b.x + .01, y2: b.y }, a.classNames.point).attr({ "ct:value": [b.data.value.x, b.data.value.y].filter(c.isNumeric).join(","), "ct:meta": c.serialize(b.data.meta) }); this.eventEmitter.emit("draw", { type: "point", value: b.data.value, index: b.data.valueIndex, meta: b.data.meta, series: f, seriesIndex: g, axisX: d, axisY: e, group: i, element: h, x: b.x, y: b.y }) }.bind(this)), m.showLine) { var p = i.elem("path", { d: o.stringify() }, a.classNames.line, !0); this.eventEmitter.emit("draw", { type: "line", values: b.normalized.series[g], path: o.clone(), chartRect: j, index: g, series: f, seriesIndex: g, seriesMeta: f.meta, axisX: d, axisY: e, group: i, element: p }) } if (m.showArea && e.range) { var q = Math.max(Math.min(m.areaBase, e.range.max), e.range.min), r = j.y1 - e.projectValue(q); o.splitByCommand("M").filter(function (a) { return a.pathElements.length > 1 }).map(function (a) { var b = a.pathElements[0], c = a.pathElements[a.pathElements.length - 1]; return a.clone(!0).position(0).remove(1).move(b.x, r).line(b.x, b.y).position(a.pathElements.length + 1).line(c.x, r) }).forEach(function (c) { var h = i.elem("path", { d: c.stringify() }, a.classNames.area, !0); this.eventEmitter.emit("draw", { type: "area", values: b.normalized.series[g], path: c.clone(), series: f, seriesIndex: g, axisX: d, axisY: e, chartRect: j, index: g, group: i, element: h }) }.bind(this)) } }.bind(this)), this.eventEmitter.emit("created", { bounds: e.bounds, chartRect: j, axisX: d, axisY: e, svg: this.svg, options: a }) } function e(a, b, d, e) { c.Line["super"].constructor.call(this, a, b, f, c.extend({}, f, d), e) } var f = { axisX: { offset: 30, position: "end", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, type: void 0 }, axisY: { offset: 40, position: "start", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, type: void 0, scaleMinSpace: 20, onlyInteger: !1 }, width: void 0, height: void 0, showLine: !0, showPoint: !0, showArea: !1, areaBase: 0, lineSmooth: !0, showGridBackground: !1, low: void 0, high: void 0, chartPadding: { top: 15, right: 15, bottom: 5, left: 10 }, fullWidth: !1, reverseData: !1, classNames: { chart: "ct-chart-line", label: "ct-label", labelGroup: "ct-labels", series: "ct-series", line: "ct-line", point: "ct-point", area: "ct-area", grid: "ct-grid", gridGroup: "ct-grids", gridBackground: "ct-grid-background", vertical: "ct-vertical", horizontal: "ct-horizontal", start: "ct-start", end: "ct-end" } }; c.Line = c.Base.extend({ constructor: e, createChart: d }) }(window, document, a), function (a, b, c) { - "use strict"; function d(a) { - var b, d; a.distributeSeries ? (b = c.normalizeData(this.data, a.reverseData, a.horizontalBars ? "x" : "y"), b.normalized.series = b.normalized.series.map(function (a) { return [a] })) : b = c.normalizeData(this.data, a.reverseData, a.horizontalBars ? "x" : "y"), this.svg = c.createSvg(this.container, a.width, a.height, a.classNames.chart + (a.horizontalBars ? " " + a.classNames.horizontalBars : "")); var e = this.svg.elem("g").addClass(a.classNames.gridGroup), g = this.svg.elem("g"), h = this.svg.elem("g").addClass(a.classNames.labelGroup); if (a.stackBars && 0 !== b.normalized.series.length) { - var i = c.serialMap(b.normalized.series, function () { - return Array.prototype.slice.call(arguments).map(function (a) { return a }).reduce(function (a, b) { return { x: a.x + (b && b.x) || 0, y: a.y + (b && b.y) || 0 } }, { x: 0, y: 0 }) - }); d = c.getHighLow([i], a, a.horizontalBars ? "x" : "y") - } else d = c.getHighLow(b.normalized.series, a, a.horizontalBars ? "x" : "y"); d.high = +a.high || (0 === a.high ? 0 : d.high), d.low = +a.low || (0 === a.low ? 0 : d.low); var j, k, l, m, n, o = c.createChartRect(this.svg, a, f.padding); k = a.distributeSeries && a.stackBars ? b.normalized.labels.slice(0, 1) : b.normalized.labels, a.horizontalBars ? (j = m = void 0 === a.axisX.type ? new c.AutoScaleAxis(c.Axis.units.x, b.normalized.series, o, c.extend({}, a.axisX, { highLow: d, referenceValue: 0 })) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, o, c.extend({}, a.axisX, { highLow: d, referenceValue: 0 })), l = n = void 0 === a.axisY.type ? new c.StepAxis(c.Axis.units.y, b.normalized.series, o, { ticks: k }) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, o, a.axisY)) : (l = m = void 0 === a.axisX.type ? new c.StepAxis(c.Axis.units.x, b.normalized.series, o, { ticks: k }) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, o, a.axisX), j = n = void 0 === a.axisY.type ? new c.AutoScaleAxis(c.Axis.units.y, b.normalized.series, o, c.extend({}, a.axisY, { highLow: d, referenceValue: 0 })) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, o, c.extend({}, a.axisY, { highLow: d, referenceValue: 0 }))); var p = a.horizontalBars ? o.x1 + j.projectValue(0) : o.y1 - j.projectValue(0), q = []; l.createGridAndLabels(e, h, this.supportsForeignObject, a, this.eventEmitter), j.createGridAndLabels(e, h, this.supportsForeignObject, a, this.eventEmitter), a.showGridBackground && c.createGridBackground(e, o, a.classNames.gridBackground, this.eventEmitter), b.raw.series.forEach(function (d, e) { var f, h, i = e - (b.raw.series.length - 1) / 2; f = a.distributeSeries && !a.stackBars ? l.axisLength / b.normalized.series.length / 2 : a.distributeSeries && a.stackBars ? l.axisLength / 2 : l.axisLength / b.normalized.series[e].length / 2, h = g.elem("g"), h.attr({ "ct:series-name": d.name, "ct:meta": c.serialize(d.meta) }), h.addClass([a.classNames.series, d.className || a.classNames.series + "-" + c.alphaNumerate(e)].join(" ")), b.normalized.series[e].forEach(function (g, k) { var r, s, t, u; if (u = a.distributeSeries && !a.stackBars ? e : a.distributeSeries && a.stackBars ? 0 : k, r = a.horizontalBars ? { x: o.x1 + j.projectValue(g && g.x ? g.x : 0, k, b.normalized.series[e]), y: o.y1 - l.projectValue(g && g.y ? g.y : 0, u, b.normalized.series[e]) } : { x: o.x1 + l.projectValue(g && g.x ? g.x : 0, u, b.normalized.series[e]), y: o.y1 - j.projectValue(g && g.y ? g.y : 0, k, b.normalized.series[e]) }, l instanceof c.StepAxis && (l.options.stretch || (r[l.units.pos] += f * (a.horizontalBars ? -1 : 1)), r[l.units.pos] += a.stackBars || a.distributeSeries ? 0 : i * a.seriesBarDistance * (a.horizontalBars ? -1 : 1)), t = q[k] || p, q[k] = t - (p - r[l.counterUnits.pos]), void 0 !== g) { var v = {}; v[l.units.pos + "1"] = r[l.units.pos], v[l.units.pos + "2"] = r[l.units.pos], !a.stackBars || "accumulate" !== a.stackMode && a.stackMode ? (v[l.counterUnits.pos + "1"] = p, v[l.counterUnits.pos + "2"] = r[l.counterUnits.pos]) : (v[l.counterUnits.pos + "1"] = t, v[l.counterUnits.pos + "2"] = q[k]), v.x1 = Math.min(Math.max(v.x1, o.x1), o.x2), v.x2 = Math.min(Math.max(v.x2, o.x1), o.x2), v.y1 = Math.min(Math.max(v.y1, o.y2), o.y1), v.y2 = Math.min(Math.max(v.y2, o.y2), o.y1); var w = c.getMetaData(d, k); s = h.elem("line", v, a.classNames.bar).attr({ "ct:value": [g.x, g.y].filter(c.isNumeric).join(","), "ct:meta": c.serialize(w) }), this.eventEmitter.emit("draw", c.extend({ type: "bar", value: g, index: k, meta: w, series: d, seriesIndex: e, axisX: m, axisY: n, chartRect: o, group: h, element: s }, v)) } }.bind(this)) }.bind(this)), this.eventEmitter.emit("created", { bounds: j.bounds, chartRect: o, axisX: m, axisY: n, svg: this.svg, options: a }) - } function e(a, b, d, e) { c.Bar["super"].constructor.call(this, a, b, f, c.extend({}, f, d), e) } var f = { axisX: { offset: 30, position: "end", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, scaleMinSpace: 30, onlyInteger: !1 }, axisY: { offset: 40, position: "start", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, scaleMinSpace: 20, onlyInteger: !1 }, width: void 0, height: void 0, high: void 0, low: void 0, referenceValue: 0, chartPadding: { top: 15, right: 15, bottom: 5, left: 10 }, seriesBarDistance: 15, stackBars: !1, stackMode: "accumulate", horizontalBars: !1, distributeSeries: !1, reverseData: !1, showGridBackground: !1, classNames: { chart: "ct-chart-bar", horizontalBars: "ct-horizontal-bars", label: "ct-label", labelGroup: "ct-labels", series: "ct-series", bar: "ct-bar", grid: "ct-grid", gridGroup: "ct-grids", gridBackground: "ct-grid-background", vertical: "ct-vertical", horizontal: "ct-horizontal", start: "ct-start", end: "ct-end" } }; c.Bar = c.Base.extend({ constructor: e, createChart: d }) - }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, c) { var d = b.x > a.x; return d && "explode" === c || !d && "implode" === c ? "start" : d && "implode" === c || !d && "explode" === c ? "end" : "middle" } function e(a) { var b, e, f, h, i, j = c.normalizeData(this.data), k = [], l = a.startAngle; this.svg = c.createSvg(this.container, a.width, a.height, a.donut ? a.classNames.chartDonut : a.classNames.chartPie), e = c.createChartRect(this.svg, a, g.padding), f = Math.min(e.width() / 2, e.height() / 2), i = a.total || j.normalized.series.reduce(function (a, b) { return a + b }, 0); var m = c.quantity(a.donutWidth); "%" === m.unit && (m.value *= f / 100), f -= a.donut && !a.donutSolid ? m.value / 2 : 0, h = "outside" === a.labelPosition || a.donut && !a.donutSolid ? f : "center" === a.labelPosition ? 0 : a.donutSolid ? f - m.value / 2 : f / 2, h += a.labelOffset; var n = { x: e.x1 + e.width() / 2, y: e.y2 + e.height() / 2 }, o = 1 === j.raw.series.filter(function (a) { return a.hasOwnProperty("value") ? 0 !== a.value : 0 !== a }).length; j.raw.series.forEach(function (a, b) { k[b] = this.svg.elem("g", null, null) }.bind(this)), a.showLabel && (b = this.svg.elem("g", null, null)), j.raw.series.forEach(function (e, g) { if (0 !== j.normalized.series[g] || !a.ignoreEmptyValues) { k[g].attr({ "ct:series-name": e.name }), k[g].addClass([a.classNames.series, e.className || a.classNames.series + "-" + c.alphaNumerate(g)].join(" ")); var p = i > 0 ? l + j.normalized.series[g] / i * 360 : 0, q = Math.max(0, l - (0 === g || o ? 0 : .2)); p - q >= 359.99 && (p = q + 359.99); var r, s, t, u = c.polarToCartesian(n.x, n.y, f, q), v = c.polarToCartesian(n.x, n.y, f, p), w = new c.Svg.Path(!a.donut || a.donutSolid).move(v.x, v.y).arc(f, f, 0, p - l > 180, 0, u.x, u.y); a.donut ? a.donutSolid && (t = f - m.value, r = c.polarToCartesian(n.x, n.y, t, l - (0 === g || o ? 0 : .2)), s = c.polarToCartesian(n.x, n.y, t, p), w.line(r.x, r.y), w.arc(t, t, 0, p - l > 180, 1, s.x, s.y)) : w.line(n.x, n.y); var x = a.classNames.slicePie; a.donut && (x = a.classNames.sliceDonut, a.donutSolid && (x = a.classNames.sliceDonutSolid)); var y = k[g].elem("path", { d: w.stringify() }, x); if (y.attr({ "ct:value": j.normalized.series[g], "ct:meta": c.serialize(e.meta) }), a.donut && !a.donutSolid && (y._node.style.strokeWidth = m.value + "px"), this.eventEmitter.emit("draw", { type: "slice", value: j.normalized.series[g], totalDataSum: i, index: g, meta: e.meta, series: e, group: k[g], element: y, path: w.clone(), center: n, radius: f, startAngle: l, endAngle: p }), a.showLabel) { var z; z = 1 === j.raw.series.length ? { x: n.x, y: n.y } : c.polarToCartesian(n.x, n.y, h, l + (p - l) / 2); var A; A = j.normalized.labels && !c.isFalseyButZero(j.normalized.labels[g]) ? j.normalized.labels[g] : j.normalized.series[g]; var B = a.labelInterpolationFnc(A, g); if (B || 0 === B) { var C = b.elem("text", { dx: z.x, dy: z.y, "text-anchor": d(n, z, a.labelDirection) }, a.classNames.label).text("" + B); this.eventEmitter.emit("draw", { type: "label", index: g, group: b, element: C, text: "" + B, x: z.x, y: z.y }) } } l = p } }.bind(this)), this.eventEmitter.emit("created", { chartRect: e, svg: this.svg, options: a }) } function f(a, b, d, e) { c.Pie["super"].constructor.call(this, a, b, g, c.extend({}, g, d), e) } var g = { width: void 0, height: void 0, chartPadding: 5, classNames: { chartPie: "ct-chart-pie", chartDonut: "ct-chart-donut", series: "ct-series", slicePie: "ct-slice-pie", sliceDonut: "ct-slice-donut", sliceDonutSolid: "ct-slice-donut-solid", label: "ct-label" }, startAngle: 0, total: void 0, donut: !1, donutSolid: !1, donutWidth: 60, showLabel: !0, labelOffset: 0, labelPosition: "inside", labelInterpolationFnc: c.noop, labelDirection: "neutral", reverseData: !1, ignoreEmptyValues: !1 }; c.Pie = c.Base.extend({ constructor: f, createChart: e, determineAnchorPosition: d }) }(window, document, a), a -}); - -var i, l, selectedLine = null; - -/* Navigate to hash without browser history entry */ -var navigateToHash = function () { - if (window.history !== undefined && window.history.replaceState !== undefined) { - window.history.replaceState(undefined, undefined, this.getAttribute("href")); - } -}; - -var hashLinks = document.getElementsByClassName('navigatetohash'); -for (i = 0, l = hashLinks.length; i < l; i++) { - hashLinks[i].addEventListener('click', navigateToHash); -} - -/* Switch test method */ -var switchTestMethod = function () { - var method = this.getAttribute("value"); - console.log("Selected test method: " + method); - - var lines, i, l, coverageData, lineAnalysis, cells; - - lines = document.querySelectorAll('.lineAnalysis tr'); - - for (i = 1, l = lines.length; i < l; i++) { - coverageData = JSON.parse(lines[i].getAttribute('data-coverage').replace(/'/g, '"')); - lineAnalysis = coverageData[method]; - cells = lines[i].querySelectorAll('td'); - if (lineAnalysis === undefined) { - lineAnalysis = coverageData.AllTestMethods; - if (lineAnalysis.LVS !== 'gray') { - cells[0].setAttribute('class', 'red'); - cells[1].innerText = cells[1].textContent = '0'; - cells[4].setAttribute('class', 'lightred'); - } - } else { - cells[0].setAttribute('class', lineAnalysis.LVS); - cells[1].innerText = cells[1].textContent = lineAnalysis.VC; - cells[4].setAttribute('class', 'light' + lineAnalysis.LVS); - } - } -}; - -var testMethods = document.getElementsByClassName('switchtestmethod'); -for (i = 0, l = testMethods.length; i < l; i++) { - testMethods[i].addEventListener('change', switchTestMethod); -} - -/* Highlight test method by line */ -var toggleLine = function () { - if (selectedLine === this) { - selectedLine = null; - } else { - selectedLine = null; - unhighlightTestMethods(); - highlightTestMethods.call(this); - selectedLine = this; - } - -}; -var highlightTestMethods = function () { - if (selectedLine !== null) { - return; - } - - var lineAnalysis; - var coverageData = JSON.parse(this.getAttribute('data-coverage').replace(/'/g, '"')); - var testMethods = document.getElementsByClassName('testmethod'); - - for (i = 0, l = testMethods.length; i < l; i++) { - lineAnalysis = coverageData[testMethods[i].id]; - if (lineAnalysis === undefined) { - testMethods[i].className = testMethods[i].className.replace(/\s*light.+/g, ""); - } else { - testMethods[i].className += ' light' + lineAnalysis.LVS; - } - } -}; -var unhighlightTestMethods = function () { - if (selectedLine !== null) { - return; - } - - var testMethods = document.getElementsByClassName('testmethod'); - for (i = 0, l = testMethods.length; i < l; i++) { - testMethods[i].className = testMethods[i].className.replace(/\s*light.+/g, ""); - } -}; -var coverableLines = document.getElementsByClassName('coverableline'); -for (i = 0, l = coverableLines.length; i < l; i++) { - coverableLines[i].addEventListener('click', toggleLine); - coverableLines[i].addEventListener('mouseenter', highlightTestMethods); - coverableLines[i].addEventListener('mouseleave', unhighlightTestMethods); -} - -/* History charts */ -var renderChart = function (chart) { - // Remove current children (e.g. PNG placeholder) - while (chart.firstChild) { - chart.firstChild.remove(); - } - - var chartData = window[chart.getAttribute('data-data')]; - var options = { - axisY: { - type: undefined, - onlyInteger: true - }, - lineSmooth: false, - low: 0, - high: 100, - scaleMinSpace: 20, - onlyInteger: true, - fullWidth: true - }; - var lineChart = new Chartist.Line(chart, { - labels: [], - series: chartData.series - }, options); - - /* Zoom */ - var zoomButtonDiv = document.createElement("div"); - zoomButtonDiv.className = "toggleZoom"; - var zoomButtonLink = document.createElement("a"); - zoomButtonLink.setAttribute("href", ""); - var zoomButtonText = document.createElement("i"); - zoomButtonText.className = "icon-search-plus"; - - zoomButtonLink.appendChild(zoomButtonText); - zoomButtonDiv.appendChild(zoomButtonLink); - - chart.appendChild(zoomButtonDiv); - - zoomButtonDiv.addEventListener('click', function (event) { - event.preventDefault(); - - if (options.axisY.type === undefined) { - options.axisY.type = Chartist.AutoScaleAxis; - zoomButtonText.className = "icon-search-minus"; - } else { - options.axisY.type = undefined; - zoomButtonText.className = "icon-search-plus"; - } - - lineChart.update(null, options); - }); - - var tooltip = document.createElement("div"); - tooltip.className = "tooltip"; - - chart.appendChild(tooltip); - - /* Tooltips */ - var showToolTip = function () { - var point = this; - var index = [].slice.call(chart.getElementsByClassName('ct-point')).indexOf(point); - - tooltip.innerHTML = chartData.tooltips[index % chartData.tooltips.length]; - tooltip.style.display = 'block'; - }; - - var moveToolTip = function (event) { - var box = chart.getBoundingClientRect(); - var left = event.pageX - box.left - window.pageXOffset; - var top = event.pageY - box.top - window.pageYOffset; - - tooltip.style.left = left - tooltip.offsetWidth / 2 - 5 + 'px'; - tooltip.style.top = top - tooltip.offsetHeight - 40 + 'px'; - }; - - var hideToolTip = function () { - tooltip.style.display = 'none'; - }; - - chart.addEventListener('mousemove', moveToolTip); - - lineChart.on('created', function () { - var chartPoints = chart.getElementsByClassName('ct-point'); - for (i = 0, l = chartPoints.length; i < l; i++) { - chartPoints[i].addEventListener('mousemove', showToolTip); - chartPoints[i].addEventListener('mouseout', hideToolTip); - } - }); -}; - -var charts = document.getElementsByClassName('historychart'); -for (i = 0, l = charts.length; i < l; i++) { - renderChart(charts[i]); -} \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/coverage.xml b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/coverage.xml deleted file mode 100644 index dbfc7f84..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/coverage.xml +++ /dev/null @@ -1,1270 +0,0 @@ - - - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll - 2021-02-09T23:13:42Z - mscorlib - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\vstest.console.exe - 2021-03-10T01:18:39.6988681Z - vstest.console - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll - 2021-03-10T01:18:39.1758657Z - Microsoft.TestPlatform.CoreUtilities - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll - 2020-10-08T02:21:33.8194762Z - System - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.Client.dll - 2021-03-10T01:18:39.3608699Z - Microsoft.VisualStudio.TestPlatform.Client - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.Common.dll - 2021-03-10T01:18:39.3648679Z - Microsoft.VisualStudio.TestPlatform.Common - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll - 2021-02-09T23:13:40.5754059Z - System.Core - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll - 2019-12-07T09:10:37.7737543Z - System.Xml - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.Utilities.dll - 2021-03-10T01:18:39.1838668Z - Microsoft.TestPlatform.Utilities - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\NuGet.Frameworks.dll - 2021-03-10T01:18:39.455872Z - NuGet.Frameworks - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll - 2020-06-05T05:04:05.335002Z - System.Configuration - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.Extensions.FileSystemGlobbing.dll - 2021-03-10T01:18:39.1398684Z - Microsoft.Extensions.FileSystemGlobbing - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CrossPlatEngine.dll - 2021-03-10T01:18:39.1798685Z - Microsoft.TestPlatform.CrossPlatEngine - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.Cci.dll - 2021-03-10T01:18:40.1568669Z - Microsoft.Cci - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll - 2019-12-07T09:10:34.4923358Z - System.Runtime - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.InteropServices\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.InteropServices.dll - 2019-12-07T09:10:35.9300981Z - System.Runtime.InteropServices - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll - 2019-12-07T09:10:34.5705678Z - System.Collections - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.TestPlatform.Extensions.BlameDataCollector.dll - 2021-03-10T01:18:38.7708669Z - Microsoft.TestPlatform.Extensions.BlameDataCollector - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.TestPlatform.Extensions.EventLogCollector.dll - 2021-03-10T01:18:38.7728666Z - Microsoft.TestPlatform.Extensions.EventLogCollector - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.TestPlatform.TestHostRuntimeProvider.dll - 2021-03-10T01:18:38.7748692Z - Microsoft.TestPlatform.TestHostRuntimeProvider - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.ArchitectureTools.PEReader.dll - 2021-03-10T01:18:39.2018677Z - Microsoft.VisualStudio.ArchitectureTools.PEReader - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.Coverage.Interprocess.dll - 2021-03-10T01:18:38.7888677Z - Microsoft.VisualStudio.Coverage.Interprocess - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\netstandard\v4.0_2.0.0.0__cc7b13ffcd2ddd51\netstandard.dll - 2019-12-07T09:10:37.6330785Z - netstandard - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.ValueTuple\v4.0_4.0.0.0__cc7b13ffcd2ddd51\System.ValueTuple.dll - 2019-12-07T09:10:37.742541Z - System.ValueTuple - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.Fakes.DataCollector.dll - 2021-03-10T01:18:40.1588666Z - Microsoft.VisualStudio.Fakes.DataCollector - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.CodedWebTestAdapter.dll - 2021-03-10T01:18:38.7898664Z - Microsoft.VisualStudio.TestPlatform.Extensions.CodedWebTestAdapter - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.TmiAdapter.dll - 2021-03-10T01:18:38.7968662Z - Microsoft.VisualStudio.TestPlatform.Extensions.TmiAdapter - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.Common.dll - 2021-03-10T01:18:39.2388684Z - Microsoft.VisualStudio.QualityTools.Common - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.GenericTestAdapter.dll - 2021-03-10T01:18:38.7918661Z - Microsoft.VisualStudio.TestPlatform.Extensions.GenericTestAdapter - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger.dll - 2021-03-10T01:18:38.792866Z - Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.OrderedTestAdapter.dll - 2021-03-10T01:18:38.7948677Z - Microsoft.VisualStudio.TestPlatform.Extensions.OrderedTestAdapter - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.dll - 2021-03-10T01:18:38.7998656Z - Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.VSTestIntegration.dll - 2021-03-10T01:18:38.8038671Z - Microsoft.VisualStudio.TestPlatform.Extensions.VSTestIntegration - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll - 2021-03-10T01:18:39.3488657Z - Microsoft.VisualStudio.QualityTools.UnitTestFramework - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll - 2020-09-04T22:37:08Z - System.Data - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.WebTestAdapter.dll - 2021-03-10T01:18:38.8048665Z - Microsoft.VisualStudio.TestPlatform.Extensions.WebTestAdapter - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.CppUnitTestFramework.ComInterfaces.dll - 2021-03-10T01:18:38.8068659Z - Microsoft.VisualStudio.TestTools.CppUnitTestFramework.ComInterfaces - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.CppUnitTestFramework.CppUnitTestExtension.dll - 2021-03-10T01:18:38.8078663Z - Microsoft.VisualStudio.TestTools.CppUnitTestFramework.CppUnitTestExtension - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.DataCollection.MediaRecorder.Model.dll - 2021-03-10T01:18:38.8098657Z - Microsoft.VisualStudio.TestTools.DataCollection.MediaRecorder.Model - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.DataCollection.VideoRecorderCollector.dll - 2021-03-10T01:18:38.8118659Z - Microsoft.VisualStudio.TestTools.DataCollection.VideoRecorderCollector - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TraceDataCollector.dll - 2021-03-10T01:18:38.8168685Z - Microsoft.VisualStudio.TraceDataCollector - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.IntelliTrace.Core.dll - 2021-03-10T01:18:39.1678662Z - Microsoft.IntelliTrace.Core - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\System.Reflection.Metadata.dll - 2021-03-10T01:18:39.5908676Z - System.Reflection.Metadata - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.dll - 2019-12-07T09:10:34.5552678Z - System.IO - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\System.Collections.Immutable.dll - 2021-03-10T01:18:39.5778692Z - System.Collections.Immutable - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll - 2019-12-07T09:10:36.0237545Z - System.Reflection - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO.FileSystem\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.FileSystem.dll - 2019-12-07T09:10:36.0095245Z - System.IO.FileSystem - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO.MemoryMappedFiles\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.MemoryMappedFiles.dll - 2019-12-07T09:10:34.5552678Z - System.IO.MemoryMappedFiles - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Handles\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Handles.dll - 2019-12-07T09:10:37.6643716Z - System.Runtime.Handles - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll - 2019-12-07T09:10:37.7113237Z - System.Reflection.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll - 2019-12-07T09:10:34.5860246Z - System.Threading - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Text.Encoding\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Text.Encoding.dll - 2019-12-07T09:10:36.0705812Z - System.Text.Encoding - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll - 2019-12-07T09:10:37.6174386Z - System.Runtime.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Text.Encoding.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Text.Encoding.Extensions.dll - 2019-12-07T09:10:36.1495949Z - System.Text.Encoding.Extensions - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.Fakes.dll - 2021-03-10T01:18:40.1608669Z - Microsoft.VisualStudio.TestPlatform.Fakes - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CommunicationUtilities.dll - 2021-03-10T01:18:39.1748663Z - Microsoft.TestPlatform.CommunicationUtilities - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Newtonsoft.Json.dll - 2021-03-10T01:18:39.451869Z - Newtonsoft.Json - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll - 2018-06-05T04:51:36Z - Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Resources.ResourceManager\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Resources.ResourceManager.dll - 2019-12-07T09:10:37.6960843Z - System.Resources.ResourceManager - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - 2018-06-05T04:47:02Z - Microsoft.VisualStudio.TestPlatform.TestFramework - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll - 2018-06-05T04:50:06Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.ComponentModel.Composition\v4.0_4.0.0.0__b77a5c561934e089\System.ComponentModel.Composition.dll - 2019-12-07T09:10:36.1023351Z - System.ComponentModel.Composition - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll - 2019-12-07T09:10:36.086204Z - System.Linq - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml.ReaderWriter\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Xml.ReaderWriter.dll - 2019-12-07T09:10:34.5705678Z - System.Xml.ReaderWriter - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml.XDocument\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Xml.XDocument.dll - 2019-12-07T09:10:34.5552678Z - System.Xml.XDocument - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml.Linq\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.Linq.dll - 2019-12-07T09:10:35.962301Z - System.Xml.Linq - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll - 2019-12-07T09:10:36.086204Z - System.Globalization - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\UnitTestProject1.dll - 2019-04-19T07:50:12.1584913Z - UnitTestProject1 - - - - - - - <Module> - - - - - UnitTestProject1.UnitTest1 - - - - 100663297 - System.Void UnitTestProject1.UnitTest1::TestMethod1() - - - - - - - - - - - - - - 100663298 - System.Void UnitTestProject1.UnitTest1::TestMethod2() - - - - - - - - - - - - - - 100663299 - System.Void UnitTestProject1.UnitTest1::.ctor() - - - - - - - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll - 2020-10-08T02:21:33.8194762Z - System - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll - 2021-03-10T01:18:39.1758657Z - Microsoft.TestPlatform.CoreUtilities - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll - 2020-10-08T02:21:33.8194762Z - System - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll - 2018-06-05T04:51:36Z - Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll - 2019-12-07T09:10:34.4923358Z - System.Runtime - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - 2018-06-05T04:47:02Z - Microsoft.VisualStudio.TestPlatform.TestFramework - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll - 2019-12-07T09:10:34.5705678Z - System.Collections - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll - 2018-06-05T04:50:06Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll - 2019-12-07T09:10:36.0237545Z - System.Reflection - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll - 2019-12-07T09:10:36.086204Z - System.Globalization - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll - 2019-12-07T09:10:36.086204Z - System.Linq - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll - 2021-02-09T23:13:40.5754059Z - System.Core - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll - 2019-12-07T09:10:34.5860246Z - System.Threading - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll - 2019-12-07T09:10:37.7113237Z - System.Reflection.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll - 2019-12-07T09:10:34.5705678Z - System.Threading.Tasks - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll - 2019-12-07T09:10:37.6174386Z - System.Runtime.Extensions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - 2018-06-05T04:53:44Z - Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Serialization\v4.0_4.0.0.0__b77a5c561934e089\System.Runtime.Serialization.dll - 2020-08-05T02:39:55.9298663Z - System.Runtime.Serialization - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject2\bin\debug\UnitTestProject2.dll - 2019-04-19T07:50:12.1584913Z - UnitTestProject2 - - - - - - - <Module> - - - - - UnitTestProject2.UnitTest1 - - - - 100663297 - System.Void UnitTestProject2.UnitTest1::TestMethod1() - - - - - - - - - - - - 100663298 - System.Void UnitTestProject2.UnitTest1::.ctor() - - - - - - - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll - 2021-03-10T01:18:39.1758657Z - Microsoft.TestPlatform.CoreUtilities - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll - 2020-10-08T02:21:33.8194762Z - System - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll - 2018-06-05T04:51:36Z - Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll - 2019-12-07T09:10:34.4923358Z - System.Runtime - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - 2018-06-05T04:47:02Z - Microsoft.VisualStudio.TestPlatform.TestFramework - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll - 2019-12-07T09:10:34.5705678Z - System.Collections - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll - 2018-06-05T04:50:06Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll - 2019-12-07T09:10:36.0237545Z - System.Reflection - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll - 2019-12-07T09:10:36.086204Z - System.Globalization - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll - 2019-12-07T09:10:36.086204Z - System.Linq - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll - 2021-02-09T23:13:40.5754059Z - System.Core - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll - 2019-12-07T09:10:34.5860246Z - System.Threading - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll - 2019-12-07T09:10:37.7113237Z - System.Reflection.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll - 2019-12-07T09:10:34.5705678Z - System.Threading.Tasks - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll - 2019-12-07T09:10:37.6174386Z - System.Runtime.Extensions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - 2018-06-05T04:53:44Z - Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Serialization\v4.0_4.0.0.0__b77a5c561934e089\System.Runtime.Serialization.dll - 2020-08-05T02:39:55.9298663Z - System.Runtime.Serialization - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections.Concurrent\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.Concurrent.dll - 2019-12-07T09:10:34.5552678Z - System.Collections.Concurrent - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll - 2019-12-07T09:10:34.5705678Z - System.Threading.Tasks - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll - 2021-03-10T01:18:39.1758657Z - Microsoft.TestPlatform.CoreUtilities - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll - 2020-10-08T02:21:33.8194762Z - System - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll - 2018-06-05T04:51:36Z - Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll - 2019-12-07T09:10:34.4923358Z - System.Runtime - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - 2018-06-05T04:47:02Z - Microsoft.VisualStudio.TestPlatform.TestFramework - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll - 2019-12-07T09:10:34.5705678Z - System.Collections - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll - 2019-12-07T09:10:36.0237545Z - System.Reflection - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll - 2019-12-07T09:10:37.6174386Z - System.Runtime.Extensions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll - 2018-06-05T04:50:06Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll - 2019-12-07T09:10:36.086204Z - System.Linq - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll - 2021-02-09T23:13:40.5754059Z - System.Core - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll - 2019-12-07T09:10:36.086204Z - System.Globalization - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.dll - 2019-12-07T09:10:34.5552678Z - System.IO - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - 2018-06-05T04:53:44Z - Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll - 2020-09-04T22:37:08Z - System.Data - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll - 2019-12-07T09:10:34.5860246Z - System.Threading - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll - 2019-12-07T09:10:37.7113237Z - System.Reflection.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll - 2019-12-07T09:10:34.5705678Z - System.Threading.Tasks - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll - 2020-06-05T05:04:05.335002Z - System.Configuration - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll - 2019-12-07T09:10:37.7737543Z - System.Xml - - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\ClassLibrary1.dll - 2019-04-19T06:37:47.6105603Z - ClassLibrary1 - - - - - - - <Module> - - - - - ClassLibrary1.Calculation - - - - 100663297 - System.Int32 ClassLibrary1.Calculation::Add(System.Int32,System.Int32) - - - - - - - - - - - - 100663298 - System.Int32 ClassLibrary1.Calculation::Sub(System.Int32,System.Int32) - - - - - - - - - - - - 100663299 - System.Int32 ClassLibrary1.Calculation::Sub1(System.Int32,System.Int32) - - - - - - - - - - - - 100663300 - System.Void ClassLibrary1.Calculation::.ctor() - - - - - - - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Resources.ResourceManager\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Resources.ResourceManager.dll - 2019-12-07T09:10:37.6960843Z - System.Resources.ResourceManager - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll - 2021-03-10T01:18:39.1758657Z - Microsoft.TestPlatform.CoreUtilities - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Text.RegularExpressions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Text.RegularExpressions.dll - 2019-12-07T09:10:34.4610177Z - System.Text.RegularExpressions - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll - 2021-03-10T01:18:39.1758657Z - Microsoft.TestPlatform.CoreUtilities - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll - 2020-10-08T02:21:33.8194762Z - System - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll - 2018-06-05T04:51:36Z - Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll - 2019-12-07T09:10:34.4923358Z - System.Runtime - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - 2018-06-05T04:47:02Z - Microsoft.VisualStudio.TestPlatform.TestFramework - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll - 2019-12-07T09:10:34.5705678Z - System.Collections - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll - 2019-12-07T09:10:36.0237545Z - System.Reflection - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll - 2019-12-07T09:10:37.6174386Z - System.Runtime.Extensions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll - 2018-06-05T04:50:06Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll - 2019-12-07T09:10:36.086204Z - System.Linq - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll - 2021-02-09T23:13:40.5754059Z - System.Core - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll - 2019-12-07T09:10:36.086204Z - System.Globalization - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.dll - 2019-12-07T09:10:34.5552678Z - System.IO - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - 2018-06-05T04:53:44Z - Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll - 2020-09-04T22:37:08Z - System.Data - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll - 2019-12-07T09:10:34.5860246Z - System.Threading - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll - 2019-12-07T09:10:37.7113237Z - System.Reflection.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll - 2019-12-07T09:10:34.5705678Z - System.Threading.Tasks - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll - 2020-06-05T05:04:05.335002Z - System.Configuration - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll - 2019-12-07T09:10:37.7737543Z - System.Xml - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.ExecutionCommon.dll - 2021-03-10T01:18:39.2588672Z - Microsoft.VisualStudio.QualityTools.ExecutionCommon - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.Resource.dll - 2021-03-10T01:18:39.3078688Z - Microsoft.VisualStudio.QualityTools.Resource - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.WebTestFramework.dll - 2021-03-10T01:18:39.3588667Z - Microsoft.VisualStudio.QualityTools.WebTestFramework - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_cube.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_cube.svg deleted file mode 100644 index 11b5cabf..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_cube.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_down-dir_active.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_down-dir_active.svg deleted file mode 100644 index d11cf041..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_down-dir_active.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_fork.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_fork.svg deleted file mode 100644 index f0148b3a..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_fork.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_info-circled.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_info-circled.svg deleted file mode 100644 index 252166bb..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_info-circled.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_minus.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_minus.svg deleted file mode 100644 index 3c30c365..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_minus.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_plus.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_plus.svg deleted file mode 100644 index 79327232..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_plus.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-minus.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-minus.svg deleted file mode 100644 index c174eb5e..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-minus.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-plus.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-plus.svg deleted file mode 100644 index 04b24ecc..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-plus.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir.svg deleted file mode 100644 index 567c11f3..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir_active.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir_active.svg deleted file mode 100644 index bb225544..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir_active.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_wrench.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_wrench.svg deleted file mode 100644 index 0e9a8601..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_wrench.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/index.htm b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/index.htm deleted file mode 100644 index f2f23f7e..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/index.htm +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - -Summary - Coverage Report - -
-

Summary

- ---- - - - - - - - - - - - - - - - -
Generated on:2021/3/26 - 下午 04:34:49
Parser:OpenCoverParser
Assemblies:3
Classes:3
Files:3
Covered lines:15
Uncovered lines:7
Coverable lines:22
Total lines:59
Line coverage:68.1% (15 of 22)
Covered branches:0
Total branches:0
Tag:Build.2019.11.11
-

Coverage History

-
- -

Risk Hotspots

- - -

No risk hotspots found.

-

Coverage

- - ----------- - - - - - - - - - -
NameCoveredUncoveredCoverableTotalLine coverageBranch coverage
ClassLibrary13691833.3%
  
 
ClassLibrary1.Calculation3691833.3%
  
 
UnitTestProject191102690%
  
 
UnitTestProject1.UnitTest191102690%
  
 
UnitTestProject230315100%
 
 
UnitTestProject2.UnitTest130315100%
 
 
-
-
- - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/main.js b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/main.js deleted file mode 100644 index 160252eb..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/main.js +++ /dev/null @@ -1,274 +0,0 @@ -/* Chartist.js 0.11.0 - * Copyright © 2017 Gion Kunz - * Free to use under either the WTFPL license or the MIT license. - * https://raw.githubusercontent.com/gionkunz/chartist-js/master/LICENSE-WTFPL - * https://raw.githubusercontent.com/gionkunz/chartist-js/master/LICENSE-MIT - */ - -!function (a, b) { "function" == typeof define && define.amd ? define("Chartist", [], function () { return a.Chartist = b() }) : "object" == typeof module && module.exports ? module.exports = b() : a.Chartist = b() }(this, function () { - var a = { version: "0.11.0" }; return function (a, b, c) { "use strict"; c.namespaces = { svg: "http://www.w3.org/2000/svg", xmlns: "http://www.w3.org/2000/xmlns/", xhtml: "http://www.w3.org/1999/xhtml", xlink: "http://www.w3.org/1999/xlink", ct: "http://gionkunz.github.com/chartist-js/ct" }, c.noop = function (a) { return a }, c.alphaNumerate = function (a) { return String.fromCharCode(97 + a % 26) }, c.extend = function (a) { var b, d, e; for (a = a || {}, b = 1; b < arguments.length; b++) { d = arguments[b]; for (var f in d) e = d[f], "object" != typeof e || null === e || e instanceof Array ? a[f] = e : a[f] = c.extend(a[f], e) } return a }, c.replaceAll = function (a, b, c) { return a.replace(new RegExp(b, "g"), c) }, c.ensureUnit = function (a, b) { return "number" == typeof a && (a += b), a }, c.quantity = function (a) { if ("string" == typeof a) { var b = /^(\d+)\s*(.*)$/g.exec(a); return { value: +b[1], unit: b[2] || void 0 } } return { value: a } }, c.querySelector = function (a) { return a instanceof Node ? a : b.querySelector(a) }, c.times = function (a) { return Array.apply(null, new Array(a)) }, c.sum = function (a, b) { return a + (b ? b : 0) }, c.mapMultiply = function (a) { return function (b) { return b * a } }, c.mapAdd = function (a) { return function (b) { return b + a } }, c.serialMap = function (a, b) { var d = [], e = Math.max.apply(null, a.map(function (a) { return a.length })); return c.times(e).forEach(function (c, e) { var f = a.map(function (a) { return a[e] }); d[e] = b.apply(null, f) }), d }, c.roundWithPrecision = function (a, b) { var d = Math.pow(10, b || c.precision); return Math.round(a * d) / d }, c.precision = 8, c.escapingMap = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }, c.serialize = function (a) { return null === a || void 0 === a ? a : ("number" == typeof a ? a = "" + a : "object" == typeof a && (a = JSON.stringify({ data: a })), Object.keys(c.escapingMap).reduce(function (a, b) { return c.replaceAll(a, b, c.escapingMap[b]) }, a)) }, c.deserialize = function (a) { if ("string" != typeof a) return a; a = Object.keys(c.escapingMap).reduce(function (a, b) { return c.replaceAll(a, c.escapingMap[b], b) }, a); try { a = JSON.parse(a), a = void 0 !== a.data ? a.data : a } catch (b) { } return a }, c.createSvg = function (a, b, d, e) { var f; return b = b || "100%", d = d || "100%", Array.prototype.slice.call(a.querySelectorAll("svg")).filter(function (a) { return a.getAttributeNS(c.namespaces.xmlns, "ct") }).forEach(function (b) { a.removeChild(b) }), f = new c.Svg("svg").attr({ width: b, height: d }).addClass(e), f._node.style.width = b, f._node.style.height = d, a.appendChild(f._node), f }, c.normalizeData = function (a, b, d) { var e, f = { raw: a, normalized: {} }; return f.normalized.series = c.getDataArray({ series: a.series || [] }, b, d), e = f.normalized.series.every(function (a) { return a instanceof Array }) ? Math.max.apply(null, f.normalized.series.map(function (a) { return a.length })) : f.normalized.series.length, f.normalized.labels = (a.labels || []).slice(), Array.prototype.push.apply(f.normalized.labels, c.times(Math.max(0, e - f.normalized.labels.length)).map(function () { return "" })), b && c.reverseData(f.normalized), f }, c.safeHasProperty = function (a, b) { return null !== a && "object" == typeof a && a.hasOwnProperty(b) }, c.isDataHoleValue = function (a) { return null === a || void 0 === a || "number" == typeof a && isNaN(a) }, c.reverseData = function (a) { a.labels.reverse(), a.series.reverse(); for (var b = 0; b < a.series.length; b++)"object" == typeof a.series[b] && void 0 !== a.series[b].data ? a.series[b].data.reverse() : a.series[b] instanceof Array && a.series[b].reverse() }, c.getDataArray = function (a, b, d) { function e(a) { if (c.safeHasProperty(a, "value")) return e(a.value); if (c.safeHasProperty(a, "data")) return e(a.data); if (a instanceof Array) return a.map(e); if (!c.isDataHoleValue(a)) { if (d) { var b = {}; return "string" == typeof d ? b[d] = c.getNumberOrUndefined(a) : b.y = c.getNumberOrUndefined(a), b.x = a.hasOwnProperty("x") ? c.getNumberOrUndefined(a.x) : b.x, b.y = a.hasOwnProperty("y") ? c.getNumberOrUndefined(a.y) : b.y, b } return c.getNumberOrUndefined(a) } } return a.series.map(e) }, c.normalizePadding = function (a, b) { return b = b || 0, "number" == typeof a ? { top: a, right: a, bottom: a, left: a } : { top: "number" == typeof a.top ? a.top : b, right: "number" == typeof a.right ? a.right : b, bottom: "number" == typeof a.bottom ? a.bottom : b, left: "number" == typeof a.left ? a.left : b } }, c.getMetaData = function (a, b) { var c = a.data ? a.data[b] : a[b]; return c ? c.meta : void 0 }, c.orderOfMagnitude = function (a) { return Math.floor(Math.log(Math.abs(a)) / Math.LN10) }, c.projectLength = function (a, b, c) { return b / c.range * a }, c.getAvailableHeight = function (a, b) { return Math.max((c.quantity(b.height).value || a.height()) - (b.chartPadding.top + b.chartPadding.bottom) - b.axisX.offset, 0) }, c.getHighLow = function (a, b, d) { function e(a) { if (void 0 !== a) if (a instanceof Array) for (var b = 0; b < a.length; b++)e(a[b]); else { var c = d ? +a[d] : +a; g && c > f.high && (f.high = c), h && c < f.low && (f.low = c) } } b = c.extend({}, b, d ? b["axis" + d.toUpperCase()] : {}); var f = { high: void 0 === b.high ? -Number.MAX_VALUE : +b.high, low: void 0 === b.low ? Number.MAX_VALUE : +b.low }, g = void 0 === b.high, h = void 0 === b.low; return (g || h) && e(a), (b.referenceValue || 0 === b.referenceValue) && (f.high = Math.max(b.referenceValue, f.high), f.low = Math.min(b.referenceValue, f.low)), f.high <= f.low && (0 === f.low ? f.high = 1 : f.low < 0 ? f.high = 0 : f.high > 0 ? f.low = 0 : (f.high = 1, f.low = 0)), f }, c.isNumeric = function (a) { return null !== a && isFinite(a) }, c.isFalseyButZero = function (a) { return !a && 0 !== a }, c.getNumberOrUndefined = function (a) { return c.isNumeric(a) ? +a : void 0 }, c.isMultiValue = function (a) { return "object" == typeof a && ("x" in a || "y" in a) }, c.getMultiValue = function (a, b) { return c.isMultiValue(a) ? c.getNumberOrUndefined(a[b || "y"]) : c.getNumberOrUndefined(a) }, c.rho = function (a) { function b(a, c) { return a % c === 0 ? c : b(c, a % c) } function c(a) { return a * a + 1 } if (1 === a) return a; var d, e = 2, f = 2; if (a % 2 === 0) return 2; do e = c(e) % a, f = c(c(f)) % a, d = b(Math.abs(e - f), a); while (1 === d); return d }, c.getBounds = function (a, b, d, e) { function f(a, b) { return a === (a += b) && (a *= 1 + (b > 0 ? o : -o)), a } var g, h, i, j = 0, k = { high: b.high, low: b.low }; k.valueRange = k.high - k.low, k.oom = c.orderOfMagnitude(k.valueRange), k.step = Math.pow(10, k.oom), k.min = Math.floor(k.low / k.step) * k.step, k.max = Math.ceil(k.high / k.step) * k.step, k.range = k.max - k.min, k.numberOfSteps = Math.round(k.range / k.step); var l = c.projectLength(a, k.step, k), m = l < d, n = e ? c.rho(k.range) : 0; if (e && c.projectLength(a, 1, k) >= d) k.step = 1; else if (e && n < k.step && c.projectLength(a, n, k) >= d) k.step = n; else for (; ;) { if (m && c.projectLength(a, k.step, k) <= d) k.step *= 2; else { if (m || !(c.projectLength(a, k.step / 2, k) >= d)) break; if (k.step /= 2, e && k.step % 1 !== 0) { k.step *= 2; break } } if (j++ > 1e3) throw new Error("Exceeded maximum number of iterations while optimizing scale step!") } var o = 2.221e-16; for (k.step = Math.max(k.step, o), h = k.min, i = k.max; h + k.step <= k.low;)h = f(h, k.step); for (; i - k.step >= k.high;)i = f(i, -k.step); k.min = h, k.max = i, k.range = k.max - k.min; var p = []; for (g = k.min; g <= k.max; g = f(g, k.step)) { var q = c.roundWithPrecision(g); q !== p[p.length - 1] && p.push(q) } return k.values = p, k }, c.polarToCartesian = function (a, b, c, d) { var e = (d - 90) * Math.PI / 180; return { x: a + c * Math.cos(e), y: b + c * Math.sin(e) } }, c.createChartRect = function (a, b, d) { var e = !(!b.axisX && !b.axisY), f = e ? b.axisY.offset : 0, g = e ? b.axisX.offset : 0, h = a.width() || c.quantity(b.width).value || 0, i = a.height() || c.quantity(b.height).value || 0, j = c.normalizePadding(b.chartPadding, d); h = Math.max(h, f + j.left + j.right), i = Math.max(i, g + j.top + j.bottom); var k = { padding: j, width: function () { return this.x2 - this.x1 }, height: function () { return this.y1 - this.y2 } }; return e ? ("start" === b.axisX.position ? (k.y2 = j.top + g, k.y1 = Math.max(i - j.bottom, k.y2 + 1)) : (k.y2 = j.top, k.y1 = Math.max(i - j.bottom - g, k.y2 + 1)), "start" === b.axisY.position ? (k.x1 = j.left + f, k.x2 = Math.max(h - j.right, k.x1 + 1)) : (k.x1 = j.left, k.x2 = Math.max(h - j.right - f, k.x1 + 1))) : (k.x1 = j.left, k.x2 = Math.max(h - j.right, k.x1 + 1), k.y2 = j.top, k.y1 = Math.max(i - j.bottom, k.y2 + 1)), k }, c.createGrid = function (a, b, d, e, f, g, h, i) { var j = {}; j[d.units.pos + "1"] = a, j[d.units.pos + "2"] = a, j[d.counterUnits.pos + "1"] = e, j[d.counterUnits.pos + "2"] = e + f; var k = g.elem("line", j, h.join(" ")); i.emit("draw", c.extend({ type: "grid", axis: d, index: b, group: g, element: k }, j)) }, c.createGridBackground = function (a, b, c, d) { var e = a.elem("rect", { x: b.x1, y: b.y2, width: b.width(), height: b.height() }, c, !0); d.emit("draw", { type: "gridBackground", group: a, element: e }) }, c.createLabel = function (a, d, e, f, g, h, i, j, k, l, m) { var n, o = {}; if (o[g.units.pos] = a + i[g.units.pos], o[g.counterUnits.pos] = i[g.counterUnits.pos], o[g.units.len] = d, o[g.counterUnits.len] = Math.max(0, h - 10), l) { var p = b.createElement("span"); p.className = k.join(" "), p.setAttribute("xmlns", c.namespaces.xhtml), p.innerText = f[e], p.style[g.units.len] = Math.round(o[g.units.len]) + "px", p.style[g.counterUnits.len] = Math.round(o[g.counterUnits.len]) + "px", n = j.foreignObject(p, c.extend({ style: "overflow: visible;" }, o)) } else n = j.elem("text", o, k.join(" ")).text(f[e]); m.emit("draw", c.extend({ type: "label", axis: g, index: e, group: j, element: n, text: f[e] }, o)) }, c.getSeriesOption = function (a, b, c) { if (a.name && b.series && b.series[a.name]) { var d = b.series[a.name]; return d.hasOwnProperty(c) ? d[c] : b[c] } return b[c] }, c.optionsProvider = function (b, d, e) { function f(b) { var f = h; if (h = c.extend({}, j), d) for (i = 0; i < d.length; i++) { var g = a.matchMedia(d[i][0]); g.matches && (h = c.extend(h, d[i][1])) } e && b && e.emit("optionsChanged", { previousOptions: f, currentOptions: h }) } function g() { k.forEach(function (a) { a.removeListener(f) }) } var h, i, j = c.extend({}, b), k = []; if (!a.matchMedia) throw "window.matchMedia not found! Make sure you're using a polyfill."; if (d) for (i = 0; i < d.length; i++) { var l = a.matchMedia(d[i][0]); l.addListener(f), k.push(l) } return f(), { removeMediaQueryListeners: g, getCurrentOptions: function () { return c.extend({}, h) } } }, c.splitIntoSegments = function (a, b, d) { var e = { increasingX: !1, fillHoles: !1 }; d = c.extend({}, e, d); for (var f = [], g = !0, h = 0; h < a.length; h += 2)void 0 === c.getMultiValue(b[h / 2].value) ? d.fillHoles || (g = !0) : (d.increasingX && h >= 2 && a[h] <= a[h - 2] && (g = !0), g && (f.push({ pathCoordinates: [], valueData: [] }), g = !1), f[f.length - 1].pathCoordinates.push(a[h], a[h + 1]), f[f.length - 1].valueData.push(b[h / 2])); return f } }(window, document, a), function (a, b, c) { "use strict"; c.Interpolation = {}, c.Interpolation.none = function (a) { var b = { fillHoles: !1 }; return a = c.extend({}, b, a), function (b, d) { for (var e = new c.Svg.Path, f = !0, g = 0; g < b.length; g += 2) { var h = b[g], i = b[g + 1], j = d[g / 2]; void 0 !== c.getMultiValue(j.value) ? (f ? e.move(h, i, !1, j) : e.line(h, i, !1, j), f = !1) : a.fillHoles || (f = !0) } return e } }, c.Interpolation.simple = function (a) { var b = { divisor: 2, fillHoles: !1 }; a = c.extend({}, b, a); var d = 1 / Math.max(1, a.divisor); return function (b, e) { for (var f, g, h, i = new c.Svg.Path, j = 0; j < b.length; j += 2) { var k = b[j], l = b[j + 1], m = (k - f) * d, n = e[j / 2]; void 0 !== n.value ? (void 0 === h ? i.move(k, l, !1, n) : i.curve(f + m, g, k - m, l, k, l, !1, n), f = k, g = l, h = n) : a.fillHoles || (f = k = h = void 0) } return i } }, c.Interpolation.cardinal = function (a) { var b = { tension: 1, fillHoles: !1 }; a = c.extend({}, b, a); var d = Math.min(1, Math.max(0, a.tension)), e = 1 - d; return function f(b, g) { var h = c.splitIntoSegments(b, g, { fillHoles: a.fillHoles }); if (h.length) { if (h.length > 1) { var i = []; return h.forEach(function (a) { i.push(f(a.pathCoordinates, a.valueData)) }), c.Svg.Path.join(i) } if (b = h[0].pathCoordinates, g = h[0].valueData, b.length <= 4) return c.Interpolation.none()(b, g); for (var j, k = (new c.Svg.Path).move(b[0], b[1], !1, g[0]), l = 0, m = b.length; m - 2 * !j > l; l += 2) { var n = [{ x: +b[l - 2], y: +b[l - 1] }, { x: +b[l], y: +b[l + 1] }, { x: +b[l + 2], y: +b[l + 3] }, { x: +b[l + 4], y: +b[l + 5] }]; j ? l ? m - 4 === l ? n[3] = { x: +b[0], y: +b[1] } : m - 2 === l && (n[2] = { x: +b[0], y: +b[1] }, n[3] = { x: +b[2], y: +b[3] }) : n[0] = { x: +b[m - 2], y: +b[m - 1] } : m - 4 === l ? n[3] = n[2] : l || (n[0] = { x: +b[l], y: +b[l + 1] }), k.curve(d * (-n[0].x + 6 * n[1].x + n[2].x) / 6 + e * n[2].x, d * (-n[0].y + 6 * n[1].y + n[2].y) / 6 + e * n[2].y, d * (n[1].x + 6 * n[2].x - n[3].x) / 6 + e * n[2].x, d * (n[1].y + 6 * n[2].y - n[3].y) / 6 + e * n[2].y, n[2].x, n[2].y, !1, g[(l + 2) / 2]) } return k } return c.Interpolation.none()([]) } }, c.Interpolation.monotoneCubic = function (a) { var b = { fillHoles: !1 }; return a = c.extend({}, b, a), function d(b, e) { var f = c.splitIntoSegments(b, e, { fillHoles: a.fillHoles, increasingX: !0 }); if (f.length) { if (f.length > 1) { var g = []; return f.forEach(function (a) { g.push(d(a.pathCoordinates, a.valueData)) }), c.Svg.Path.join(g) } if (b = f[0].pathCoordinates, e = f[0].valueData, b.length <= 4) return c.Interpolation.none()(b, e); var h, i, j = [], k = [], l = b.length / 2, m = [], n = [], o = [], p = []; for (h = 0; h < l; h++)j[h] = b[2 * h], k[h] = b[2 * h + 1]; for (h = 0; h < l - 1; h++)o[h] = k[h + 1] - k[h], p[h] = j[h + 1] - j[h], n[h] = o[h] / p[h]; for (m[0] = n[0], m[l - 1] = n[l - 2], h = 1; h < l - 1; h++)0 === n[h] || 0 === n[h - 1] || n[h - 1] > 0 != n[h] > 0 ? m[h] = 0 : (m[h] = 3 * (p[h - 1] + p[h]) / ((2 * p[h] + p[h - 1]) / n[h - 1] + (p[h] + 2 * p[h - 1]) / n[h]), isFinite(m[h]) || (m[h] = 0)); for (i = (new c.Svg.Path).move(j[0], k[0], !1, e[0]), h = 0; h < l - 1; h++)i.curve(j[h] + p[h] / 3, k[h] + m[h] * p[h] / 3, j[h + 1] - p[h] / 3, k[h + 1] - m[h + 1] * p[h] / 3, j[h + 1], k[h + 1], !1, e[h + 1]); return i } return c.Interpolation.none()([]) } }, c.Interpolation.step = function (a) { var b = { postpone: !0, fillHoles: !1 }; return a = c.extend({}, b, a), function (b, d) { for (var e, f, g, h = new c.Svg.Path, i = 0; i < b.length; i += 2) { var j = b[i], k = b[i + 1], l = d[i / 2]; void 0 !== l.value ? (void 0 === g ? h.move(j, k, !1, l) : (a.postpone ? h.line(j, f, !1, g) : h.line(e, k, !1, l), h.line(j, k, !1, l)), e = j, f = k, g = l) : a.fillHoles || (e = f = g = void 0) } return h } } }(window, document, a), function (a, b, c) { "use strict"; c.EventEmitter = function () { function a(a, b) { d[a] = d[a] || [], d[a].push(b) } function b(a, b) { d[a] && (b ? (d[a].splice(d[a].indexOf(b), 1), 0 === d[a].length && delete d[a]) : delete d[a]) } function c(a, b) { d[a] && d[a].forEach(function (a) { a(b) }), d["*"] && d["*"].forEach(function (c) { c(a, b) }) } var d = []; return { addEventHandler: a, removeEventHandler: b, emit: c } } }(window, document, a), function (a, b, c) { "use strict"; function d(a) { var b = []; if (a.length) for (var c = 0; c < a.length; c++)b.push(a[c]); return b } function e(a, b) { var d = b || this.prototype || c.Class, e = Object.create(d); c.Class.cloneDefinitions(e, a); var f = function () { var a, b = e.constructor || function () { }; return a = this === c ? Object.create(e) : this, b.apply(a, Array.prototype.slice.call(arguments, 0)), a }; return f.prototype = e, f["super"] = d, f.extend = this.extend, f } function f() { var a = d(arguments), b = a[0]; return a.splice(1, a.length - 1).forEach(function (a) { Object.getOwnPropertyNames(a).forEach(function (c) { delete b[c], Object.defineProperty(b, c, Object.getOwnPropertyDescriptor(a, c)) }) }), b } c.Class = { extend: e, cloneDefinitions: f } }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d) { return a && (this.data = a || {}, this.data.labels = this.data.labels || [], this.data.series = this.data.series || [], this.eventEmitter.emit("data", { type: "update", data: this.data })), b && (this.options = c.extend({}, d ? this.options : this.defaultOptions, b), this.initializeTimeoutId || (this.optionsProvider.removeMediaQueryListeners(), this.optionsProvider = c.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter))), this.initializeTimeoutId || this.createChart(this.optionsProvider.getCurrentOptions()), this } function e() { return this.initializeTimeoutId ? a.clearTimeout(this.initializeTimeoutId) : (a.removeEventListener("resize", this.resizeListener), this.optionsProvider.removeMediaQueryListeners()), this } function f(a, b) { return this.eventEmitter.addEventHandler(a, b), this } function g(a, b) { return this.eventEmitter.removeEventHandler(a, b), this } function h() { a.addEventListener("resize", this.resizeListener), this.optionsProvider = c.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter), this.eventEmitter.addEventHandler("optionsChanged", function () { this.update() }.bind(this)), this.options.plugins && this.options.plugins.forEach(function (a) { a instanceof Array ? a[0](this, a[1]) : a(this) }.bind(this)), this.eventEmitter.emit("data", { type: "initial", data: this.data }), this.createChart(this.optionsProvider.getCurrentOptions()), this.initializeTimeoutId = void 0 } function i(a, b, d, e, f) { this.container = c.querySelector(a), this.data = b || {}, this.data.labels = this.data.labels || [], this.data.series = this.data.series || [], this.defaultOptions = d, this.options = e, this.responsiveOptions = f, this.eventEmitter = c.EventEmitter(), this.supportsForeignObject = c.Svg.isSupported("Extensibility"), this.supportsAnimations = c.Svg.isSupported("AnimationEventsAttribute"), this.resizeListener = function () { this.update() }.bind(this), this.container && (this.container.__chartist__ && this.container.__chartist__.detach(), this.container.__chartist__ = this), this.initializeTimeoutId = setTimeout(h.bind(this), 0) } c.Base = c.Class.extend({ constructor: i, optionsProvider: void 0, container: void 0, svg: void 0, eventEmitter: void 0, createChart: function () { throw new Error("Base chart type can't be instantiated!") }, update: d, detach: e, on: f, off: g, version: c.version, supportsForeignObject: !1 }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, d, e, f, g) { a instanceof Element ? this._node = a : (this._node = b.createElementNS(c.namespaces.svg, a), "svg" === a && this.attr({ "xmlns:ct": c.namespaces.ct })), d && this.attr(d), e && this.addClass(e), f && (g && f._node.firstChild ? f._node.insertBefore(this._node, f._node.firstChild) : f._node.appendChild(this._node)) } function e(a, b) { return "string" == typeof a ? b ? this._node.getAttributeNS(b, a) : this._node.getAttribute(a) : (Object.keys(a).forEach(function (b) { if (void 0 !== a[b]) if (b.indexOf(":") !== -1) { var d = b.split(":"); this._node.setAttributeNS(c.namespaces[d[0]], b, a[b]) } else this._node.setAttribute(b, a[b]) }.bind(this)), this) } function f(a, b, d, e) { return new c.Svg(a, b, d, this, e) } function g() { return this._node.parentNode instanceof SVGElement ? new c.Svg(this._node.parentNode) : null } function h() { for (var a = this._node; "svg" !== a.nodeName;)a = a.parentNode; return new c.Svg(a) } function i(a) { var b = this._node.querySelector(a); return b ? new c.Svg(b) : null } function j(a) { var b = this._node.querySelectorAll(a); return b.length ? new c.Svg.List(b) : null } function k() { return this._node } function l(a, d, e, f) { if ("string" == typeof a) { var g = b.createElement("div"); g.innerHTML = a, a = g.firstChild } a.setAttribute("xmlns", c.namespaces.xmlns); var h = this.elem("foreignObject", d, e, f); return h._node.appendChild(a), h } function m(a) { return this._node.appendChild(b.createTextNode(a)), this } function n() { for (; this._node.firstChild;)this._node.removeChild(this._node.firstChild); return this } function o() { return this._node.parentNode.removeChild(this._node), this.parent() } function p(a) { return this._node.parentNode.replaceChild(a._node, this._node), a } function q(a, b) { return b && this._node.firstChild ? this._node.insertBefore(a._node, this._node.firstChild) : this._node.appendChild(a._node), this } function r() { return this._node.getAttribute("class") ? this._node.getAttribute("class").trim().split(/\s+/) : [] } function s(a) { return this._node.setAttribute("class", this.classes(this._node).concat(a.trim().split(/\s+/)).filter(function (a, b, c) { return c.indexOf(a) === b }).join(" ")), this } function t(a) { var b = a.trim().split(/\s+/); return this._node.setAttribute("class", this.classes(this._node).filter(function (a) { return b.indexOf(a) === -1 }).join(" ")), this } function u() { return this._node.setAttribute("class", ""), this } function v() { return this._node.getBoundingClientRect().height } function w() { return this._node.getBoundingClientRect().width } function x(a, b, d) { return void 0 === b && (b = !0), Object.keys(a).forEach(function (e) { function f(a, b) { var f, g, h, i = {}; a.easing && (h = a.easing instanceof Array ? a.easing : c.Svg.Easing[a.easing], delete a.easing), a.begin = c.ensureUnit(a.begin, "ms"), a.dur = c.ensureUnit(a.dur, "ms"), h && (a.calcMode = "spline", a.keySplines = h.join(" "), a.keyTimes = "0;1"), b && (a.fill = "freeze", i[e] = a.from, this.attr(i), g = c.quantity(a.begin || 0).value, a.begin = "indefinite"), f = this.elem("animate", c.extend({ attributeName: e }, a)), b && setTimeout(function () { try { f._node.beginElement() } catch (b) { i[e] = a.to, this.attr(i), f.remove() } }.bind(this), g), d && f._node.addEventListener("beginEvent", function () { d.emit("animationBegin", { element: this, animate: f._node, params: a }) }.bind(this)), f._node.addEventListener("endEvent", function () { d && d.emit("animationEnd", { element: this, animate: f._node, params: a }), b && (i[e] = a.to, this.attr(i), f.remove()) }.bind(this)) } a[e] instanceof Array ? a[e].forEach(function (a) { f.bind(this)(a, !1) }.bind(this)) : f.bind(this)(a[e], b) }.bind(this)), this } function y(a) { var b = this; this.svgElements = []; for (var d = 0; d < a.length; d++)this.svgElements.push(new c.Svg(a[d])); Object.keys(c.Svg.prototype).filter(function (a) { return ["constructor", "parent", "querySelector", "querySelectorAll", "replace", "append", "classes", "height", "width"].indexOf(a) === -1 }).forEach(function (a) { b[a] = function () { var d = Array.prototype.slice.call(arguments, 0); return b.svgElements.forEach(function (b) { c.Svg.prototype[a].apply(b, d) }), b } }) } c.Svg = c.Class.extend({ constructor: d, attr: e, elem: f, parent: g, root: h, querySelector: i, querySelectorAll: j, getNode: k, foreignObject: l, text: m, empty: n, remove: o, replace: p, append: q, classes: r, addClass: s, removeClass: t, removeAllClasses: u, height: v, width: w, animate: x }), c.Svg.isSupported = function (a) { return b.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#" + a, "1.1") }; var z = { easeInSine: [.47, 0, .745, .715], easeOutSine: [.39, .575, .565, 1], easeInOutSine: [.445, .05, .55, .95], easeInQuad: [.55, .085, .68, .53], easeOutQuad: [.25, .46, .45, .94], easeInOutQuad: [.455, .03, .515, .955], easeInCubic: [.55, .055, .675, .19], easeOutCubic: [.215, .61, .355, 1], easeInOutCubic: [.645, .045, .355, 1], easeInQuart: [.895, .03, .685, .22], easeOutQuart: [.165, .84, .44, 1], easeInOutQuart: [.77, 0, .175, 1], easeInQuint: [.755, .05, .855, .06], easeOutQuint: [.23, 1, .32, 1], easeInOutQuint: [.86, 0, .07, 1], easeInExpo: [.95, .05, .795, .035], easeOutExpo: [.19, 1, .22, 1], easeInOutExpo: [1, 0, 0, 1], easeInCirc: [.6, .04, .98, .335], easeOutCirc: [.075, .82, .165, 1], easeInOutCirc: [.785, .135, .15, .86], easeInBack: [.6, -.28, .735, .045], easeOutBack: [.175, .885, .32, 1.275], easeInOutBack: [.68, -.55, .265, 1.55] }; c.Svg.Easing = z, c.Svg.List = c.Class.extend({ constructor: y }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e, f, g) { var h = c.extend({ command: f ? a.toLowerCase() : a.toUpperCase() }, b, g ? { data: g } : {}); d.splice(e, 0, h) } function e(a, b) { a.forEach(function (c, d) { u[c.command.toLowerCase()].forEach(function (e, f) { b(c, e, d, f, a) }) }) } function f(a, b) { this.pathElements = [], this.pos = 0, this.close = a, this.options = c.extend({}, v, b) } function g(a) { return void 0 !== a ? (this.pos = Math.max(0, Math.min(this.pathElements.length, a)), this) : this.pos } function h(a) { return this.pathElements.splice(this.pos, a), this } function i(a, b, c, e) { return d("M", { x: +a, y: +b }, this.pathElements, this.pos++, c, e), this } function j(a, b, c, e) { return d("L", { x: +a, y: +b }, this.pathElements, this.pos++, c, e), this } function k(a, b, c, e, f, g, h, i) { return d("C", { x1: +a, y1: +b, x2: +c, y2: +e, x: +f, y: +g }, this.pathElements, this.pos++, h, i), this } function l(a, b, c, e, f, g, h, i, j) { return d("A", { rx: +a, ry: +b, xAr: +c, lAf: +e, sf: +f, x: +g, y: +h }, this.pathElements, this.pos++, i, j), this } function m(a) { var b = a.replace(/([A-Za-z])([0-9])/g, "$1 $2").replace(/([0-9])([A-Za-z])/g, "$1 $2").split(/[\s,]+/).reduce(function (a, b) { return b.match(/[A-Za-z]/) && a.push([]), a[a.length - 1].push(b), a }, []); "Z" === b[b.length - 1][0].toUpperCase() && b.pop(); var d = b.map(function (a) { var b = a.shift(), d = u[b.toLowerCase()]; return c.extend({ command: b }, d.reduce(function (b, c, d) { return b[c] = +a[d], b }, {})) }), e = [this.pos, 0]; return Array.prototype.push.apply(e, d), Array.prototype.splice.apply(this.pathElements, e), this.pos += d.length, this } function n() { var a = Math.pow(10, this.options.accuracy); return this.pathElements.reduce(function (b, c) { var d = u[c.command.toLowerCase()].map(function (b) { return this.options.accuracy ? Math.round(c[b] * a) / a : c[b] }.bind(this)); return b + c.command + d.join(",") }.bind(this), "") + (this.close ? "Z" : "") } function o(a, b) { return e(this.pathElements, function (c, d) { c[d] *= "x" === d[0] ? a : b }), this } function p(a, b) { return e(this.pathElements, function (c, d) { c[d] += "x" === d[0] ? a : b }), this } function q(a) { return e(this.pathElements, function (b, c, d, e, f) { var g = a(b, c, d, e, f); (g || 0 === g) && (b[c] = g) }), this } function r(a) { var b = new c.Svg.Path(a || this.close); return b.pos = this.pos, b.pathElements = this.pathElements.slice().map(function (a) { return c.extend({}, a) }), b.options = c.extend({}, this.options), b } function s(a) { var b = [new c.Svg.Path]; return this.pathElements.forEach(function (d) { d.command === a.toUpperCase() && 0 !== b[b.length - 1].pathElements.length && b.push(new c.Svg.Path), b[b.length - 1].pathElements.push(d) }), b } function t(a, b, d) { for (var e = new c.Svg.Path(b, d), f = 0; f < a.length; f++)for (var g = a[f], h = 0; h < g.pathElements.length; h++)e.pathElements.push(g.pathElements[h]); return e } var u = { m: ["x", "y"], l: ["x", "y"], c: ["x1", "y1", "x2", "y2", "x", "y"], a: ["rx", "ry", "xAr", "lAf", "sf", "x", "y"] }, v = { accuracy: 3 }; c.Svg.Path = c.Class.extend({ constructor: f, position: g, remove: h, move: i, line: j, curve: k, arc: l, scale: o, translate: p, transform: q, parse: m, stringify: n, clone: r, splitByCommand: s }), c.Svg.Path.elementDescriptions = u, c.Svg.Path.join = t }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, c, d) { this.units = a, this.counterUnits = a === f.x ? f.y : f.x, this.chartRect = b, this.axisLength = b[a.rectEnd] - b[a.rectStart], this.gridOffset = b[a.rectOffset], this.ticks = c, this.options = d } function e(a, b, d, e, f) { var g = e["axis" + this.units.pos.toUpperCase()], h = this.ticks.map(this.projectValue.bind(this)), i = this.ticks.map(g.labelInterpolationFnc); h.forEach(function (j, k) { var l, m = { x: 0, y: 0 }; l = h[k + 1] ? h[k + 1] - j : Math.max(this.axisLength - j, 30), c.isFalseyButZero(i[k]) && "" !== i[k] || ("x" === this.units.pos ? (j = this.chartRect.x1 + j, m.x = e.axisX.labelOffset.x, "start" === e.axisX.position ? m.y = this.chartRect.padding.top + e.axisX.labelOffset.y + (d ? 5 : 20) : m.y = this.chartRect.y1 + e.axisX.labelOffset.y + (d ? 5 : 20)) : (j = this.chartRect.y1 - j, m.y = e.axisY.labelOffset.y - (d ? l : 0), "start" === e.axisY.position ? m.x = d ? this.chartRect.padding.left + e.axisY.labelOffset.x : this.chartRect.x1 - 10 : m.x = this.chartRect.x2 + e.axisY.labelOffset.x + 10), g.showGrid && c.createGrid(j, k, this, this.gridOffset, this.chartRect[this.counterUnits.len](), a, [e.classNames.grid, e.classNames[this.units.dir]], f), g.showLabel && c.createLabel(j, l, k, i, this, g.offset, m, b, [e.classNames.label, e.classNames[this.units.dir], "start" === g.position ? e.classNames[g.position] : e.classNames.end], d, f)) }.bind(this)) } var f = { x: { pos: "x", len: "width", dir: "horizontal", rectStart: "x1", rectEnd: "x2", rectOffset: "y2" }, y: { pos: "y", len: "height", dir: "vertical", rectStart: "y2", rectEnd: "y1", rectOffset: "x1" } }; c.Axis = c.Class.extend({ constructor: d, createGridAndLabels: e, projectValue: function (a, b, c) { throw new Error("Base axis can't be instantiated!") } }), c.Axis.units = f }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { var f = e.highLow || c.getHighLow(b, e, a.pos); this.bounds = c.getBounds(d[a.rectEnd] - d[a.rectStart], f, e.scaleMinSpace || 20, e.onlyInteger), this.range = { min: this.bounds.min, max: this.bounds.max }, c.AutoScaleAxis["super"].constructor.call(this, a, d, this.bounds.values, e) } function e(a) { return this.axisLength * (+c.getMultiValue(a, this.units.pos) - this.bounds.min) / this.bounds.range } c.AutoScaleAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { var f = e.highLow || c.getHighLow(b, e, a.pos); this.divisor = e.divisor || 1, this.ticks = e.ticks || c.times(this.divisor).map(function (a, b) { return f.low + (f.high - f.low) / this.divisor * b }.bind(this)), this.ticks.sort(function (a, b) { return a - b }), this.range = { min: f.low, max: f.high }, c.FixedScaleAxis["super"].constructor.call(this, a, d, this.ticks, e), this.stepLength = this.axisLength / this.divisor } function e(a) { return this.axisLength * (+c.getMultiValue(a, this.units.pos) - this.range.min) / (this.range.max - this.range.min) } c.FixedScaleAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { c.StepAxis["super"].constructor.call(this, a, d, e.ticks, e); var f = Math.max(1, e.ticks.length - (e.stretch ? 1 : 0)); this.stepLength = this.axisLength / f } function e(a, b) { return this.stepLength * b } c.StepAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a) { var b = c.normalizeData(this.data, a.reverseData, !0); this.svg = c.createSvg(this.container, a.width, a.height, a.classNames.chart); var d, e, g = this.svg.elem("g").addClass(a.classNames.gridGroup), h = this.svg.elem("g"), i = this.svg.elem("g").addClass(a.classNames.labelGroup), j = c.createChartRect(this.svg, a, f.padding); d = void 0 === a.axisX.type ? new c.StepAxis(c.Axis.units.x, b.normalized.series, j, c.extend({}, a.axisX, { ticks: b.normalized.labels, stretch: a.fullWidth })) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, j, a.axisX), e = void 0 === a.axisY.type ? new c.AutoScaleAxis(c.Axis.units.y, b.normalized.series, j, c.extend({}, a.axisY, { high: c.isNumeric(a.high) ? a.high : a.axisY.high, low: c.isNumeric(a.low) ? a.low : a.axisY.low })) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, j, a.axisY), d.createGridAndLabels(g, i, this.supportsForeignObject, a, this.eventEmitter), e.createGridAndLabels(g, i, this.supportsForeignObject, a, this.eventEmitter), a.showGridBackground && c.createGridBackground(g, j, a.classNames.gridBackground, this.eventEmitter), b.raw.series.forEach(function (f, g) { var i = h.elem("g"); i.attr({ "ct:series-name": f.name, "ct:meta": c.serialize(f.meta) }), i.addClass([a.classNames.series, f.className || a.classNames.series + "-" + c.alphaNumerate(g)].join(" ")); var k = [], l = []; b.normalized.series[g].forEach(function (a, h) { var i = { x: j.x1 + d.projectValue(a, h, b.normalized.series[g]), y: j.y1 - e.projectValue(a, h, b.normalized.series[g]) }; k.push(i.x, i.y), l.push({ value: a, valueIndex: h, meta: c.getMetaData(f, h) }) }.bind(this)); var m = { lineSmooth: c.getSeriesOption(f, a, "lineSmooth"), showPoint: c.getSeriesOption(f, a, "showPoint"), showLine: c.getSeriesOption(f, a, "showLine"), showArea: c.getSeriesOption(f, a, "showArea"), areaBase: c.getSeriesOption(f, a, "areaBase") }, n = "function" == typeof m.lineSmooth ? m.lineSmooth : m.lineSmooth ? c.Interpolation.monotoneCubic() : c.Interpolation.none(), o = n(k, l); if (m.showPoint && o.pathElements.forEach(function (b) { var h = i.elem("line", { x1: b.x, y1: b.y, x2: b.x + .01, y2: b.y }, a.classNames.point).attr({ "ct:value": [b.data.value.x, b.data.value.y].filter(c.isNumeric).join(","), "ct:meta": c.serialize(b.data.meta) }); this.eventEmitter.emit("draw", { type: "point", value: b.data.value, index: b.data.valueIndex, meta: b.data.meta, series: f, seriesIndex: g, axisX: d, axisY: e, group: i, element: h, x: b.x, y: b.y }) }.bind(this)), m.showLine) { var p = i.elem("path", { d: o.stringify() }, a.classNames.line, !0); this.eventEmitter.emit("draw", { type: "line", values: b.normalized.series[g], path: o.clone(), chartRect: j, index: g, series: f, seriesIndex: g, seriesMeta: f.meta, axisX: d, axisY: e, group: i, element: p }) } if (m.showArea && e.range) { var q = Math.max(Math.min(m.areaBase, e.range.max), e.range.min), r = j.y1 - e.projectValue(q); o.splitByCommand("M").filter(function (a) { return a.pathElements.length > 1 }).map(function (a) { var b = a.pathElements[0], c = a.pathElements[a.pathElements.length - 1]; return a.clone(!0).position(0).remove(1).move(b.x, r).line(b.x, b.y).position(a.pathElements.length + 1).line(c.x, r) }).forEach(function (c) { var h = i.elem("path", { d: c.stringify() }, a.classNames.area, !0); this.eventEmitter.emit("draw", { type: "area", values: b.normalized.series[g], path: c.clone(), series: f, seriesIndex: g, axisX: d, axisY: e, chartRect: j, index: g, group: i, element: h }) }.bind(this)) } }.bind(this)), this.eventEmitter.emit("created", { bounds: e.bounds, chartRect: j, axisX: d, axisY: e, svg: this.svg, options: a }) } function e(a, b, d, e) { c.Line["super"].constructor.call(this, a, b, f, c.extend({}, f, d), e) } var f = { axisX: { offset: 30, position: "end", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, type: void 0 }, axisY: { offset: 40, position: "start", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, type: void 0, scaleMinSpace: 20, onlyInteger: !1 }, width: void 0, height: void 0, showLine: !0, showPoint: !0, showArea: !1, areaBase: 0, lineSmooth: !0, showGridBackground: !1, low: void 0, high: void 0, chartPadding: { top: 15, right: 15, bottom: 5, left: 10 }, fullWidth: !1, reverseData: !1, classNames: { chart: "ct-chart-line", label: "ct-label", labelGroup: "ct-labels", series: "ct-series", line: "ct-line", point: "ct-point", area: "ct-area", grid: "ct-grid", gridGroup: "ct-grids", gridBackground: "ct-grid-background", vertical: "ct-vertical", horizontal: "ct-horizontal", start: "ct-start", end: "ct-end" } }; c.Line = c.Base.extend({ constructor: e, createChart: d }) }(window, document, a), function (a, b, c) { - "use strict"; function d(a) { - var b, d; a.distributeSeries ? (b = c.normalizeData(this.data, a.reverseData, a.horizontalBars ? "x" : "y"), b.normalized.series = b.normalized.series.map(function (a) { return [a] })) : b = c.normalizeData(this.data, a.reverseData, a.horizontalBars ? "x" : "y"), this.svg = c.createSvg(this.container, a.width, a.height, a.classNames.chart + (a.horizontalBars ? " " + a.classNames.horizontalBars : "")); var e = this.svg.elem("g").addClass(a.classNames.gridGroup), g = this.svg.elem("g"), h = this.svg.elem("g").addClass(a.classNames.labelGroup); if (a.stackBars && 0 !== b.normalized.series.length) { - var i = c.serialMap(b.normalized.series, function () { - return Array.prototype.slice.call(arguments).map(function (a) { return a }).reduce(function (a, b) { return { x: a.x + (b && b.x) || 0, y: a.y + (b && b.y) || 0 } }, { x: 0, y: 0 }) - }); d = c.getHighLow([i], a, a.horizontalBars ? "x" : "y") - } else d = c.getHighLow(b.normalized.series, a, a.horizontalBars ? "x" : "y"); d.high = +a.high || (0 === a.high ? 0 : d.high), d.low = +a.low || (0 === a.low ? 0 : d.low); var j, k, l, m, n, o = c.createChartRect(this.svg, a, f.padding); k = a.distributeSeries && a.stackBars ? b.normalized.labels.slice(0, 1) : b.normalized.labels, a.horizontalBars ? (j = m = void 0 === a.axisX.type ? new c.AutoScaleAxis(c.Axis.units.x, b.normalized.series, o, c.extend({}, a.axisX, { highLow: d, referenceValue: 0 })) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, o, c.extend({}, a.axisX, { highLow: d, referenceValue: 0 })), l = n = void 0 === a.axisY.type ? new c.StepAxis(c.Axis.units.y, b.normalized.series, o, { ticks: k }) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, o, a.axisY)) : (l = m = void 0 === a.axisX.type ? new c.StepAxis(c.Axis.units.x, b.normalized.series, o, { ticks: k }) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, o, a.axisX), j = n = void 0 === a.axisY.type ? new c.AutoScaleAxis(c.Axis.units.y, b.normalized.series, o, c.extend({}, a.axisY, { highLow: d, referenceValue: 0 })) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, o, c.extend({}, a.axisY, { highLow: d, referenceValue: 0 }))); var p = a.horizontalBars ? o.x1 + j.projectValue(0) : o.y1 - j.projectValue(0), q = []; l.createGridAndLabels(e, h, this.supportsForeignObject, a, this.eventEmitter), j.createGridAndLabels(e, h, this.supportsForeignObject, a, this.eventEmitter), a.showGridBackground && c.createGridBackground(e, o, a.classNames.gridBackground, this.eventEmitter), b.raw.series.forEach(function (d, e) { var f, h, i = e - (b.raw.series.length - 1) / 2; f = a.distributeSeries && !a.stackBars ? l.axisLength / b.normalized.series.length / 2 : a.distributeSeries && a.stackBars ? l.axisLength / 2 : l.axisLength / b.normalized.series[e].length / 2, h = g.elem("g"), h.attr({ "ct:series-name": d.name, "ct:meta": c.serialize(d.meta) }), h.addClass([a.classNames.series, d.className || a.classNames.series + "-" + c.alphaNumerate(e)].join(" ")), b.normalized.series[e].forEach(function (g, k) { var r, s, t, u; if (u = a.distributeSeries && !a.stackBars ? e : a.distributeSeries && a.stackBars ? 0 : k, r = a.horizontalBars ? { x: o.x1 + j.projectValue(g && g.x ? g.x : 0, k, b.normalized.series[e]), y: o.y1 - l.projectValue(g && g.y ? g.y : 0, u, b.normalized.series[e]) } : { x: o.x1 + l.projectValue(g && g.x ? g.x : 0, u, b.normalized.series[e]), y: o.y1 - j.projectValue(g && g.y ? g.y : 0, k, b.normalized.series[e]) }, l instanceof c.StepAxis && (l.options.stretch || (r[l.units.pos] += f * (a.horizontalBars ? -1 : 1)), r[l.units.pos] += a.stackBars || a.distributeSeries ? 0 : i * a.seriesBarDistance * (a.horizontalBars ? -1 : 1)), t = q[k] || p, q[k] = t - (p - r[l.counterUnits.pos]), void 0 !== g) { var v = {}; v[l.units.pos + "1"] = r[l.units.pos], v[l.units.pos + "2"] = r[l.units.pos], !a.stackBars || "accumulate" !== a.stackMode && a.stackMode ? (v[l.counterUnits.pos + "1"] = p, v[l.counterUnits.pos + "2"] = r[l.counterUnits.pos]) : (v[l.counterUnits.pos + "1"] = t, v[l.counterUnits.pos + "2"] = q[k]), v.x1 = Math.min(Math.max(v.x1, o.x1), o.x2), v.x2 = Math.min(Math.max(v.x2, o.x1), o.x2), v.y1 = Math.min(Math.max(v.y1, o.y2), o.y1), v.y2 = Math.min(Math.max(v.y2, o.y2), o.y1); var w = c.getMetaData(d, k); s = h.elem("line", v, a.classNames.bar).attr({ "ct:value": [g.x, g.y].filter(c.isNumeric).join(","), "ct:meta": c.serialize(w) }), this.eventEmitter.emit("draw", c.extend({ type: "bar", value: g, index: k, meta: w, series: d, seriesIndex: e, axisX: m, axisY: n, chartRect: o, group: h, element: s }, v)) } }.bind(this)) }.bind(this)), this.eventEmitter.emit("created", { bounds: j.bounds, chartRect: o, axisX: m, axisY: n, svg: this.svg, options: a }) - } function e(a, b, d, e) { c.Bar["super"].constructor.call(this, a, b, f, c.extend({}, f, d), e) } var f = { axisX: { offset: 30, position: "end", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, scaleMinSpace: 30, onlyInteger: !1 }, axisY: { offset: 40, position: "start", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, scaleMinSpace: 20, onlyInteger: !1 }, width: void 0, height: void 0, high: void 0, low: void 0, referenceValue: 0, chartPadding: { top: 15, right: 15, bottom: 5, left: 10 }, seriesBarDistance: 15, stackBars: !1, stackMode: "accumulate", horizontalBars: !1, distributeSeries: !1, reverseData: !1, showGridBackground: !1, classNames: { chart: "ct-chart-bar", horizontalBars: "ct-horizontal-bars", label: "ct-label", labelGroup: "ct-labels", series: "ct-series", bar: "ct-bar", grid: "ct-grid", gridGroup: "ct-grids", gridBackground: "ct-grid-background", vertical: "ct-vertical", horizontal: "ct-horizontal", start: "ct-start", end: "ct-end" } }; c.Bar = c.Base.extend({ constructor: e, createChart: d }) - }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, c) { var d = b.x > a.x; return d && "explode" === c || !d && "implode" === c ? "start" : d && "implode" === c || !d && "explode" === c ? "end" : "middle" } function e(a) { var b, e, f, h, i, j = c.normalizeData(this.data), k = [], l = a.startAngle; this.svg = c.createSvg(this.container, a.width, a.height, a.donut ? a.classNames.chartDonut : a.classNames.chartPie), e = c.createChartRect(this.svg, a, g.padding), f = Math.min(e.width() / 2, e.height() / 2), i = a.total || j.normalized.series.reduce(function (a, b) { return a + b }, 0); var m = c.quantity(a.donutWidth); "%" === m.unit && (m.value *= f / 100), f -= a.donut && !a.donutSolid ? m.value / 2 : 0, h = "outside" === a.labelPosition || a.donut && !a.donutSolid ? f : "center" === a.labelPosition ? 0 : a.donutSolid ? f - m.value / 2 : f / 2, h += a.labelOffset; var n = { x: e.x1 + e.width() / 2, y: e.y2 + e.height() / 2 }, o = 1 === j.raw.series.filter(function (a) { return a.hasOwnProperty("value") ? 0 !== a.value : 0 !== a }).length; j.raw.series.forEach(function (a, b) { k[b] = this.svg.elem("g", null, null) }.bind(this)), a.showLabel && (b = this.svg.elem("g", null, null)), j.raw.series.forEach(function (e, g) { if (0 !== j.normalized.series[g] || !a.ignoreEmptyValues) { k[g].attr({ "ct:series-name": e.name }), k[g].addClass([a.classNames.series, e.className || a.classNames.series + "-" + c.alphaNumerate(g)].join(" ")); var p = i > 0 ? l + j.normalized.series[g] / i * 360 : 0, q = Math.max(0, l - (0 === g || o ? 0 : .2)); p - q >= 359.99 && (p = q + 359.99); var r, s, t, u = c.polarToCartesian(n.x, n.y, f, q), v = c.polarToCartesian(n.x, n.y, f, p), w = new c.Svg.Path(!a.donut || a.donutSolid).move(v.x, v.y).arc(f, f, 0, p - l > 180, 0, u.x, u.y); a.donut ? a.donutSolid && (t = f - m.value, r = c.polarToCartesian(n.x, n.y, t, l - (0 === g || o ? 0 : .2)), s = c.polarToCartesian(n.x, n.y, t, p), w.line(r.x, r.y), w.arc(t, t, 0, p - l > 180, 1, s.x, s.y)) : w.line(n.x, n.y); var x = a.classNames.slicePie; a.donut && (x = a.classNames.sliceDonut, a.donutSolid && (x = a.classNames.sliceDonutSolid)); var y = k[g].elem("path", { d: w.stringify() }, x); if (y.attr({ "ct:value": j.normalized.series[g], "ct:meta": c.serialize(e.meta) }), a.donut && !a.donutSolid && (y._node.style.strokeWidth = m.value + "px"), this.eventEmitter.emit("draw", { type: "slice", value: j.normalized.series[g], totalDataSum: i, index: g, meta: e.meta, series: e, group: k[g], element: y, path: w.clone(), center: n, radius: f, startAngle: l, endAngle: p }), a.showLabel) { var z; z = 1 === j.raw.series.length ? { x: n.x, y: n.y } : c.polarToCartesian(n.x, n.y, h, l + (p - l) / 2); var A; A = j.normalized.labels && !c.isFalseyButZero(j.normalized.labels[g]) ? j.normalized.labels[g] : j.normalized.series[g]; var B = a.labelInterpolationFnc(A, g); if (B || 0 === B) { var C = b.elem("text", { dx: z.x, dy: z.y, "text-anchor": d(n, z, a.labelDirection) }, a.classNames.label).text("" + B); this.eventEmitter.emit("draw", { type: "label", index: g, group: b, element: C, text: "" + B, x: z.x, y: z.y }) } } l = p } }.bind(this)), this.eventEmitter.emit("created", { chartRect: e, svg: this.svg, options: a }) } function f(a, b, d, e) { c.Pie["super"].constructor.call(this, a, b, g, c.extend({}, g, d), e) } var g = { width: void 0, height: void 0, chartPadding: 5, classNames: { chartPie: "ct-chart-pie", chartDonut: "ct-chart-donut", series: "ct-series", slicePie: "ct-slice-pie", sliceDonut: "ct-slice-donut", sliceDonutSolid: "ct-slice-donut-solid", label: "ct-label" }, startAngle: 0, total: void 0, donut: !1, donutSolid: !1, donutWidth: 60, showLabel: !0, labelOffset: 0, labelPosition: "inside", labelInterpolationFnc: c.noop, labelDirection: "neutral", reverseData: !1, ignoreEmptyValues: !1 }; c.Pie = c.Base.extend({ constructor: f, createChart: e, determineAnchorPosition: d }) }(window, document, a), a -}); - -var i, l, selectedLine = null; - -/* Navigate to hash without browser history entry */ -var navigateToHash = function () { - if (window.history !== undefined && window.history.replaceState !== undefined) { - window.history.replaceState(undefined, undefined, this.getAttribute("href")); - } -}; - -var hashLinks = document.getElementsByClassName('navigatetohash'); -for (i = 0, l = hashLinks.length; i < l; i++) { - hashLinks[i].addEventListener('click', navigateToHash); -} - -/* Switch test method */ -var switchTestMethod = function () { - var method = this.getAttribute("value"); - console.log("Selected test method: " + method); - - var lines, i, l, coverageData, lineAnalysis, cells; - - lines = document.querySelectorAll('.lineAnalysis tr'); - - for (i = 1, l = lines.length; i < l; i++) { - coverageData = JSON.parse(lines[i].getAttribute('data-coverage').replace(/'/g, '"')); - lineAnalysis = coverageData[method]; - cells = lines[i].querySelectorAll('td'); - if (lineAnalysis === undefined) { - lineAnalysis = coverageData.AllTestMethods; - if (lineAnalysis.LVS !== 'gray') { - cells[0].setAttribute('class', 'red'); - cells[1].innerText = cells[1].textContent = '0'; - cells[4].setAttribute('class', 'lightred'); - } - } else { - cells[0].setAttribute('class', lineAnalysis.LVS); - cells[1].innerText = cells[1].textContent = lineAnalysis.VC; - cells[4].setAttribute('class', 'light' + lineAnalysis.LVS); - } - } -}; - -var testMethods = document.getElementsByClassName('switchtestmethod'); -for (i = 0, l = testMethods.length; i < l; i++) { - testMethods[i].addEventListener('change', switchTestMethod); -} - -/* Highlight test method by line */ -var toggleLine = function () { - if (selectedLine === this) { - selectedLine = null; - } else { - selectedLine = null; - unhighlightTestMethods(); - highlightTestMethods.call(this); - selectedLine = this; - } - -}; -var highlightTestMethods = function () { - if (selectedLine !== null) { - return; - } - - var lineAnalysis; - var coverageData = JSON.parse(this.getAttribute('data-coverage').replace(/'/g, '"')); - var testMethods = document.getElementsByClassName('testmethod'); - - for (i = 0, l = testMethods.length; i < l; i++) { - lineAnalysis = coverageData[testMethods[i].id]; - if (lineAnalysis === undefined) { - testMethods[i].className = testMethods[i].className.replace(/\s*light.+/g, ""); - } else { - testMethods[i].className += ' light' + lineAnalysis.LVS; - } - } -}; -var unhighlightTestMethods = function () { - if (selectedLine !== null) { - return; - } - - var testMethods = document.getElementsByClassName('testmethod'); - for (i = 0, l = testMethods.length; i < l; i++) { - testMethods[i].className = testMethods[i].className.replace(/\s*light.+/g, ""); - } -}; -var coverableLines = document.getElementsByClassName('coverableline'); -for (i = 0, l = coverableLines.length; i < l; i++) { - coverableLines[i].addEventListener('click', toggleLine); - coverableLines[i].addEventListener('mouseenter', highlightTestMethods); - coverableLines[i].addEventListener('mouseleave', unhighlightTestMethods); -} - -/* History charts */ -var renderChart = function (chart) { - // Remove current children (e.g. PNG placeholder) - while (chart.firstChild) { - chart.firstChild.remove(); - } - - var chartData = window[chart.getAttribute('data-data')]; - var options = { - axisY: { - type: undefined, - onlyInteger: true - }, - lineSmooth: false, - low: 0, - high: 100, - scaleMinSpace: 20, - onlyInteger: true, - fullWidth: true - }; - var lineChart = new Chartist.Line(chart, { - labels: [], - series: chartData.series - }, options); - - /* Zoom */ - var zoomButtonDiv = document.createElement("div"); - zoomButtonDiv.className = "toggleZoom"; - var zoomButtonLink = document.createElement("a"); - zoomButtonLink.setAttribute("href", ""); - var zoomButtonText = document.createElement("i"); - zoomButtonText.className = "icon-search-plus"; - - zoomButtonLink.appendChild(zoomButtonText); - zoomButtonDiv.appendChild(zoomButtonLink); - - chart.appendChild(zoomButtonDiv); - - zoomButtonDiv.addEventListener('click', function (event) { - event.preventDefault(); - - if (options.axisY.type === undefined) { - options.axisY.type = Chartist.AutoScaleAxis; - zoomButtonText.className = "icon-search-minus"; - } else { - options.axisY.type = undefined; - zoomButtonText.className = "icon-search-plus"; - } - - lineChart.update(null, options); - }); - - var tooltip = document.createElement("div"); - tooltip.className = "tooltip"; - - chart.appendChild(tooltip); - - /* Tooltips */ - var showToolTip = function () { - var point = this; - var index = [].slice.call(chart.getElementsByClassName('ct-point')).indexOf(point); - - tooltip.innerHTML = chartData.tooltips[index % chartData.tooltips.length]; - tooltip.style.display = 'block'; - }; - - var moveToolTip = function (event) { - var box = chart.getBoundingClientRect(); - var left = event.pageX - box.left - window.pageXOffset; - var top = event.pageY - box.top - window.pageYOffset; - - tooltip.style.left = left - tooltip.offsetWidth / 2 - 5 + 'px'; - tooltip.style.top = top - tooltip.offsetHeight - 40 + 'px'; - }; - - var hideToolTip = function () { - tooltip.style.display = 'none'; - }; - - chart.addEventListener('mousemove', moveToolTip); - - lineChart.on('created', function () { - var chartPoints = chart.getElementsByClassName('ct-point'); - for (i = 0, l = chartPoints.length; i < l; i++) { - chartPoints[i].addEventListener('mousemove', showToolTip); - chartPoints[i].addEventListener('mouseout', hideToolTip); - } - }); -}; - -var charts = document.getElementsByClassName('historychart'); -for (i = 0, l = charts.length; i < l; i++) { - renderChart(charts[i]); -} - -var assemblies = [ - { - "name": "ClassLibrary1", - "classes": [ - { "name": "ClassLibrary1.Calculation", "rp": "ClassLibrary1_Calculation.htm", "cl": 3, "ucl": 6, "cal": 9, "tl": 18, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [33.3], "bch": [], "hc": [{ "et": "2021/3/26 - 下午 04:34:47", "cl": 3, "ucl": 6, "cal": 9, "tl": 18, "lcq": 33.3, "cb": 0, "tb": 0, "bcq": 0 }] }, - ]}, - { - "name": "UnitTestProject1", - "classes": [ - { "name": "UnitTestProject1.UnitTest1", "rp": "UnitTestProject1_UnitTest1.htm", "cl": 9, "ucl": 1, "cal": 10, "tl": 26, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [90], "bch": [], "hc": [{ "et": "2021/3/26 - 下午 04:34:47", "cl": 9, "ucl": 1, "cal": 10, "tl": 26, "lcq": 90, "cb": 0, "tb": 0, "bcq": 0 }] }, - ]}, - { - "name": "UnitTestProject2", - "classes": [ - { "name": "UnitTestProject2.UnitTest1", "rp": "UnitTestProject2_UnitTest1.htm", "cl": 3, "ucl": 0, "cal": 3, "tl": 15, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [100], "bch": [], "hc": [{ "et": "2021/3/26 - 下午 04:34:47", "cl": 3, "ucl": 0, "cal": 3, "tl": 15, "lcq": 100, "cb": 0, "tb": 0, "bcq": 0 }] }, - ]}, -]; - -var historicCoverageExecutionTimes = []; - -var riskHotspotMetrics = [ -]; - -var riskHotspots = [ -]; - -var branchCoverageAvailable = true; - - -var translations = { -'top': 'Top:', -'all': 'All', -'assembly': 'Assembly', -'class': 'Class', -'method': 'Method', -'lineCoverage': 'LineCoverage', -'noGrouping': 'No grouping', -'byAssembly': 'By assembly', -'byNamespace': 'By namespace, Level:', -'all': 'All', -'collapseAll': 'Collapse all', -'expandAll': 'Expand all', -'grouping': 'Grouping:', -'filter': 'Filter:', -'name': 'Name', -'covered': 'Covered', -'uncovered': 'Uncovered', -'coverable': 'Coverable', -'total': 'Total', -'coverage': 'Line coverage', -'branchCoverage': 'Branch coverage', -'history': 'Coverage History', -'compareHistory': 'Compare with:', -'date': 'Date', -'allChanges': 'All changes', -'lineCoverageIncreaseOnly': 'Line coverage: Increase only', -'lineCoverageDecreaseOnly': 'Line coverage: Decrease only', -'branchCoverageIncreaseOnly': 'Branch coverage: Increase only', -'branchCoverageDecreaseOnly': 'Branch coverage: Decrease only' -}; - - -!function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c",this._properties=n&&n.properties||{},this._zoneDelegate=new c(this,this._parent&&this._parent._zoneDelegate,n)}return n.assertZonePatched=function(){if(t.Promise!==T.ZoneAwarePromise)throw new Error("Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten.\nMost likely cause is that a Promise polyfill has been loaded after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. If you must load one, do so before loading zone.js.)")},Object.defineProperty(n,"root",{get:function(){for(var t=n.current;t.parent;)t=t.parent;return t},enumerable:!0,configurable:!0}),Object.defineProperty(n,"current",{get:function(){return j.zone},enumerable:!0,configurable:!0}),Object.defineProperty(n,"currentTask",{get:function(){return M},enumerable:!0,configurable:!0}),n.__load_patch=function(o,i){if(T.hasOwnProperty(o))throw Error("Already loaded patch: "+o);if(!t["__Zone_disable_"+o]){var u="Zone:"+o;r(u),T[o]=i(t,n,O),e(u,u)}},Object.defineProperty(n.prototype,"parent",{get:function(){return this._parent},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"name",{get:function(){return this._name},enumerable:!0,configurable:!0}),n.prototype.get=function(t){var n=this.getZoneWith(t);if(n)return n._properties[t]},n.prototype.getZoneWith=function(t){for(var n=this;n;){if(n._properties.hasOwnProperty(t))return n;n=n._parent}return null},n.prototype.fork=function(t){if(!t)throw new Error("ZoneSpec required!");return this._zoneDelegate.fork(this,t)},n.prototype.wrap=function(t,n){if("function"!=typeof t)throw new Error("Expecting function got: "+t);var r=this._zoneDelegate.intercept(this,t,n),e=this;return function(){return e.runGuarded(r,this,arguments,n)}},n.prototype.run=function(t,n,r,e){void 0===n&&(n=void 0),void 0===r&&(r=null),void 0===e&&(e=null),j={parent:j,zone:this};try{return this._zoneDelegate.invoke(this,t,n,r,e)}finally{j=j.parent}},n.prototype.runGuarded=function(t,n,r,e){void 0===n&&(n=null),void 0===r&&(r=null),void 0===e&&(e=null),j={parent:j,zone:this};try{try{return this._zoneDelegate.invoke(this,t,n,r,e)}catch(o){if(this._zoneDelegate.handleError(this,o))throw o}}finally{j=j.parent}},n.prototype.runTask=function(t,n,r){if(t.zone!=this)throw new Error("A task can only be run in the zone of creation! (Creation: "+(t.zone||d).name+"; Execution: "+this.name+")");if(t.state!==g||t.type!==F){var e=t.state!=_;e&&t._transitionTo(_,m),t.runCount++;var o=M;M=t,j={parent:j,zone:this};try{t.type==S&&t.data&&!t.data.isPeriodic&&(t.cancelFn=null);try{return this._zoneDelegate.invokeTask(this,t,n,r)}catch(i){if(this._zoneDelegate.handleError(this,i))throw i}}finally{t.state!==g&&t.state!==w&&(t.type==F||t.data&&t.data.isPeriodic?e&&t._transitionTo(m,_):(t.runCount=0,this._updateTaskCount(t,-1),e&&t._transitionTo(g,_,g))),j=j.parent,M=o}}},n.prototype.scheduleTask=function(t){if(t.zone&&t.zone!==this)for(var n=this;n;){if(n===t.zone)throw Error("can not reschedule task to "+this.name+" which is descendants of the original zone "+t.zone.name);n=n.parent}t._transitionTo(b,g);var r=[];t._zoneDelegates=r,t._zone=this;try{t=this._zoneDelegate.scheduleTask(this,t)}catch(e){throw t._transitionTo(w,b,g),this._zoneDelegate.handleError(this,e),e}return t._zoneDelegates===r&&this._updateTaskCount(t,1),t.state==b&&t._transitionTo(m,b),t},n.prototype.scheduleMicroTask=function(t,n,r,e){return this.scheduleTask(new a(x,t,n,r,e,null))},n.prototype.scheduleMacroTask=function(t,n,r,e,o){return this.scheduleTask(new a(S,t,n,r,e,o))},n.prototype.scheduleEventTask=function(t,n,r,e,o){return this.scheduleTask(new a(F,t,n,r,e,o))},n.prototype.cancelTask=function(t){if(t.zone!=this)throw new Error("A task can only be cancelled in the zone of creation! (Creation: "+(t.zone||d).name+"; Execution: "+this.name+")");t._transitionTo(k,m,_);try{this._zoneDelegate.cancelTask(this,t)}catch(n){throw t._transitionTo(w,k),this._zoneDelegate.handleError(this,n),n}return this._updateTaskCount(t,-1),t._transitionTo(g,k),t.runCount=0,t},n.prototype._updateTaskCount=function(t,n){var r=t._zoneDelegates;-1==n&&(t._zoneDelegates=null);for(var e=0;e0,macroTask:r.macroTask>0,eventTask:r.eventTask>0,change:t})},t}(),a=function(){function n(r,e,o,i,u,c){this._zone=null,this.runCount=0,this._zoneDelegates=null,this._state="notScheduled",this.type=r,this.source=e,this.data=i,this.scheduleFn=u,this.cancelFn=c,this.callback=o;var a=this;this.invoke=r===F&&i&&i.useG?n.invokeTask:function(){return n.invokeTask.call(t,a,this,arguments)}}return n.invokeTask=function(t,n,r){t||(t=this),K++;try{return t.runCount++,t.zone.runTask(t,n,r)}finally{1==K&&y(),K--}},Object.defineProperty(n.prototype,"zone",{get:function(){return this._zone},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"state",{get:function(){return this._state},enumerable:!0,configurable:!0}),n.prototype.cancelScheduleRequest=function(){this._transitionTo(g,b)},n.prototype._transitionTo=function(t,n,r){if(this._state!==n&&this._state!==r)throw new Error(this.type+" '"+this.source+"': can not transition to '"+t+"', expecting state '"+n+"'"+(r?" or '"+r+"'":"")+", was '"+this._state+"'.");this._state=t,t==g&&(this._zoneDelegates=null)},n.prototype.toString=function(){return this.data&&void 0!==this.data.handleId?this.data.handleId:Object.prototype.toString.call(this)},n.prototype.toJSON=function(){return{type:this.type,state:this.state,source:this.source,zone:this.zone.name,runCount:this.runCount}},n}(),f=X("setTimeout"),s=X("Promise"),l=X("then"),h=[],p=!1;function v(n){0===K&&0===h.length&&(o||t[s]&&(o=t[s].resolve(0)),o?o[l](y):t[f](y,0)),n&&h.push(n)}function y(){if(!p){for(p=!0;h.length;){var t=h;h=[];for(var n=0;n=0;r--)"function"==typeof t[r]&&(t[r]=h(t[r],n+"_"+r));return t}function k(t){return!t||!1!==t.writable&&!("function"==typeof t.get&&void 0===t.set)}var w="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope,x=!("nw"in g)&&void 0!==g.process&&"[object process]"==={}.toString.call(g.process),S=!x&&!w&&!(!y||!d.HTMLElement),F=void 0!==g.process&&"[object process]"==={}.toString.call(g.process)&&!w&&!(!y||!d.HTMLElement),T={},O=function(t){if(t=t||g.event){var n=T[t.type];n||(n=T[t.type]=v("ON_PROPERTY"+t.type));var r=(this||t.target||g)[n],e=r&&r.apply(this,arguments);return null==e||e||t.preventDefault(),e}};function j(r,e,o){var i=t(r,e);if(!i&&o&&t(o,e)&&(i={enumerable:!0,configurable:!0}),i&&i.configurable){delete i.writable,delete i.value;var u=i.get,c=i.set,a=e.substr(2),f=T[a];f||(f=T[a]=v("ON_PROPERTY"+a)),i.set=function(t){var n=this;n||r!==g||(n=g),n&&(n[f]&&n.removeEventListener(a,O),c&&c.apply(n,m),"function"==typeof t?(n[f]=t,n.addEventListener(a,O,!1)):n[f]=null)},i.get=function(){var t=this;if(t||r!==g||(t=g),!t)return null;var n=t[f];if(n)return n;if(u){var o=u&&u.call(this);if(o)return i.set.call(this,o),"function"==typeof t[b]&&t.removeAttribute(e),o}return null},n(r,e,i)}}function M(t,n,r){if(n)for(var e=0;e1?new c(n,r):new c(n),l=t(s,"onmessage");return l&&!1===l.configurable?(a=e(s),f=s,[i,u,"send","close"].forEach(function(t){a[t]=function(){var n=o.call(arguments);if(t===i||t===u){var r=n.length>0?n[0]:void 0;if(r){var e=Zone.__symbol__("ON_PROPERTY"+r);s[e]=a[e]}}return s[t].apply(s,n)}})):a=s,M(a,["close","error","message","open"],f),a};var a=r.WebSocket;for(var f in c)a[f]=c[f]}(0,a)}}var lt=v("unbound");Zone.__load_patch("util",function(t,n,r){r.patchOnProperties=M,r.patchMethod=X,r.bindArguments=_}),Zone.__load_patch("timers",function(t){G(t,"set","clear","Timeout"),G(t,"set","clear","Interval"),G(t,"set","clear","Immediate")}),Zone.__load_patch("requestAnimationFrame",function(t){G(t,"request","cancel","AnimationFrame"),G(t,"mozRequest","mozCancel","AnimationFrame"),G(t,"webkitRequest","webkitCancel","AnimationFrame")}),Zone.__load_patch("blocking",function(t,n){for(var r=["alert","prompt","confirm"],e=0;e=0&&"function"==typeof r[e.cbIdx]?p(e.name,r[e.cbIdx],e,i,null):t.apply(n,r)}})}()}),Zone.__load_patch("XHR",function(t,n){!function(n){var f=XMLHttpRequest.prototype,s=f[c],l=f[a];if(!s){var h=t.XMLHttpRequestEventTarget;if(h){var v=h.prototype;s=v[c],l=v[a]}}var y="readystatechange",d="scheduled";function g(t){XMLHttpRequest[i]=!1;var n=t.data,e=n.target,u=e[o];s||(s=e[c],l=e[a]),u&&l.call(e,y,u);var f=e[o]=function(){e.readyState===e.DONE&&!n.aborted&&XMLHttpRequest[i]&&t.state===d&&t.invoke()};return s.call(e,y,f),e[r]||(e[r]=t),k.apply(e,n.args),XMLHttpRequest[i]=!0,t}function b(){}function m(t){var n=t.data;return n.aborted=!0,w.apply(n.target,n.args)}var _=X(f,"open",function(){return function(t,n){return t[e]=0==n[2],t[u]=n[1],_.apply(t,n)}}),k=X(f,"send",function(){return function(t,n){return t[e]?k.apply(t,n):p("XMLHttpRequest.send",b,{target:t,url:t[u],isPeriodic:!1,delay:null,args:n,aborted:!1},g,m)}}),w=X(f,"abort",function(){return function(t){var n=t[r];if(n&&"string"==typeof n.type){if(null==n.cancelFn||n.data&&n.data.aborted)return;n.zone.cancelTask(n)}}})}();var r=v("xhrTask"),e=v("xhrSync"),o=v("xhrListener"),i=v("xhrScheduled"),u=v("xhrURL")}),Zone.__load_patch("geolocation",function(n){n.navigator&&n.navigator.geolocation&&function(n,r){for(var e=n.constructor.name,o=function(o){var i=r[o],u=n[i];if(u){if(!k(t(n,i)))return"continue";n[i]=function(t){var n=function(){return t.apply(this,_(arguments,e+"."+i))};return Z(n,t),n}(u)}},i=0;if;)a.call(t,u=c[f++])&&n.push(u);return n}},"1TsA":function(t,n){t.exports=function(t,n){return{value:n,done:!!t}}},"1sa7":function(t,n){t.exports=Math.log1p||function(t){return(t=+t)>-1e-8&&t<1e-8?t-t*t/2:Math.log(1+t)}},"25dN":function(t,n,r){var e=r("XKFU");e(e.S,"Object",{is:r("g6HL")})},"2OiF":function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},"2Spj":function(t,n,r){var e=r("XKFU");e(e.P,"Function",{bind:r("8MEG")})},"2atp":function(t,n,r){var e=r("XKFU"),o=Math.atanh;e(e.S+e.F*!(o&&1/o(-0)<0),"Math",{atanh:function(t){return 0==(t=+t)?t:Math.log((1+t)/(1-t))/2}})},"3Lyj":function(t,n,r){var e=r("KroJ");t.exports=function(t,n,r){for(var o in n)e(t,o,n[o],r);return t}},"4A4+":function(t,n,r){r("2Spj"),r("f3/d"),r("IXt9"),t.exports=r("g3g5").Function},"4LiD":function(t,n,r){"use strict";var e=r("dyZX"),o=r("XKFU"),i=r("KroJ"),u=r("3Lyj"),c=r("Z6vF"),a=r("SlkY"),f=r("9gX7"),s=r("0/R4"),l=r("eeVq"),h=r("XMVh"),p=r("fyDq"),v=r("Xbzi");t.exports=function(t,n,r,y,d,g){var b=e[t],m=b,_=d?"set":"add",k=m&&m.prototype,w={},x=function(t){var n=k[t];i(k,t,"delete"==t?function(t){return!(g&&!s(t))&&n.call(this,0===t?0:t)}:"has"==t?function(t){return!(g&&!s(t))&&n.call(this,0===t?0:t)}:"get"==t?function(t){return g&&!s(t)?void 0:n.call(this,0===t?0:t)}:"add"==t?function(t){return n.call(this,0===t?0:t),this}:function(t,r){return n.call(this,0===t?0:t,r),this})};if("function"==typeof m&&(g||k.forEach&&!l(function(){(new m).entries().next()}))){var S=new m,F=S[_](g?{}:-0,1)!=S,T=l(function(){S.has(1)}),O=h(function(t){new m(t)}),j=!g&&l(function(){for(var t=new m,n=5;n--;)t[_](n,n);return!t.has(-0)});O||((m=n(function(n,r){f(n,m,t);var e=v(new b,n,m);return null!=r&&a(r,d,e[_],e),e})).prototype=k,k.constructor=m),(T||j)&&(x("delete"),x("has"),d&&x("get")),(j||F)&&x(_),g&&k.clear&&delete k.clear}else m=y.getConstructor(n,t,d,_),u(m.prototype,r),c.NEED=!0;return p(m,t),w[t]=m,o(o.G+o.W+o.F*(m!=b),w),g||y.setStrong(m,t,d),m}},"4R4u":function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},"5Pf0":function(t,n,r){var e=r("S/j/"),o=r("OP3Y");r("Xtr8")("getPrototypeOf",function(){return function(t){return o(e(t))}})},"69bn":function(t,n,r){var e=r("y3w9"),o=r("2OiF"),i=r("K0xU")("species");t.exports=function(t,n){var r,u=e(t).constructor;return void 0===u||null==(r=e(u)[i])?n:o(r)}},"6AQ9":function(t,n,r){"use strict";var e=r("XKFU"),o=r("8a7r");e(e.S+e.F*r("eeVq")(function(){function t(){}return!(Array.of.call(t)instanceof t)}),"Array",{of:function(){for(var t=0,n=arguments.length,r=new("function"==typeof this?this:Array)(n);n>t;)o(r,t,arguments[t++]);return r.length=n,r}})},"6FMO":function(t,n,r){var e=r("0/R4"),o=r("EWmC"),i=r("K0xU")("species");t.exports=function(t){var n;return o(t)&&("function"!=typeof(n=t.constructor)||n!==Array&&!o(n.prototype)||(n=void 0),e(n)&&null===(n=n[i])&&(n=void 0)),void 0===n?Array:n}},"7h0T":function(t,n,r){var e=r("XKFU");e(e.S,"Number",{isNaN:function(t){return t!=t}})},"8+KV":function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(0),i=r("LyE8")([].forEach,!0);e(e.P+e.F*!i,"Array",{forEach:function(t){return o(this,t,arguments[1])}})},"84bF":function(t,n,r){"use strict";r("OGtf")("small",function(t){return function(){return t(this,"small","","")}})},"8MEG":function(t,n,r){"use strict";var e=r("2OiF"),o=r("0/R4"),i=r("MfQN"),u=[].slice,c={};t.exports=Function.bind||function(t){var n=e(this),r=u.call(arguments,1),a=function(){var e=r.concat(u.call(arguments));return this instanceof a?function(t,n,r){if(!(n in c)){for(var e=[],o=0;o0?arguments[0]:void 0)}},{get:function(t){var n=e.getEntry(o(this,"Map"),t);return n&&n.v},set:function(t,n){return e.def(o(this,"Map"),0===t?0:t,n)}},e,!0)},"9P93":function(t,n,r){var e=r("XKFU"),o=Math.imul;e(e.S+e.F*r("eeVq")(function(){return-5!=o(4294967295,5)||2!=o.length}),"Math",{imul:function(t,n){var r=+t,e=+n,o=65535&r,i=65535&e;return 0|o*i+((65535&r>>>16)*i+o*(65535&e>>>16)<<16>>>0)}})},"9VmF":function(t,n,r){"use strict";var e=r("XKFU"),o=r("ne8i"),i=r("0sh+"),u="".startsWith;e(e.P+e.F*r("UUeW")("startsWith"),"String",{startsWith:function(t){var n=i(this,t,"startsWith"),r=o(Math.min(arguments.length>1?arguments[1]:void 0,n.length)),e=String(t);return u?u.call(n,e,r):n.slice(r,r+e.length)===e}})},"9gX7":function(t,n){t.exports=function(t,n,r,e){if(!(t instanceof n)||void 0!==e&&e in t)throw TypeError(r+": incorrect invocation!");return t}},A2zW:function(t,n,r){"use strict";var e=r("XKFU"),o=r("RYi7"),i=r("vvmO"),u=r("l0Rn"),c=1..toFixed,a=Math.floor,f=[0,0,0,0,0,0],s="Number.toFixed: incorrect invocation!",l=function(t,n){for(var r=-1,e=n;++r<6;)f[r]=(e+=t*f[r])%1e7,e=a(e/1e7)},h=function(t){for(var n=6,r=0;--n>=0;)f[n]=a((r+=f[n])/t),r=r%t*1e7},p=function(){for(var t=6,n="";--t>=0;)if(""!==n||0===t||0!==f[t]){var r=String(f[t]);n=""===n?r:n+u.call("0",7-r.length)+r}return n},v=function(t,n,r){return 0===n?r:n%2==1?v(t,n-1,r*t):v(t*t,n/2,r)};e(e.P+e.F*(!!c&&("0.000"!==8e-5.toFixed(3)||"1"!==.9.toFixed(0)||"1.25"!==1.255.toFixed(2)||"1000000000000000128"!==(0xde0b6b3a7640080).toFixed(0))||!r("eeVq")(function(){c.call({})})),"Number",{toFixed:function(t){var n,r,e,c,a=i(this,s),f=o(t),y="",d="0";if(f<0||f>20)throw RangeError(s);if(a!=a)return"NaN";if(a<=-1e21||a>=1e21)return String(a);if(a<0&&(y="-",a=-a),a>1e-21)if(r=(n=function(t){for(var n=0,r=t;r>=4096;)n+=12,r/=4096;for(;r>=2;)n+=1,r/=2;return n}(a*v(2,69,1))-69)<0?a*v(2,-n,1):a/v(2,n,1),r*=4503599627370496,(n=52-n)>0){for(l(0,r),e=f;e>=7;)l(1e7,0),e-=7;for(l(v(10,e,1),0),e=n-1;e>=23;)h(1<<23),e-=23;h(1<0?y+((c=d.length)<=f?"0."+u.call("0",f-c)+d:d.slice(0,c-f)+"."+d.slice(c-f)):y+d}})},A5AN:function(t,n,r){"use strict";var e=r("AvRE")(!0);t.exports=function(t,n,r){return n+(r?e(t,n).length:1)}},Afnz:function(t,n,r){"use strict";var e=r("LQAc"),o=r("XKFU"),i=r("KroJ"),u=r("Mukb"),c=r("hPIQ"),a=r("QaDb"),f=r("fyDq"),s=r("OP3Y"),l=r("K0xU")("iterator"),h=!([].keys&&"next"in[].keys()),p=function(){return this};t.exports=function(t,n,r,v,y,d,g){a(r,n,v);var b,m,_,k=function(t){if(!h&&t in F)return F[t];switch(t){case"keys":case"values":return function(){return new r(this,t)}}return function(){return new r(this,t)}},w=n+" Iterator",x="values"==y,S=!1,F=t.prototype,T=F[l]||F["@@iterator"]||y&&F[y],O=T||k(y),j=y?x?k("entries"):O:void 0,M="Array"==n&&F.entries||T;if(M&&(_=s(M.call(new t)))!==Object.prototype&&_.next&&(f(_,w,!0),e||"function"==typeof _[l]||u(_,l,p)),x&&T&&"values"!==T.name&&(S=!0,O=function(){return T.call(this)}),e&&!g||!h&&!S&&F[l]||u(F,l,O),c[n]=O,c[w]=p,y)if(b={values:x?O:k("values"),keys:d?O:k("keys"),entries:j},g)for(m in b)m in F||i(F,m,b[m]);else o(o.P+o.F*(h||S),n,b);return b}},AphP:function(t,n,r){"use strict";var e=r("XKFU"),o=r("S/j/"),i=r("apmT");e(e.P+e.F*r("eeVq")(function(){return null!==new Date(NaN).toJSON()||1!==Date.prototype.toJSON.call({toISOString:function(){return 1}})}),"Date",{toJSON:function(t){var n=o(this),r=i(n);return"number"!=typeof r||isFinite(r)?n.toISOString():null}})},AvRE:function(t,n,r){var e=r("RYi7"),o=r("vhPU");t.exports=function(t){return function(n,r){var i,u,c=String(o(n)),a=e(r),f=c.length;return a<0||a>=f?t?"":void 0:(i=c.charCodeAt(a))<55296||i>56319||a+1===f||(u=c.charCodeAt(a+1))<56320||u>57343?t?c.charAt(a):i:t?c.slice(a,a+2):u-56320+(i-55296<<10)+65536}}},BC7C:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{fround:r("kcoS")})},"BJ/l":function(t,n,r){var e=r("XKFU");e(e.S,"Math",{log1p:r("1sa7")})},BP8U:function(t,n,r){var e=r("XKFU"),o=r("PKUr");e(e.S+e.F*(Number.parseInt!=o),"Number",{parseInt:o})},Btvt:function(t,n,r){"use strict";var e=r("I8a+"),o={};o[r("K0xU")("toStringTag")]="z",o+""!="[object z]"&&r("KroJ")(Object.prototype,"toString",function(){return"[object "+e(this)+"]"},!0)},"C/va":function(t,n,r){"use strict";var e=r("y3w9");t.exports=function(){var t=e(this),n="";return t.global&&(n+="g"),t.ignoreCase&&(n+="i"),t.multiline&&(n+="m"),t.unicode&&(n+="u"),t.sticky&&(n+="y"),n}},CkkT:function(t,n,r){var e=r("m0Pp"),o=r("Ymqv"),i=r("S/j/"),u=r("ne8i"),c=r("zRwo");t.exports=function(t,n){var r=1==t,a=2==t,f=3==t,s=4==t,l=6==t,h=5==t||l,p=n||c;return function(n,c,v){for(var y,d,g=i(n),b=o(g),m=e(c,v,3),_=u(b.length),k=0,w=r?p(n,_):a?p(n,0):void 0;_>k;k++)if((h||k in b)&&(d=m(y=b[k],k,g),t))if(r)w[k]=d;else if(d)switch(t){case 3:return!0;case 5:return y;case 6:return k;case 2:w.push(y)}else if(s)return!1;return l?-1:f||s?s:w}}},CuTL:function(t,n,r){r("fyVe"),r("U2t9"),r("2atp"),r("+auO"),r("MtdB"),r("Jcmo"),r("nzyx"),r("BC7C"),r("x8ZO"),r("9P93"),r("eHKK"),r("BJ/l"),r("pp/T"),r("CyHz"),r("bBoP"),r("x8Yj"),r("hLT2"),t.exports=r("g3g5").Math},CyHz:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{sign:r("lvtm")})},DNiP:function(t,n,r){"use strict";var e=r("XKFU"),o=r("eyMr");e(e.P+e.F*!r("LyE8")([].reduce,!0),"Array",{reduce:function(t){return o(this,t,arguments.length,arguments[1],!1)}})},DVgA:function(t,n,r){var e=r("zhAb"),o=r("4R4u");t.exports=Object.keys||function(t){return e(t,o)}},DW2E:function(t,n,r){var e=r("0/R4"),o=r("Z6vF").onFreeze;r("Xtr8")("freeze",function(t){return function(n){return t&&e(n)?t(o(n)):n}})},EK0E:function(t,n,r){"use strict";var e,o=r("CkkT")(0),i=r("KroJ"),u=r("Z6vF"),c=r("czNK"),a=r("ZD67"),f=r("0/R4"),s=r("eeVq"),l=r("s5qY"),h=u.getWeak,p=Object.isExtensible,v=a.ufstore,y={},d=function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},g={get:function(t){if(f(t)){var n=h(t);return!0===n?v(l(this,"WeakMap")).get(t):n?n[this._i]:void 0}},set:function(t,n){return a.def(l(this,"WeakMap"),t,n)}},b=t.exports=r("4LiD")("WeakMap",d,g,a,!0,!0);s(function(){return 7!=(new b).set((Object.freeze||Object)(y),7).get(y)})&&(c((e=a.getConstructor(d,"WeakMap")).prototype,g),u.NEED=!0,o(["delete","has","get","set"],function(t){var n=b.prototype,r=n[t];i(n,t,function(n,o){if(f(n)&&!p(n)){this._f||(this._f=new e);var i=this._f[t](n,o);return"set"==t?this:i}return r.call(this,n,o)})}))},EWmC:function(t,n,r){var e=r("LZWt");t.exports=Array.isArray||function(t){return"Array"==e(t)}},EemH:function(t,n,r){var e=r("UqcF"),o=r("RjD/"),i=r("aCFj"),u=r("apmT"),c=r("aagx"),a=r("xpql"),f=Object.getOwnPropertyDescriptor;n.f=r("nh4g")?f:function(t,n){if(t=i(t),n=u(n,!0),a)try{return f(t,n)}catch(r){}if(c(t,n))return o(!e.f.call(t,n),t[n])}},FEjr:function(t,n,r){"use strict";r("OGtf")("strike",function(t){return function(){return t(this,"strike","","")}})},FJW5:function(t,n,r){var e=r("hswa"),o=r("y3w9"),i=r("DVgA");t.exports=r("nh4g")?Object.defineProperties:function(t,n){o(t);for(var r,u=i(n),c=u.length,a=0;c>a;)e.f(t,r=u[a++],n[r]);return t}},FLlr:function(t,n,r){var e=r("XKFU");e(e.P,"String",{repeat:r("l0Rn")})},FlsD:function(t,n,r){var e=r("0/R4");r("Xtr8")("isExtensible",function(t){return function(n){return!!e(n)&&(!t||t(n))}})},GNAe:function(t,n,r){var e=r("XKFU"),o=r("PKUr");e(e.G+e.F*(parseInt!=o),{parseInt:o})},H6hf:function(t,n,r){var e=r("y3w9");t.exports=function(t,n,r,o){try{return o?n(e(r)[0],r[1]):n(r)}catch(u){var i=t.return;throw void 0!==i&&e(i.call(t)),u}}},"HAE/":function(t,n,r){var e=r("XKFU");e(e.S+e.F*!r("nh4g"),"Object",{defineProperty:r("hswa").f})},HEwt:function(t,n,r){"use strict";var e=r("m0Pp"),o=r("XKFU"),i=r("S/j/"),u=r("H6hf"),c=r("M6Qj"),a=r("ne8i"),f=r("8a7r"),s=r("J+6e");o(o.S+o.F*!r("XMVh")(function(t){Array.from(t)}),"Array",{from:function(t){var n,r,o,l,h=i(t),p="function"==typeof this?this:Array,v=arguments.length,y=v>1?arguments[1]:void 0,d=void 0!==y,g=0,b=s(h);if(d&&(y=e(y,v>2?arguments[2]:void 0,2)),null==b||p==Array&&c(b))for(r=new p(n=a(h.length));n>g;g++)f(r,g,d?y(h[g],g):h[g]);else for(l=b.call(h),r=new p;!(o=l.next()).done;g++)f(r,g,d?u(l,y,[o.value,g],!0):o.value);return r.length=g,r}})},I78e:function(t,n,r){"use strict";var e=r("XKFU"),o=r("+rLv"),i=r("LZWt"),u=r("d/Gc"),c=r("ne8i"),a=[].slice;e(e.P+e.F*r("eeVq")(function(){o&&a.call(o)}),"Array",{slice:function(t,n){var r=c(this.length),e=i(this);if(n=void 0===n?r:n,"Array"==e)return a.call(this,t,n);for(var o=u(t,r),f=u(n,r),s=c(f-o),l=new Array(s),h=0;h1?arguments[1]:void 0)}}),r("nGyu")(i)},"IU+Z":function(t,n,r){"use strict";r("sMXx");var e=r("KroJ"),o=r("Mukb"),i=r("eeVq"),u=r("vhPU"),c=r("K0xU"),a=r("Ugos"),f=c("species"),s=!i(function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")}),l=function(){var t=/(?:)/,n=t.exec;t.exec=function(){return n.apply(this,arguments)};var r="ab".split(t);return 2===r.length&&"a"===r[0]&&"b"===r[1]}();t.exports=function(t,n,r){var h=c(t),p=!i(function(){var n={};return n[h]=function(){return 7},7!=""[t](n)}),v=p?!i(function(){var n=!1,r=/a/;return r.exec=function(){return n=!0,null},"split"===t&&(r.constructor={},r.constructor[f]=function(){return r}),r[h](""),!n}):void 0;if(!p||!v||"replace"===t&&!s||"split"===t&&!l){var y=/./[h],d=r(u,h,""[t],function(t,n,r,e,o){return n.exec===a?p&&!o?{done:!0,value:y.call(n,r,e)}:{done:!0,value:t.call(r,n,e)}:{done:!1}}),g=d[1];e(String.prototype,t,d[0]),o(RegExp.prototype,h,2==n?function(t,n){return g.call(t,this,n)}:function(t){return g.call(t,this)})}}},IXt9:function(t,n,r){"use strict";var e=r("0/R4"),o=r("OP3Y"),i=r("K0xU")("hasInstance"),u=Function.prototype;i in u||r("hswa").f(u,i,{value:function(t){if("function"!=typeof this||!e(t))return!1;if(!e(this.prototype))return t instanceof this;for(;t=o(t);)if(this.prototype===t)return!0;return!1}})},Iw71:function(t,n,r){var e=r("0/R4"),o=r("dyZX").document,i=e(o)&&e(o.createElement);t.exports=function(t){return i?o.createElement(t):{}}},"J+6e":function(t,n,r){var e=r("I8a+"),o=r("K0xU")("iterator"),i=r("hPIQ");t.exports=r("g3g5").getIteratorMethod=function(t){if(null!=t)return t[o]||t["@@iterator"]||i[e(t)]}},JCqj:function(t,n,r){"use strict";r("OGtf")("sup",function(t){return function(){return t(this,"sup","","")}})},Jcmo:function(t,n,r){var e=r("XKFU"),o=Math.exp;e(e.S,"Math",{cosh:function(t){return(o(t=+t)+o(-t))/2}})},JduL:function(t,n,r){r("Xtr8")("getOwnPropertyNames",function(){return r("e7yV").f})},JiEa:function(t,n){n.f=Object.getOwnPropertySymbols},K0xU:function(t,n,r){var e=r("VTer")("wks"),o=r("ylqs"),i=r("dyZX").Symbol,u="function"==typeof i;(t.exports=function(t){return e[t]||(e[t]=u&&i[t]||(u?i:o)("Symbol."+t))}).store=e},KKXr:function(t,n,r){"use strict";var e=r("quPj"),o=r("y3w9"),i=r("69bn"),u=r("A5AN"),c=r("ne8i"),a=r("Xxuz"),f=r("Ugos"),s=Math.min,l=[].push,h=!!function(){try{return new RegExp("x","y")}catch(t){}}();r("IU+Z")("split",2,function(t,n,r,p){var v;return v="c"=="abbc".split(/(b)*/)[1]||4!="test".split(/(?:)/,-1).length||2!="ab".split(/(?:ab)*/).length||4!=".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length?function(t,n){var o=String(this);if(void 0===t&&0===n)return[];if(!e(t))return r.call(o,t,n);for(var i,u,c,a=[],s=0,h=void 0===n?4294967295:n>>>0,p=new RegExp(t.source,(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":"")+"g");(i=f.call(p,o))&&!((u=p.lastIndex)>s&&(a.push(o.slice(s,i.index)),i.length>1&&i.index=h));)p.lastIndex===i.index&&p.lastIndex++;return s===o.length?!c&&p.test("")||a.push(""):a.push(o.slice(s)),a.length>h?a.slice(0,h):a}:"0".split(void 0,0).length?function(t,n){return void 0===t&&0===n?[]:r.call(this,t,n)}:r,[function(r,e){var o=t(this),i=null==r?void 0:r[n];return void 0!==i?i.call(r,o,e):v.call(String(o),r,e)},function(t,n){var e=p(v,t,this,n,v!==r);if(e.done)return e.value;var f=o(t),l=String(this),y=i(f,RegExp),d=f.unicode,g=new y(h?f:"^(?:"+f.source+")",(f.ignoreCase?"i":"")+(f.multiline?"m":"")+(f.unicode?"u":"")+(h?"y":"g")),b=void 0===n?4294967295:n>>>0;if(0===b)return[];if(0===l.length)return null===a(g,l)?[l]:[];for(var m=0,_=0,k=[];_document.F=Object<\/script>"),t.close(),a=t.F;e--;)delete a.prototype[i[e]];return a()};t.exports=Object.create||function(t,n){var r;return null!==t?(c.prototype=e(t),r=new c,c.prototype=null,r[u]=t):r=a(),void 0===n?r:o(r,n)}},L9s1:function(t,n,r){"use strict";var e=r("XKFU"),o=r("0sh+");e(e.P+e.F*r("UUeW")("includes"),"String",{includes:function(t){return!!~o(this,t,"includes").indexOf(t,arguments.length>1?arguments[1]:void 0)}})},LK8F:function(t,n,r){var e=r("XKFU");e(e.S,"Array",{isArray:r("EWmC")})},LQAc:function(t,n){t.exports=!1},LVwc:function(t,n){var r=Math.expm1;t.exports=!r||r(10)>22025.465794806718||r(10)<22025.465794806718||-2e-17!=r(-2e-17)?function(t){return 0==(t=+t)?t:t>-1e-6&&t<1e-6?t+t*t/2:Math.exp(t)-1}:r},LZWt:function(t,n){var r={}.toString;t.exports=function(t){return r.call(t).slice(8,-1)}},Ljet:function(t,n,r){var e=r("XKFU");e(e.S,"Number",{EPSILON:Math.pow(2,-52)})},Lmuc:function(t,n,r){r("xfY5"),r("A2zW"),r("VKir"),r("Ljet"),r("/KAi"),r("fN96"),r("7h0T"),r("sbF8"),r("h/M4"),r("knhD"),r("XfKG"),r("BP8U"),t.exports=r("g3g5").Number},LyE8:function(t,n,r){"use strict";var e=r("eeVq");t.exports=function(t,n){return!!t&&e(function(){n?t.call(null,function(){},1):t.call(null)})}},M6Qj:function(t,n,r){var e=r("hPIQ"),o=r("K0xU")("iterator"),i=Array.prototype;t.exports=function(t){return void 0!==t&&(e.Array===t||i[o]===t)}},MfQN:function(t,n){t.exports=function(t,n,r){var e=void 0===r;switch(n.length){case 0:return e?t():t.call(r);case 1:return e?t(n[0]):t.call(r,n[0]);case 2:return e?t(n[0],n[1]):t.call(r,n[0],n[1]);case 3:return e?t(n[0],n[1],n[2]):t.call(r,n[0],n[1],n[2]);case 4:return e?t(n[0],n[1],n[2],n[3]):t.call(r,n[0],n[1],n[2],n[3])}return t.apply(r,n)}},MtdB:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{clz32:function(t){return(t>>>=0)?31-Math.floor(Math.log(t+.5)*Math.LOG2E):32}})},Mukb:function(t,n,r){var e=r("hswa"),o=r("RjD/");t.exports=r("nh4g")?function(t,n,r){return e.f(t,n,o(1,r))}:function(t,n,r){return t[n]=r,t}},N8g3:function(t,n,r){n.f=r("K0xU")},Nr18:function(t,n,r){"use strict";var e=r("S/j/"),o=r("d/Gc"),i=r("ne8i");t.exports=function(t){for(var n=e(this),r=i(n.length),u=arguments.length,c=o(u>1?arguments[1]:void 0,r),a=u>2?arguments[2]:void 0,f=void 0===a?r:o(a,r);f>c;)n[c++]=t;return n}},Nz9U:function(t,n,r){"use strict";var e=r("XKFU"),o=r("aCFj"),i=[].join;e(e.P+e.F*(r("Ymqv")!=Object||!r("LyE8")(i)),"Array",{join:function(t){return i.call(o(this),void 0===t?",":t)}})},OEbY:function(t,n,r){r("nh4g")&&"g"!=/./g.flags&&r("hswa").f(RegExp.prototype,"flags",{configurable:!0,get:r("C/va")})},OG14:function(t,n,r){"use strict";var e=r("y3w9"),o=r("g6HL"),i=r("Xxuz");r("IU+Z")("search",1,function(t,n,r,u){return[function(r){var e=t(this),o=null==r?void 0:r[n];return void 0!==o?o.call(r,e):new RegExp(r)[n](String(e))},function(t){var n=u(r,t,this);if(n.done)return n.value;var c=e(t),a=String(this),f=c.lastIndex;o(f,0)||(c.lastIndex=0);var s=i(c,a);return o(c.lastIndex,f)||(c.lastIndex=f),null===s?-1:s.index}]})},OGtf:function(t,n,r){var e=r("XKFU"),o=r("eeVq"),i=r("vhPU"),u=/"/g,c=function(t,n,r,e){var o=String(i(t)),c="<"+n;return""!==r&&(c+=" "+r+'="'+String(e).replace(u,""")+'"'),c+">"+o+""};t.exports=function(t,n){var r={};r[t]=n(c),e(e.P+e.F*o(function(){var n=""[t]('"');return n!==n.toLowerCase()||n.split('"').length>3}),"String",r)}},OP3Y:function(t,n,r){var e=r("aagx"),o=r("S/j/"),i=r("YTvA")("IE_PROTO"),u=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=o(t),e(t,i)?t[i]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?u:null}},OnI7:function(t,n,r){var e=r("dyZX"),o=r("g3g5"),i=r("LQAc"),u=r("N8g3"),c=r("hswa").f;t.exports=function(t){var n=o.Symbol||(o.Symbol=i?{}:e.Symbol||{});"_"==t.charAt(0)||t in n||c(n,t,{value:u.f(t)})}},Oyvg:function(t,n,r){var e=r("dyZX"),o=r("Xbzi"),i=r("hswa").f,u=r("kJMx").f,c=r("quPj"),a=r("C/va"),f=e.RegExp,s=f,l=f.prototype,h=/a/g,p=/a/g,v=new f(h)!==h;if(r("nh4g")&&(!v||r("eeVq")(function(){return p[r("K0xU")("match")]=!1,f(h)!=h||f(p)==p||"/a/i"!=f(h,"i")}))){f=function(t,n){var r=this instanceof f,e=c(t),i=void 0===n;return!r&&e&&t.constructor===f&&i?t:o(v?new s(e&&!i?t.source:t,n):s((e=t instanceof f)?t.source:t,e&&i?a.call(t):n),r?this:l,f)};for(var y=function(t){t in f||i(f,t,{configurable:!0,get:function(){return s[t]},set:function(n){s[t]=n}})},d=u(s),g=0;d.length>g;)y(d[g++]);l.constructor=f,f.prototype=l,r("KroJ")(e,"RegExp",f)}r("elZq")("RegExp")},PKUr:function(t,n,r){var e=r("dyZX").parseInt,o=r("qncB").trim,i=r("/e88"),u=/^[-+]?0[xX]/;t.exports=8!==e(i+"08")||22!==e(i+"0x16")?function(t,n){var r=o(String(t),3);return e(r,n>>>0||(u.test(r)?16:10))}:e},QaDb:function(t,n,r){"use strict";var e=r("Kuth"),o=r("RjD/"),i=r("fyDq"),u={};r("Mukb")(u,r("K0xU")("iterator"),function(){return this}),t.exports=function(t,n,r){t.prototype=e(u,{next:o(1,r)}),i(t,n+" Iterator")}},RW0V:function(t,n,r){var e=r("S/j/"),o=r("DVgA");r("Xtr8")("keys",function(){return function(t){return o(e(t))}})},RYi7:function(t,n){var r=Math.ceil,e=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?e:r)(t)}},"RjD/":function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},"S/j/":function(t,n,r){var e=r("vhPU");t.exports=function(t){return Object(e(t))}},SMB2:function(t,n,r){"use strict";r("OGtf")("bold",function(t){return function(){return t(this,"b","","")}})},SPin:function(t,n,r){"use strict";var e=r("XKFU"),o=r("eyMr");e(e.P+e.F*!r("LyE8")([].reduceRight,!0),"Array",{reduceRight:function(t){return o(this,t,arguments.length,arguments[1],!0)}})},SRfc:function(t,n,r){"use strict";var e=r("y3w9"),o=r("ne8i"),i=r("A5AN"),u=r("Xxuz");r("IU+Z")("match",1,function(t,n,r,c){return[function(r){var e=t(this),o=null==r?void 0:r[n];return void 0!==o?o.call(r,e):new RegExp(r)[n](String(e))},function(t){var n=c(r,t,this);if(n.done)return n.value;var a=e(t),f=String(this);if(!a.global)return u(a,f);var s=a.unicode;a.lastIndex=0;for(var l,h=[],p=0;null!==(l=u(a,f));){var v=String(l[0]);h[p]=v,""===v&&(a.lastIndex=i(f,o(a.lastIndex),s)),p++}return 0===p?null:h}]})},SlkY:function(t,n,r){var e=r("m0Pp"),o=r("H6hf"),i=r("M6Qj"),u=r("y3w9"),c=r("ne8i"),a=r("J+6e"),f={},s={};(n=t.exports=function(t,n,r,l,h){var p,v,y,d,g=h?function(){return t}:a(t),b=e(r,l,n?2:1),m=0;if("function"!=typeof g)throw TypeError(t+" is not iterable!");if(i(g)){for(p=c(t.length);p>m;m++)if((d=n?b(u(v=t[m])[0],v[1]):b(t[m]))===f||d===s)return d}else for(y=g.call(t);!(v=y.next()).done;)if((d=o(y,b,v.value,n))===f||d===s)return d}).BREAK=f,n.RETURN=s},T39b:function(t,n,r){"use strict";var e=r("wmvG"),o=r("s5qY");t.exports=r("4LiD")("Set",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{add:function(t){return e.def(o(this,"Set"),t=0===t?0:t,t)}},e)},Tze0:function(t,n,r){"use strict";r("qncB")("trim",function(t){return function(){return t(this,3)}})},U2t9:function(t,n,r){var e=r("XKFU"),o=Math.asinh;e(e.S+e.F*!(o&&1/o(0)>0),"Math",{asinh:function t(n){return isFinite(n=+n)&&0!=n?n<0?-t(-n):Math.log(n+Math.sqrt(n*n+1)):n}})},UUeW:function(t,n,r){var e=r("K0xU")("match");t.exports=function(t){var n=/./;try{"/./"[t](n)}catch(r){try{return n[e]=!1,!"/./"[t](n)}catch(o){}}return!0}},Ugos:function(t,n,r){"use strict";var e,o,i=r("C/va"),u=RegExp.prototype.exec,c=String.prototype.replace,a=u,f=(o=/b*/g,u.call(e=/a/,"a"),u.call(o,"a"),0!==e.lastIndex||0!==o.lastIndex),s=void 0!==/()??/.exec("")[1];(f||s)&&(a=function(t){var n,r,e,o,a=this;return s&&(r=new RegExp("^"+a.source+"$(?!\\s)",i.call(a))),f&&(n=a.lastIndex),e=u.call(a,t),f&&e&&(a.lastIndex=a.global?e.index+e[0].length:n),s&&e&&e.length>1&&c.call(e[0],r,function(){for(o=1;ou;){if(n=+arguments[u++],o(n,1114111)!==n)throw RangeError(n+" is not a valid code point");r.push(n<65536?i(n):i(55296+((n-=65536)>>10),n%1024+56320))}return r.join("")}})},WLL4:function(t,n,r){var e=r("XKFU");e(e.S+e.F*!r("nh4g"),"Object",{defineProperties:r("FJW5")})},XKFU:function(t,n,r){var e=r("dyZX"),o=r("g3g5"),i=r("Mukb"),u=r("KroJ"),c=r("m0Pp"),a=function(t,n,r){var f,s,l,h,p=t&a.F,v=t&a.G,y=t&a.P,d=t&a.B,g=v?e:t&a.S?e[n]||(e[n]={}):(e[n]||{}).prototype,b=v?o:o[n]||(o[n]={}),m=b.prototype||(b.prototype={});for(f in v&&(r=n),r)l=((s=!p&&g&&void 0!==g[f])?g:r)[f],h=d&&s?c(l,e):y&&"function"==typeof l?c(Function.call,l):l,g&&u(g,f,l,t&a.U),b[f]!=l&&i(b,f,h),y&&m[f]!=l&&(m[f]=l)};e.core=o,a.F=1,a.G=2,a.S=4,a.P=8,a.B=16,a.W=32,a.U=64,a.R=128,t.exports=a},XMVh:function(t,n,r){var e=r("K0xU")("iterator"),o=!1;try{var i=[7][e]();i.return=function(){o=!0},Array.from(i,function(){throw 2})}catch(u){}t.exports=function(t,n){if(!n&&!o)return!1;var r=!1;try{var i=[7],c=i[e]();c.next=function(){return{done:r=!0}},i[e]=function(){return c},t(i)}catch(u){}return r}},Xbzi:function(t,n,r){var e=r("0/R4"),o=r("i5dc").set;t.exports=function(t,n,r){var i,u=n.constructor;return u!==r&&"function"==typeof u&&(i=u.prototype)!==r.prototype&&e(i)&&o&&o(t,i),t}},XfKG:function(t,n,r){var e=r("XKFU"),o=r("11IZ");e(e.S+e.F*(Number.parseFloat!=o),"Number",{parseFloat:o})},XfO3:function(t,n,r){"use strict";var e=r("AvRE")(!0);r("Afnz")(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,n=this._t,r=this._i;return r>=n.length?{value:void 0,done:!0}:(t=e(n,r),this._i+=t.length,{value:t,done:!1})})},Xtr8:function(t,n,r){var e=r("XKFU"),o=r("g3g5"),i=r("eeVq");t.exports=function(t,n){var r=(o.Object||{})[t]||Object[t],u={};u[t]=n(r),e(e.S+e.F*i(function(){r(1)}),"Object",u)}},Xxuz:function(t,n,r){"use strict";var e=r("I8a+"),o=RegExp.prototype.exec;t.exports=function(t,n){var r=t.exec;if("function"==typeof r){var i=r.call(t,n);if("object"!=typeof i)throw new TypeError("RegExp exec method returned something other than an Object or null");return i}if("RegExp"!==e(t))throw new TypeError("RegExp#exec called on incompatible receiver");return o.call(t,n)}},YJVH:function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(4);e(e.P+e.F*!r("LyE8")([].every,!0),"Array",{every:function(t){return o(this,t,arguments[1])}})},YTvA:function(t,n,r){var e=r("VTer")("keys"),o=r("ylqs");t.exports=function(t){return e[t]||(e[t]=o(t))}},Ymqv:function(t,n,r){var e=r("LZWt");t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==e(t)?t.split(""):Object(t)}},Z6vF:function(t,n,r){var e=r("ylqs")("meta"),o=r("0/R4"),i=r("aagx"),u=r("hswa").f,c=0,a=Object.isExtensible||function(){return!0},f=!r("eeVq")(function(){return a(Object.preventExtensions({}))}),s=function(t){u(t,e,{value:{i:"O"+ ++c,w:{}}})},l=t.exports={KEY:e,NEED:!1,fastKey:function(t,n){if(!o(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!i(t,e)){if(!a(t))return"F";if(!n)return"E";s(t)}return t[e].i},getWeak:function(t,n){if(!i(t,e)){if(!a(t))return!0;if(!n)return!1;s(t)}return t[e].w},onFreeze:function(t){return f&&l.NEED&&a(t)&&!i(t,e)&&s(t),t}}},ZD67:function(t,n,r){"use strict";var e=r("3Lyj"),o=r("Z6vF").getWeak,i=r("y3w9"),u=r("0/R4"),c=r("9gX7"),a=r("SlkY"),f=r("CkkT"),s=r("aagx"),l=r("s5qY"),h=f(5),p=f(6),v=0,y=function(t){return t._l||(t._l=new d)},d=function(){this.a=[]},g=function(t,n){return h(t.a,function(t){return t[0]===n})};d.prototype={get:function(t){var n=g(this,t);if(n)return n[1]},has:function(t){return!!g(this,t)},set:function(t,n){var r=g(this,t);r?r[1]=n:this.a.push([t,n])},delete:function(t){var n=p(this.a,function(n){return n[0]===t});return~n&&this.a.splice(n,1),!!~n}},t.exports={getConstructor:function(t,n,r,i){var f=t(function(t,e){c(t,f,n,"_i"),t._t=n,t._i=v++,t._l=void 0,null!=e&&a(e,r,t[i],t)});return e(f.prototype,{delete:function(t){if(!u(t))return!1;var r=o(t);return!0===r?y(l(this,n)).delete(t):r&&s(r,this._i)&&delete r[this._i]},has:function(t){if(!u(t))return!1;var r=o(t);return!0===r?y(l(this,n)).has(t):r&&s(r,this._i)}}),f},def:function(t,n,r){var e=o(i(n),!0);return!0===e?y(t).set(n,r):e[t._i]=r,t},ufstore:y}},Zshi:function(t,n,r){var e=r("0/R4");r("Xtr8")("isFrozen",function(t){return function(n){return!e(n)||!!t&&t(n)}})},Zz4T:function(t,n,r){"use strict";r("OGtf")("sub",function(t){return function(){return t(this,"sub","","")}})},a1Th:function(t,n,r){"use strict";r("OEbY");var e=r("y3w9"),o=r("C/va"),i=r("nh4g"),u=/./.toString,c=function(t){r("KroJ")(RegExp.prototype,"toString",t,!0)};r("eeVq")(function(){return"/a/b"!=u.call({source:"a",flags:"b"})})?c(function(){var t=e(this);return"/".concat(t.source,"/","flags"in t?t.flags:!i&&t instanceof RegExp?o.call(t):void 0)}):"toString"!=u.name&&c(function(){return u.call(this)})},aCFj:function(t,n,r){var e=r("Ymqv"),o=r("vhPU");t.exports=function(t){return e(o(t))}},aagx:function(t,n){var r={}.hasOwnProperty;t.exports=function(t,n){return r.call(t,n)}},apmT:function(t,n,r){var e=r("0/R4");t.exports=function(t,n){if(!e(t))return t;var r,o;if(n&&"function"==typeof(r=t.toString)&&!e(o=r.call(t)))return o;if("function"==typeof(r=t.valueOf)&&!e(o=r.call(t)))return o;if(!n&&"function"==typeof(r=t.toString)&&!e(o=r.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},bBoP:function(t,n,r){var e=r("XKFU"),o=r("LVwc"),i=Math.exp;e(e.S+e.F*r("eeVq")(function(){return-2e-17!=!Math.sinh(-2e-17)}),"Math",{sinh:function(t){return Math.abs(t=+t)<1?(o(t)-o(-t))/2:(i(t-1)-i(-t-1))*(Math.E/2)}})},bDcW:function(t,n,r){"use strict";r("OGtf")("fontcolor",function(t){return function(n){return t(this,"font","color",n)}})},bHtr:function(t,n,r){var e=r("XKFU");e(e.P,"Array",{fill:r("Nr18")}),r("nGyu")("fill")},bWfx:function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(1);e(e.P+e.F*!r("LyE8")([].map,!0),"Array",{map:function(t){return o(this,t,arguments[1])}})},czNK:function(t,n,r){"use strict";var e=r("DVgA"),o=r("JiEa"),i=r("UqcF"),u=r("S/j/"),c=r("Ymqv"),a=Object.assign;t.exports=!a||r("eeVq")(function(){var t={},n={},r=Symbol(),e="abcdefghijklmnopqrst";return t[r]=7,e.split("").forEach(function(t){n[t]=t}),7!=a({},t)[r]||Object.keys(a({},n)).join("")!=e})?function(t,n){for(var r=u(t),a=arguments.length,f=1,s=o.f,l=i.f;a>f;)for(var h,p=c(arguments[f++]),v=s?e(p).concat(s(p)):e(p),y=v.length,d=0;y>d;)l.call(p,h=v[d++])&&(r[h]=p[h]);return r}:a},"d/Gc":function(t,n,r){var e=r("RYi7"),o=Math.max,i=Math.min;t.exports=function(t,n){return(t=e(t))<0?o(t+n,0):i(t,n)}},"dE+T":function(t,n,r){var e=r("XKFU");e(e.P,"Array",{copyWithin:r("upKx")}),r("nGyu")("copyWithin")},dQfE:function(t,n,r){r("XfO3"),r("LK8F"),r("HEwt"),r("6AQ9"),r("Nz9U"),r("I78e"),r("Vd3H"),r("8+KV"),r("bWfx"),r("0l/t"),r("dZ+Y"),r("YJVH"),r("DNiP"),r("SPin"),r("V+eJ"),r("mGWK"),r("dE+T"),r("bHtr"),r("dRSK"),r("INYr"),r("0E+W"),r("yt8O"),t.exports=r("g3g5").Array},dRSK:function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(5),i=!0;"find"in[]&&Array(1).find(function(){i=!1}),e(e.P+e.F*i,"Array",{find:function(t){return o(this,t,arguments.length>1?arguments[1]:void 0)}}),r("nGyu")("find")},"dZ+Y":function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(3);e(e.P+e.F*!r("LyE8")([].some,!0),"Array",{some:function(t){return o(this,t,arguments[1])}})},dyZX:function(t,n){var r=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},e7yV:function(t,n,r){var e=r("aCFj"),o=r("kJMx").f,i={}.toString,u="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];t.exports.f=function(t){return u&&"[object Window]"==i.call(t)?function(t){try{return o(t)}catch(n){return u.slice()}}(t):o(e(t))}},eHKK:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{log10:function(t){return Math.log(t)*Math.LOG10E}})},eI33:function(t,n,r){var e=r("XKFU"),o=r("aCFj"),i=r("ne8i");e(e.S,"String",{raw:function(t){for(var n=o(t.raw),r=i(n.length),e=arguments.length,u=[],c=0;r>c;)u.push(String(n[c++])),c=0:l>h;h+=p)h in s&&(c=n(c,s[h],h,f));return c}},"f3/d":function(t,n,r){var e=r("hswa").f,o=Function.prototype,i=/^\s*function ([^ (]*)/;"name"in o||r("nh4g")&&e(o,"name",{configurable:!0,get:function(){try{return(""+this).match(i)[1]}catch(t){return""}}})},fN96:function(t,n,r){var e=r("XKFU");e(e.S,"Number",{isInteger:r("nBIS")})},fyDq:function(t,n,r){var e=r("hswa").f,o=r("aagx"),i=r("K0xU")("toStringTag");t.exports=function(t,n,r){t&&!o(t=r?t:t.prototype,i)&&e(t,i,{configurable:!0,value:n})}},fyVe:function(t,n,r){var e=r("XKFU"),o=r("1sa7"),i=Math.sqrt,u=Math.acosh;e(e.S+e.F*!(u&&710==Math.floor(u(Number.MAX_VALUE))&&u(1/0)==1/0),"Math",{acosh:function(t){return(t=+t)<1?NaN:t>94906265.62425156?Math.log(t)+Math.LN2:o(t-1+i(t-1)*i(t+1))}})},g3g5:function(t,n){var r=t.exports={version:"2.6.1"};"number"==typeof __e&&(__e=r)},g4EE:function(t,n,r){"use strict";var e=r("y3w9"),o=r("apmT");t.exports=function(t){if("string"!==t&&"number"!==t&&"default"!==t)throw TypeError("Incorrect hint");return o(e(this),"number"!=t)}},g6HL:function(t,n){t.exports=Object.is||function(t,n){return t===n?0!==t||1/t==1/n:t!=t&&n!=n}},"h/M4":function(t,n,r){var e=r("XKFU");e(e.S,"Number",{MAX_SAFE_INTEGER:9007199254740991})},h7Nl:function(t,n,r){var e=Date.prototype,o=e.toString,i=e.getTime;new Date(NaN)+""!="Invalid Date"&&r("KroJ")(e,"toString",function(){var t=i.call(this);return t==t?o.call(this):"Invalid Date"})},hEkN:function(t,n,r){"use strict";r("OGtf")("anchor",function(t){return function(n){return t(this,"a","name",n)}})},hHhE:function(t,n,r){var e=r("XKFU");e(e.S,"Object",{create:r("Kuth")})},hLT2:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{trunc:function(t){return(t>0?Math.floor:Math.ceil)(t)}})},"hN/g":function(t,n,r){"use strict";r.r(n),r("dQfE"),r("nx1v"),r("4A4+"),r("qKs0"),r("CuTL"),r("Lmuc"),r("99sg"),r("ifmr"),r("oka+"),r("rfyP"),r("VXxg"),r("V5/Y"),r("vqGA"),r("hYbK"),r("0TWp")},hPIQ:function(t,n){t.exports={}},hYbK:function(t,n,r){r("Btvt"),r("yt8O"),r("EK0E"),t.exports=r("g3g5").WeakMap},hswa:function(t,n,r){var e=r("y3w9"),o=r("xpql"),i=r("apmT"),u=Object.defineProperty;n.f=r("nh4g")?Object.defineProperty:function(t,n,r){if(e(t),n=i(n,!0),e(r),o)try{return u(t,n,r)}catch(c){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(t[n]=r.value),t}},i5dc:function(t,n,r){var e=r("0/R4"),o=r("y3w9"),i=function(t,n){if(o(t),!e(n)&&null!==n)throw TypeError(n+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,n,e){try{(e=r("m0Pp")(Function.call,r("EemH").f(Object.prototype,"__proto__").set,2))(t,[]),n=!(t instanceof Array)}catch(o){n=!0}return function(t,r){return i(t,r),n?t.__proto__=r:e(t,r),t}}({},!1):void 0),check:i}},ifmr:function(t,n,r){r("tyy+"),t.exports=r("g3g5").parseFloat},ioFf:function(t,n,r){"use strict";var e=r("dyZX"),o=r("aagx"),i=r("nh4g"),u=r("XKFU"),c=r("KroJ"),a=r("Z6vF").KEY,f=r("eeVq"),s=r("VTer"),l=r("fyDq"),h=r("ylqs"),p=r("K0xU"),v=r("N8g3"),y=r("OnI7"),d=r("1MBn"),g=r("EWmC"),b=r("y3w9"),m=r("0/R4"),_=r("aCFj"),k=r("apmT"),w=r("RjD/"),x=r("Kuth"),S=r("e7yV"),F=r("EemH"),T=r("hswa"),O=r("DVgA"),j=F.f,M=T.f,K=S.f,U=e.Symbol,X=e.JSON,Z=X&&X.stringify,z=p("_hidden"),E=p("toPrimitive"),D={}.propertyIsEnumerable,A=s("symbol-registry"),L=s("symbols"),I=s("op-symbols"),P=Object.prototype,C="function"==typeof U,q=e.QObject,V=!q||!q.prototype||!q.prototype.findChild,R=i&&f(function(){return 7!=x(M({},"a",{get:function(){return M(this,"a",{value:7}).a}})).a})?function(t,n,r){var e=j(P,n);e&&delete P[n],M(t,n,r),e&&t!==P&&M(P,n,e)}:M,G=function(t){var n=L[t]=x(U.prototype);return n._k=t,n},J=C&&"symbol"==typeof U.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof U},Y=function(t,n,r){return t===P&&Y(I,n,r),b(t),n=k(n,!0),b(r),o(L,n)?(r.enumerable?(o(t,z)&&t[z][n]&&(t[z][n]=!1),r=x(r,{enumerable:w(0,!1)})):(o(t,z)||M(t,z,w(1,{})),t[z][n]=!0),R(t,n,r)):M(t,n,r)},H=function(t,n){b(t);for(var r,e=d(n=_(n)),o=0,i=e.length;i>o;)Y(t,r=e[o++],n[r]);return t},N=function(t){var n=D.call(this,t=k(t,!0));return!(this===P&&o(L,t)&&!o(I,t))&&(!(n||!o(this,t)||!o(L,t)||o(this,z)&&this[z][t])||n)},W=function(t,n){if(t=_(t),n=k(n,!0),t!==P||!o(L,n)||o(I,n)){var r=j(t,n);return!r||!o(L,n)||o(t,z)&&t[z][n]||(r.enumerable=!0),r}},B=function(t){for(var n,r=K(_(t)),e=[],i=0;r.length>i;)o(L,n=r[i++])||n==z||n==a||e.push(n);return e},Q=function(t){for(var n,r=t===P,e=K(r?I:_(t)),i=[],u=0;e.length>u;)!o(L,n=e[u++])||r&&!o(P,n)||i.push(L[n]);return i};C||(c((U=function(){if(this instanceof U)throw TypeError("Symbol is not a constructor!");var t=h(arguments.length>0?arguments[0]:void 0),n=function(r){this===P&&n.call(I,r),o(this,z)&&o(this[z],t)&&(this[z][t]=!1),R(this,t,w(1,r))};return i&&V&&R(P,t,{configurable:!0,set:n}),G(t)}).prototype,"toString",function(){return this._k}),F.f=W,T.f=Y,r("kJMx").f=S.f=B,r("UqcF").f=N,r("JiEa").f=Q,i&&!r("LQAc")&&c(P,"propertyIsEnumerable",N,!0),v.f=function(t){return G(p(t))}),u(u.G+u.W+u.F*!C,{Symbol:U});for(var $="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),tt=0;$.length>tt;)p($[tt++]);for(var nt=O(p.store),rt=0;nt.length>rt;)y(nt[rt++]);u(u.S+u.F*!C,"Symbol",{for:function(t){return o(A,t+="")?A[t]:A[t]=U(t)},keyFor:function(t){if(!J(t))throw TypeError(t+" is not a symbol!");for(var n in A)if(A[n]===t)return n},useSetter:function(){V=!0},useSimple:function(){V=!1}}),u(u.S+u.F*!C,"Object",{create:function(t,n){return void 0===n?x(t):H(x(t),n)},defineProperty:Y,defineProperties:H,getOwnPropertyDescriptor:W,getOwnPropertyNames:B,getOwnPropertySymbols:Q}),X&&u(u.S+u.F*(!C||f(function(){var t=U();return"[null]"!=Z([t])||"{}"!=Z({a:t})||"{}"!=Z(Object(t))})),"JSON",{stringify:function(t){for(var n,r,e=[t],o=1;arguments.length>o;)e.push(arguments[o++]);if(r=n=e[1],(m(n)||void 0!==t)&&!J(t))return g(n)||(n=function(t,n){if("function"==typeof r&&(n=r.call(this,t,n)),!J(n))return n}),e[1]=n,Z.apply(X,e)}}),U.prototype[E]||r("Mukb")(U.prototype,E,U.prototype.valueOf),l(U,"Symbol"),l(Math,"Math",!0),l(e.JSON,"JSON",!0)},jqX0:function(t,n,r){var e=r("XKFU"),o=r("jtBr");e(e.P+e.F*(Date.prototype.toISOString!==o),"Date",{toISOString:o})},jtBr:function(t,n,r){"use strict";var e=r("eeVq"),o=Date.prototype.getTime,i=Date.prototype.toISOString,u=function(t){return t>9?t:"0"+t};t.exports=e(function(){return"0385-07-25T07:06:39.999Z"!=i.call(new Date(-5e13-1))})||!e(function(){i.call(new Date(NaN))})?function(){if(!isFinite(o.call(this)))throw RangeError("Invalid time value");var t=this,n=t.getUTCFullYear(),r=t.getUTCMilliseconds(),e=n<0?"-":n>9999?"+":"";return e+("00000"+Math.abs(n)).slice(e?-6:-4)+"-"+u(t.getUTCMonth()+1)+"-"+u(t.getUTCDate())+"T"+u(t.getUTCHours())+":"+u(t.getUTCMinutes())+":"+u(t.getUTCSeconds())+"."+(r>99?r:"0"+u(r))+"Z"}:i},kJMx:function(t,n,r){var e=r("zhAb"),o=r("4R4u").concat("length","prototype");n.f=Object.getOwnPropertyNames||function(t){return e(t,o)}},kcoS:function(t,n,r){var e=r("lvtm"),o=Math.pow,i=o(2,-52),u=o(2,-23),c=o(2,127)*(2-u),a=o(2,-126);t.exports=Math.fround||function(t){var n,r,o=Math.abs(t),f=e(t);return oc||r!=r?f*(1/0):f*r}},knhD:function(t,n,r){var e=r("XKFU");e(e.S,"Number",{MIN_SAFE_INTEGER:-9007199254740991})},l0Rn:function(t,n,r){"use strict";var e=r("RYi7"),o=r("vhPU");t.exports=function(t){var n=String(o(this)),r="",i=e(t);if(i<0||i==1/0)throw RangeError("Count can't be negative");for(;i>0;(i>>>=1)&&(n+=n))1&i&&(r+=n);return r}},lvtm:function(t,n){t.exports=Math.sign||function(t){return 0==(t=+t)||t!=t?t:t<0?-1:1}},m0Pp:function(t,n,r){var e=r("2OiF");t.exports=function(t,n,r){if(e(t),void 0===n)return t;switch(r){case 1:return function(r){return t.call(n,r)};case 2:return function(r,e){return t.call(n,r,e)};case 3:return function(r,e,o){return t.call(n,r,e,o)}}return function(){return t.apply(n,arguments)}}},mGWK:function(t,n,r){"use strict";var e=r("XKFU"),o=r("aCFj"),i=r("RYi7"),u=r("ne8i"),c=[].lastIndexOf,a=!!c&&1/[1].lastIndexOf(1,-0)<0;e(e.P+e.F*(a||!r("LyE8")(c)),"Array",{lastIndexOf:function(t){if(a)return c.apply(this,arguments)||0;var n=o(this),r=u(n.length),e=r-1;for(arguments.length>1&&(e=Math.min(e,i(arguments[1]))),e<0&&(e=r+e);e>=0;e--)if(e in n&&n[e]===t)return e||0;return-1}})},mYba:function(t,n,r){var e=r("aCFj"),o=r("EemH").f;r("Xtr8")("getOwnPropertyDescriptor",function(){return function(t,n){return o(e(t),n)}})},mura:function(t,n,r){var e=r("0/R4"),o=r("Z6vF").onFreeze;r("Xtr8")("preventExtensions",function(t){return function(n){return t&&e(n)?t(o(n)):n}})},nBIS:function(t,n,r){var e=r("0/R4"),o=Math.floor;t.exports=function(t){return!e(t)&&isFinite(t)&&o(t)===t}},nGyu:function(t,n,r){var e=r("K0xU")("unscopables"),o=Array.prototype;null==o[e]&&r("Mukb")(o,e,{}),t.exports=function(t){o[e][t]=!0}},nIY7:function(t,n,r){"use strict";r("OGtf")("big",function(t){return function(){return t(this,"big","","")}})},ne8i:function(t,n,r){var e=r("RYi7"),o=Math.min;t.exports=function(t){return t>0?o(e(t),9007199254740991):0}},nh4g:function(t,n,r){t.exports=!r("eeVq")(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},nsiH:function(t,n,r){"use strict";r("OGtf")("fontsize",function(t){return function(n){return t(this,"font","size",n)}})},nx1v:function(t,n,r){r("eM6i"),r("AphP"),r("jqX0"),r("h7Nl"),r("yM4b"),t.exports=Date},nzyx:function(t,n,r){var e=r("XKFU"),o=r("LVwc");e(e.S+e.F*(o!=Math.expm1),"Math",{expm1:o})},oDIu:function(t,n,r){"use strict";var e=r("XKFU"),o=r("AvRE")(!1);e(e.P,"String",{codePointAt:function(t){return o(this,t)}})},"oka+":function(t,n,r){r("GNAe"),t.exports=r("g3g5").parseInt},pIFo:function(t,n,r){"use strict";var e=r("y3w9"),o=r("S/j/"),i=r("ne8i"),u=r("RYi7"),c=r("A5AN"),a=r("Xxuz"),f=Math.max,s=Math.min,l=Math.floor,h=/\$([$&`']|\d\d?|<[^>]*>)/g,p=/\$([$&`']|\d\d?)/g;r("IU+Z")("replace",2,function(t,n,r,v){return[function(e,o){var i=t(this),u=null==e?void 0:e[n];return void 0!==u?u.call(e,i,o):r.call(String(i),e,o)},function(t,n){var o=v(r,t,this,n);if(o.done)return o.value;var l=e(t),h=String(this),p="function"==typeof n;p||(n=String(n));var d=l.global;if(d){var g=l.unicode;l.lastIndex=0}for(var b=[];;){var m=a(l,h);if(null===m)break;if(b.push(m),!d)break;""===String(m[0])&&(l.lastIndex=c(h,i(l.lastIndex),g))}for(var _,k="",w=0,x=0;x=w&&(k+=h.slice(w,F)+K,w=F+S.length)}return k+h.slice(w)}];function y(t,n,e,i,u,c){var a=e+t.length,f=i.length,s=p;return void 0!==u&&(u=o(u),s=h),r.call(c,s,function(r,o){var c;switch(o.charAt(0)){case"$":return"$";case"&":return t;case"`":return n.slice(0,e);case"'":return n.slice(a);case"<":c=u[o.slice(1,-1)];break;default:var s=+o;if(0===s)return o;if(s>f){var h=l(s/10);return 0===h?o:h<=f?void 0===i[h-1]?o.charAt(1):i[h-1]+o.charAt(1):o}c=i[s-1]}return void 0===c?"":c})}})},"pp/T":function(t,n,r){var e=r("XKFU");e(e.S,"Math",{log2:function(t){return Math.log(t)/Math.LN2}})},qKs0:function(t,n,r){r("Btvt"),r("XfO3"),r("rGqo"),r("9AAn"),t.exports=r("g3g5").Map},qncB:function(t,n,r){var e=r("XKFU"),o=r("vhPU"),i=r("eeVq"),u=r("/e88"),c="["+u+"]",a=RegExp("^"+c+c+"*"),f=RegExp(c+c+"*$"),s=function(t,n,r){var o={},c=i(function(){return!!u[t]()||"\u200b\x85"!="\u200b\x85"[t]()}),a=o[t]=c?n(l):u[t];r&&(o[r]=a),e(e.P+e.F*c,"String",o)},l=s.trim=function(t,n){return t=String(o(t)),1&n&&(t=t.replace(a,"")),2&n&&(t=t.replace(f,"")),t};t.exports=s},quPj:function(t,n,r){var e=r("0/R4"),o=r("LZWt"),i=r("K0xU")("match");t.exports=function(t){var n;return e(t)&&(void 0!==(n=t[i])?!!n:"RegExp"==o(t))}},rGqo:function(t,n,r){for(var e=r("yt8O"),o=r("DVgA"),i=r("KroJ"),u=r("dyZX"),c=r("Mukb"),a=r("hPIQ"),f=r("K0xU"),s=f("iterator"),l=f("toStringTag"),h=a.Array,p={CSSRuleList:!0,CSSStyleDeclaration:!1,CSSValueList:!1,ClientRectList:!1,DOMRectList:!1,DOMStringList:!1,DOMTokenList:!0,DataTransferItemList:!1,FileList:!1,HTMLAllCollection:!1,HTMLCollection:!1,HTMLFormElement:!1,HTMLSelectElement:!1,MediaList:!0,MimeTypeArray:!1,NamedNodeMap:!1,NodeList:!0,PaintRequestList:!1,Plugin:!1,PluginArray:!1,SVGLengthList:!1,SVGNumberList:!1,SVGPathSegList:!1,SVGPointList:!1,SVGStringList:!1,SVGTransformList:!1,SourceBufferList:!1,StyleSheetList:!0,TextTrackCueList:!1,TextTrackList:!1,TouchList:!1},v=o(p),y=0;y1?arguments[1]:void 0,e=o(n.length),c=void 0===r?e:Math.min(o(r),e),a=String(t);return u?u.call(n,a,c):n.slice(c-a.length,c)===a}})},s5qY:function(t,n,r){var e=r("0/R4");t.exports=function(t,n){if(!e(t)||t._t!==n)throw TypeError("Incompatible receiver, "+n+" required!");return t}},sMXx:function(t,n,r){"use strict";var e=r("Ugos");r("XKFU")({target:"RegExp",proto:!0,forced:e!==/./.exec},{exec:e})},sbF8:function(t,n,r){var e=r("XKFU"),o=r("nBIS"),i=Math.abs;e(e.S,"Number",{isSafeInteger:function(t){return o(t)&&i(t)<=9007199254740991}})},tUrg:function(t,n,r){"use strict";r("OGtf")("link",function(t){return function(n){return t(this,"a","href",n)}})},"tyy+":function(t,n,r){var e=r("XKFU"),o=r("11IZ");e(e.G+e.F*(parseFloat!=o),{parseFloat:o})},upKx:function(t,n,r){"use strict";var e=r("S/j/"),o=r("d/Gc"),i=r("ne8i");t.exports=[].copyWithin||function(t,n){var r=e(this),u=i(r.length),c=o(t,u),a=o(n,u),f=arguments.length>2?arguments[2]:void 0,s=Math.min((void 0===f?u:o(f,u))-a,u-c),l=1;for(a0;)a in r?r[c]=r[a]:delete r[c],c+=l,a+=l;return r}},vhPU:function(t,n){t.exports=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t}},vqGA:function(t,n,r){r("ioFf"),r("Btvt"),t.exports=r("g3g5").Symbol},vvmO:function(t,n,r){var e=r("LZWt");t.exports=function(t,n){if("number"!=typeof t&&"Number"!=e(t))throw TypeError(n);return+t}},w2a5:function(t,n,r){var e=r("aCFj"),o=r("ne8i"),i=r("d/Gc");t.exports=function(t){return function(n,r,u){var c,a=e(n),f=o(a.length),s=i(u,f);if(t&&r!=r){for(;f>s;)if((c=a[s++])!=c)return!0}else for(;f>s;s++)if((t||s in a)&&a[s]===r)return t||s||0;return!t&&-1}}},wmvG:function(t,n,r){"use strict";var e=r("hswa").f,o=r("Kuth"),i=r("3Lyj"),u=r("m0Pp"),c=r("9gX7"),a=r("SlkY"),f=r("Afnz"),s=r("1TsA"),l=r("elZq"),h=r("nh4g"),p=r("Z6vF").fastKey,v=r("s5qY"),y=h?"_s":"size",d=function(t,n){var r,e=p(n);if("F"!==e)return t._i[e];for(r=t._f;r;r=r.n)if(r.k==n)return r};t.exports={getConstructor:function(t,n,r,f){var s=t(function(t,e){c(t,s,n,"_i"),t._t=n,t._i=o(null),t._f=void 0,t._l=void 0,t[y]=0,null!=e&&a(e,r,t[f],t)});return i(s.prototype,{clear:function(){for(var t=v(this,n),r=t._i,e=t._f;e;e=e.n)e.r=!0,e.p&&(e.p=e.p.n=void 0),delete r[e.i];t._f=t._l=void 0,t[y]=0},delete:function(t){var r=v(this,n),e=d(r,t);if(e){var o=e.n,i=e.p;delete r._i[e.i],e.r=!0,i&&(i.n=o),o&&(o.p=i),r._f==e&&(r._f=o),r._l==e&&(r._l=i),r[y]--}return!!e},forEach:function(t){v(this,n);for(var r,e=u(t,arguments.length>1?arguments[1]:void 0,3);r=r?r.n:this._f;)for(e(r.v,r.k,this);r&&r.r;)r=r.p},has:function(t){return!!d(v(this,n),t)}}),h&&e(s.prototype,"size",{get:function(){return v(this,n)[y]}}),s},def:function(t,n,r){var e,o,i=d(t,n);return i?i.v=r:(t._l=i={i:o=p(n,!0),k:n,v:r,p:e=t._l,n:void 0,r:!1},t._f||(t._f=i),e&&(e.n=i),t[y]++,"F"!==o&&(t._i[o]=i)),t},getEntry:d,setStrong:function(t,n,r){f(t,n,function(t,r){this._t=v(t,n),this._k=r,this._l=void 0},function(){for(var t=this._k,n=this._l;n&&n.r;)n=n.p;return this._t&&(this._l=n=n?n.n:this._t._f)?s(0,"keys"==t?n.k:"values"==t?n.v:[n.k,n.v]):(this._t=void 0,s(1))},r?"entries":"values",!r,!0),l(n)}}},x8Yj:function(t,n,r){var e=r("XKFU"),o=r("LVwc"),i=Math.exp;e(e.S,"Math",{tanh:function(t){var n=o(t=+t),r=o(-t);return n==1/0?1:r==1/0?-1:(n-r)/(i(t)+i(-t))}})},x8ZO:function(t,n,r){var e=r("XKFU"),o=Math.abs;e(e.S,"Math",{hypot:function(t,n){for(var r,e,i=0,u=0,c=arguments.length,a=0;u0?(e=r/a)*e:r;return a===1/0?1/0:a*Math.sqrt(i)}})},xfY5:function(t,n,r){"use strict";var e=r("dyZX"),o=r("aagx"),i=r("LZWt"),u=r("Xbzi"),c=r("apmT"),a=r("eeVq"),f=r("kJMx").f,s=r("EemH").f,l=r("hswa").f,h=r("qncB").trim,p=e.Number,v=p,y=p.prototype,d="Number"==i(r("Kuth")(y)),g="trim"in String.prototype,b=function(t){var n=c(t,!1);if("string"==typeof n&&n.length>2){var r,e,o,i=(n=g?n.trim():h(n,3)).charCodeAt(0);if(43===i||45===i){if(88===(r=n.charCodeAt(2))||120===r)return NaN}else if(48===i){switch(n.charCodeAt(1)){case 66:case 98:e=2,o=49;break;case 79:case 111:e=8,o=55;break;default:return+n}for(var u,a=n.slice(2),f=0,s=a.length;fo)return NaN;return parseInt(a,e)}}return+n};if(!p(" 0o1")||!p("0b1")||p("+0x1")){p=function(t){var n=arguments.length<1?0:t,r=this;return r instanceof p&&(d?a(function(){y.valueOf.call(r)}):"Number"!=i(r))?u(new v(b(n)),r,p):b(n)};for(var m,_=r("nh4g")?f(v):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger".split(","),k=0;_.length>k;k++)o(v,m=_[k])&&!o(p,m)&&l(p,m,s(v,m));p.prototype=y,y.constructor=p,r("KroJ")(e,"Number",p)}},xpql:function(t,n,r){t.exports=!r("nh4g")&&!r("eeVq")(function(){return 7!=Object.defineProperty(r("Iw71")("div"),"a",{get:function(){return 7}}).a})},y3w9:function(t,n,r){var e=r("0/R4");t.exports=function(t){if(!e(t))throw TypeError(t+" is not an object!");return t}},yM4b:function(t,n,r){var e=r("K0xU")("toPrimitive"),o=Date.prototype;e in o||r("Mukb")(o,e,r("g4EE"))},ylqs:function(t,n){var r=0,e=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++r+e).toString(36))}},yt8O:function(t,n,r){"use strict";var e=r("nGyu"),o=r("1TsA"),i=r("hPIQ"),u=r("aCFj");t.exports=r("Afnz")(Array,"Array",function(t,n){this._t=u(t),this._i=0,this._k=n},function(){var t=this._t,n=this._k,r=this._i++;return!t||r>=t.length?(this._t=void 0,o(1)):o(0,"keys"==n?r:"values"==n?t[r]:[r,t[r]])},"values"),i.Arguments=i.Array,e("keys"),e("values"),e("entries")},z2o2:function(t,n,r){var e=r("0/R4"),o=r("Z6vF").onFreeze;r("Xtr8")("seal",function(t){return function(n){return t&&e(n)?t(o(n)):n}})},zRwo:function(t,n,r){var e=r("6FMO");t.exports=function(t,n){return new(e(t))(n)}},zhAb:function(t,n,r){var e=r("aagx"),o=r("aCFj"),i=r("w2a5")(!1),u=r("YTvA")("IE_PROTO");t.exports=function(t,n){var r,c=o(t),a=0,f=[];for(r in c)r!=u&&e(c,r)&&f.push(r);for(;n.length>a;)e(c,r=n[a++])&&(~i(f,r)||f.push(r));return f}}},[[1,0]]]); - -(window.webpackJsonp=window.webpackJsonp||[]).push([[1],{0:function(n,t,e){n.exports=e("zUnb")},crnd:function(n,t){function e(n){return Promise.resolve().then(function(){var t=new Error("Cannot find module '"+n+"'");throw t.code="MODULE_NOT_FOUND",t})}e.keys=function(){return[]},e.resolve=e,n.exports=e,e.id="crnd"},zUnb:function(n,t,e){"use strict";e.r(t);var r=function(n,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,t){n.__proto__=t}||function(n,t){for(var e in t)t.hasOwnProperty(e)&&(n[e]=t[e])})(n,t)};function o(n,t){function e(){this.constructor=n}r(n,t),n.prototype=null===t?Object.create(t):(e.prototype=t.prototype,new e)}var i=function(){return(i=Object.assign||function(n){for(var t,e=1,r=arguments.length;e=0;u--)(o=n[u])&&(l=(i<3?o(l):i>3?o(t,e,l):o(t,e))||l);return i>3&&l&&Object.defineProperty(t,e,l),l}function u(n,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(n,t)}function s(n){var t="function"==typeof Symbol&&n[Symbol.iterator],e=0;return t?t.call(n):{next:function(){return n&&e>=n.length&&(n=void 0),{value:n&&n[e++],done:!n}}}}function a(n,t){var e="function"==typeof Symbol&&n[Symbol.iterator];if(!e)return n;var r,o,i=e.call(n),l=[];try{for(;(void 0===t||t-- >0)&&!(r=i.next()).done;)l.push(r.value)}catch(u){o={error:u}}finally{try{r&&!r.done&&(e=i.return)&&e.call(i)}finally{if(o)throw o.error}}return l}function c(){for(var n=[],t=0;t0?this._next(t.shift()):0===this.active&&this.hasCompleted&&this.destination.complete()},t}(W);function rn(n){return n}function on(){return function(n){return n.lift(new ln(n))}}var ln=function(){function n(n){this.connectable=n}return n.prototype.call=function(n,t){var e=this.connectable;e._refCount++;var r=new un(n,e),o=t.subscribe(r);return r.closed||(r.connection=e.connect()),o},n}(),un=function(n){function t(t,e){var r=n.call(this,t)||this;return r.connectable=e,r}return o(t,n),t.prototype._unsubscribe=function(){var n=this.connectable;if(n){this.connectable=null;var t=n._refCount;if(t<=0)this.connection=null;else if(n._refCount=t-1,t>1)this.connection=null;else{var e=this.connection,r=n._connection;this.connection=null,!r||e&&r!==e||r.unsubscribe()}}else this.connection=null},t}(S),sn=function(n){function t(t,e){var r=n.call(this)||this;return r.source=t,r.subjectFactory=e,r._refCount=0,r._isComplete=!1,r}return o(t,n),t.prototype._subscribe=function(n){return this.getSubject().subscribe(n)},t.prototype.getSubject=function(){var n=this._subject;return n&&!n.isStopped||(this._subject=this.subjectFactory()),this._subject},t.prototype.connect=function(){var n=this._connection;return n||(this._isComplete=!1,(n=this._connection=new _).add(this.source.subscribe(new cn(this.getSubject(),this))),n.closed?(this._connection=null,n=_.EMPTY):this._connection=n),n},t.prototype.refCount=function(){return on()(this)},t}(I).prototype,an={operator:{value:null},_refCount:{value:0,writable:!0},_subject:{value:null,writable:!0},_connection:{value:null,writable:!0},_subscribe:{value:sn._subscribe},_isComplete:{value:sn._isComplete,writable:!0},getSubject:{value:sn.getSubject},connect:{value:sn.connect},refCount:{value:sn.refCount}},cn=function(n){function t(t,e){var r=n.call(this,t)||this;return r.connectable=e,r}return o(t,n),t.prototype._error=function(t){this._unsubscribe(),n.prototype._error.call(this,t)},t.prototype._complete=function(){this.connectable._isComplete=!0,this._unsubscribe(),n.prototype._complete.call(this)},t.prototype._unsubscribe=function(){var n=this.connectable;if(n){this.connectable=null;var t=n._connection;n._refCount=0,n._subject=null,n._connection=null,t&&t.unsubscribe()}},t}(V);function fn(){return new H}function hn(n){for(var t in n)if(n[t]===hn)return t;throw Error("Could not find renamed property on target object.")}var pn=hn({ngComponentDef:hn}),dn=hn({ngInjectableDef:hn}),gn=hn({ngInjectorDef:hn}),vn=hn({ngModuleDef:hn}),yn=hn({__NG_ELEMENT_ID__:hn});function mn(n){return{providedIn:n.providedIn||null,factory:n.factory,value:void 0}}function bn(n){return n.hasOwnProperty(dn)?n[dn]:null}function _n(n){return n.hasOwnProperty(gn)?n[gn]:null}var wn=function(){function n(n,t){this._desc=n,this.ngMetadataName="InjectionToken",this.ngInjectableDef=void 0!==t?mn({providedIn:t.providedIn||"root",factory:t.factory}):void 0}return n.prototype.toString=function(){return"InjectionToken "+this._desc},n}(),Cn="__parameters__";function En(n,t,e){var r=function(n){return function(){for(var t=[],e=0;e=Yn?e:e[ot]}function Nt(n){return n[St]}function Dt(n){var t=Nt(n);return t?Array.isArray(t)?t:t.lViewData:null}function Mt(n){return 32767&n}function Vt(n,t){for(var e=n>>16,r=t;e>0;)r=r[pt],e--;return r}var Ht,Rt,jt,Lt,zt,Ft,Bt,Ut,Gt=("undefined"!=typeof requestAnimationFrame&&requestAnimationFrame||setTimeout).bind(Sn);function Zt(){return Ht}function qt(){return Rt}function $t(){return jt}function Qt(n){jt=n}function Wt(n,t){jt=n,Ut=t}function Kt(){return Lt}function Jt(n){Lt=n}function Yt(){return zt}function Xt(){return Bt}function ne(){return Ut}var te=!1;function ee(){return te}function re(n){te=n}var oe=!0;function ie(n){oe=n}function le(n,t){var e=Ut;return zt=n&&n[Xn],Bt=n&&1==(1&n[nt]),oe=n&&zt.firstTemplatePass,Ht=n&&n[ct],jt=t,Lt=!0,Ut=n,e&&(e[rt]=Ft),Ft=n&&n[rt],e}function ue(n,t){t||(te||yt(Ut,zt.viewHooks,zt.viewCheckHooks,Bt),Ut[nt]&=-6),Ut[nt]|=16,Ut[lt]=zt.bindingStartIndex,le(n,null)}var se=!1;function ae(n){var t=se;return se=n,t}var ce=255,fe=0;function he(n,t){var e=de(n,t);if(-1!==e)return e;var r=t[Xn];r.firstTemplatePass&&(n.injectorIndex=t.length,pe(r.data,n),pe(t,null),pe(r.blueprint,null));var o=ge(n,t),i=Mt(o),l=Vt(o,t),u=n.injectorIndex;if(o!==Wn)for(var s=l[Xn].data,a=0;a<8;a++)t[u+a]=l[i+a]|s[i+a];return t[u+$n]=o,u}function pe(n,t){n.push(0,0,0,0,0,0,0,0,t)}function de(n,t){return-1===n.injectorIndex||n.parent&&n.parent.injectorIndex===n.injectorIndex||null==t[n.injectorIndex+$n]?-1:n.injectorIndex}function ge(n,t){if(n.parent&&-1!==n.parent.injectorIndex)return n.parent.injectorIndex;for(var e=t[it],r=1;e&&-1===e.injectorIndex;)e=(t=t[pt])[it],r++;return e?e.injectorIndex|r<<16|(e&&3===e.type?32768:0):-1}var ve={};function ye(n,t,e,r){var o=t[Xn],i=o.data[n+qn],l=i.flags,u=i.providerIndexes,s=o.data,a=!1;(null==r&&function(n){return 4096==(4096&n.flags)}(i)&&se||null!=r&&r!=o&&(null==o.node||3===o.node.type))&&(a=!0);for(var c=65535&u,f=l>>16,h=4095&l,p=a?c:c+(u>>16);p=f&&d.type===e)return me(s,t,p,i)}return ve}function me(n,t,e,r){var o,i=t[e];if(null!=(o=i)&&"object"==typeof o&&Object.getPrototypeOf(o)==Jn){var l=i;if(l.resolving)throw new Error("Circular dep for "+At(n[e]));var u=ae(l.canSeeViewProviders);l.resolving=!0;var s=void 0;l.injectImpl&&(s=Bn(l.injectImpl));var a=$t(),c=ne();Wt(r,t);try{i=t[e]=l.factory(null,n,t,r)}finally{l.injectImpl&&Bn(s),ae(u),l.resolving=!1,Wt(a,c)}}return i}function be(n,t,e){var r=64&n,o=32&n;return!!((128&n?r?o?e[t+7]:e[t+6]:o?e[t+5]:e[t+4]:r?o?e[t+3]:e[t+2]:o?e[t+1]:e[t])&1< ");else if("object"==typeof t){var o=[];for(var i in t)if(t.hasOwnProperty(i)){var l=t[i];o.push(i+":"+("string"==typeof l?JSON.stringify(l):Dn(l)))}r="{"+o.join(", ")+"}"}return"StaticInjectorError"+(e?"("+e+")":"")+"["+r+"]: "+n.replace(ze,"\n ")}function Ze(n,t){return new Error(Ge(n,t))}var qe=function(){return function(){}}(),$e=function(){return function(){}}(),Qe="ngProjectAs";function We(n){return!!n.listen}var Ke={createRenderer:function(n,t){return document}},Je=[];function Ye(n){for(var t=n[it];t&&2===t.type;)t=(n=n[tt])[it];return n}function Xe(n,t,e,r,o){0===n?We(t)?t.insertBefore(e,r,o):e.insertBefore(r,o,!0):1===n?We(t)?t.removeChild(e,r):e.removeChild(r):2===n&&t.destroyNode(r)}function nr(n){var t=n[Xn].childIndex;return-1===t?null:n[t]}function tr(n,t){var e;return n.length>=Yn&&(e=n[it])&&2===e.type?function(t,e){if(-1===t.index){var r=n[ht];return r>-1?n[tt][r]:null}return n[tt][t.parent.index]}(e):n[tt]===t?null:n[tt]}function er(n){if(n.length>=Yn){var t=n;!function(n){var t=n[Xn].cleanup;if(null!=t){for(var e=0;e=Yn?t[Xn].childIndex>-1&&(e=nr(t)):t[Ot].length&&(e=t[Ot][0]),null==e){for(;t&&!t[et]&&t!==n;)er(t),t=tr(t,n);er(t||n),e=t&&t[et]}t=e}}(n),n[nt]|=32},n.prototype.onDestroy=function(n){var t,e;e=n,function(n){return n[ut]||(n[ut]=[])}(t=this._view).push(e),t[Xn].firstTemplatePass&&function(n){return n[Xn].cleanup||(n[Xn].cleanup=[])}(t).push(t[ut].length-1,null)},n.prototype.markForCheck=function(){!function(n){for(var t=n;t&&!(64&t[nt]);)t[nt]|=4,t=t[tt];var e,r,o;t[nt]|=4,o=0===(e=t[st]).flags,e.flags|=1,o&&e.clean==or&&(e.clean=new Promise(function(n){return r=n}),e.scheduler(function(){if(1&e.flags&&(e.flags&=-2,mr(e)),2&e.flags){e.flags&=-3;var n=e.playerHandler;n&&n.flushPlayers()}e.clean=or,r(null)}))}(this._view)},n.prototype.detach=function(){this._view[nt]&=-9},n.prototype.reattach=function(){this._view[nt]|=8},n.prototype.detectChanges=function(){var n=qt();n.begin&&n.begin(),br(this.context),n.end&&n.end()},n.prototype.checkNoChanges=function(){!function(n){re(!0);try{br(n)}finally{re(!1)}}(this.context)},n.prototype.attachToViewContainerRef=function(n){this._viewContainerRef=n},n.prototype.detachFromAppRef=function(){this._appRef=null},n.prototype.attachToAppRef=function(n){this._appRef=n},n.prototype._lookUpContext=function(){return this._context=this._view[tt][this._componentIndex]},n}());function Or(n,t,e,r,o){var i=e[Xn],l=function(n,t,e){var r=$t();n.firstTemplatePass&&(e.providersResolver&&e.providersResolver(e),function(n,t,e){var o=-(r.index-Yn),i=n.data.length-(65535&r.providerIndexes);(n.expandoInstructions||(n.expandoInstructions=[])).push(o,i,1)}(n),function(n,t,e,r){n.data.push(e);var o=new Kn(r,function(n){return null!==n.template}(e),null);n.blueprint.push(o),t.push(o),function(n,t){n.expandoInstructions.push(t.hostBindings||Ee),t.hostVars&&n.expandoInstructions.push(t.hostVars)}(n,e)}(n,t,e,e.factory));var o=me(n.data,t,t.length-1,r);return function(n,t,e,r){var o=It(t,n);Ce(e,n),o&&Ce(o,n),null!=r.attributes&&3==t.type&&function(n,t){for(var e=Zt(),r=We(e),o=0;o>16,r=e+(4095&n),o=e;o',!this.inertBodyElement.querySelector||this.inertBodyElement.querySelector("svg")?(this.inertBodyElement.innerHTML='

',this.getInertBodyElement=this.inertBodyElement.querySelector&&this.inertBodyElement.querySelector("svg img")&&function(){try{return!!window.DOMParser}catch(n){return!1}}()?this.getInertBodyElement_DOMParser:this.getInertBodyElement_InertDocument):this.getInertBodyElement=this.getInertBodyElement_XHR}return n.prototype.getInertBodyElement_XHR=function(n){n=""+n+"";try{n=encodeURI(n)}catch(r){return null}var t=new XMLHttpRequest;t.responseType="document",t.open("GET","data:text/html;charset=utf-8,"+n,!1),t.send(void 0);var e=t.response.body;return e.removeChild(e.firstChild),e},n.prototype.getInertBodyElement_DOMParser=function(n){n=""+n+"";try{var t=(new window.DOMParser).parseFromString(n,"text/html").body;return t.removeChild(t.firstChild),t}catch(e){return null}},n.prototype.getInertBodyElement_InertDocument=function(n){var t=this.inertDocument.createElement("template");return"content"in t?(t.innerHTML=n,t):(this.inertBodyElement.innerHTML=n,this.defaultDoc.documentMode&&this.stripCustomNsAttrs(this.inertBodyElement),this.inertBodyElement)},n.prototype.stripCustomNsAttrs=function(n){for(var t=n.attributes,e=t.length-1;0"),!0},n.prototype.endElement=function(n){var t=n.nodeName.toLowerCase();Oo.hasOwnProperty(t)&&!wo.hasOwnProperty(t)&&(this.buf.push(""))},n.prototype.chars=function(n){this.buf.push(No(n))},n.prototype.checkClobberedElement=function(n,t){if(t&&(n.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_CONTAINED_BY)===Node.DOCUMENT_POSITION_CONTAINED_BY)throw new Error("Failed to sanitize html because the element is clobbered: "+n.outerHTML);return t},n}(),Io=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,Po=/([^\#-~ |!])/g;function No(n){return n.replace(/&/g,"&").replace(Io,function(n){return"&#"+(1024*(n.charCodeAt(0)-55296)+(n.charCodeAt(1)-56320)+65536)+";"}).replace(Po,function(n){return"&#"+n.charCodeAt(0)+";"}).replace(//g,">")}function Do(n){return"content"in n&&function(n){return n.nodeType===Node.ELEMENT_NODE&&"TEMPLATE"===n.nodeName}(n)?n.content:null}var Mo={provide:Zr,useFactory:function(){return new eo},deps:[]},Vo=function(n){function t(t,e){var r=n.call(this)||this;return r._bootstrapComponents=[],r.destroyCbs=[],r._bootstrapComponents=(t[vn]||null).bootstrap,r.injector=function(n,t,e){return void 0===t&&(t=null),void 0===e&&(e=null),t=t||Dr(),new Mr(n,e,t)}(t,e,[Mo,{provide:qe,useValue:r}]),r.instance=r.injector.get(t),r.componentFactoryResolver=new eo,r}return o(t,n),t.prototype.destroy=function(){this.destroyCbs.forEach(function(n){return n()}),this.destroyCbs=null},t.prototype.onDestroy=function(n){this.destroyCbs.push(n)},t}(qe);!function(n){function t(t){var e=n.call(this)||this;return e.moduleType=t,e}o(t,n),t.prototype.create=function(n){return new Vo(this.moduleType,n)}}($e);var Ho=function(n){function t(t){void 0===t&&(t=!1);var e=n.call(this)||this;return e.__isAsync=t,e}return o(t,n),t.prototype.emit=function(t){n.prototype.next.call(this,t)},t.prototype.subscribe=function(t,e,r){var o,i=function(n){return null},l=function(){return null};t&&"object"==typeof t?(o=this.__isAsync?function(n){setTimeout(function(){return t.next(n)})}:function(n){t.next(n)},t.error&&(i=this.__isAsync?function(n){setTimeout(function(){return t.error(n)})}:function(n){t.error(n)}),t.complete&&(l=this.__isAsync?function(){setTimeout(function(){return t.complete()})}:function(){t.complete()})):(o=this.__isAsync?function(n){setTimeout(function(){return t(n)})}:function(n){t(n)},e&&(i=this.__isAsync?function(n){setTimeout(function(){return e(n)})}:function(n){e(n)}),r&&(l=this.__isAsync?function(){setTimeout(function(){return r()})}:function(){r()}));var u=n.prototype.subscribe.call(this,o,i,l);return t instanceof _&&t.add(u),u},t}(H),Ro=function(){function n(){}return n.__NG_ELEMENT_ID__=function(){return jo(n,Qr)},n}(),jo=Ee,Lo=function(n){return n[n.NONE=0]="NONE",n[n.HTML=1]="HTML",n[n.STYLE=2]="STYLE",n[n.SCRIPT=3]="SCRIPT",n[n.URL=4]="URL",n[n.RESOURCE_URL=5]="RESOURCE_URL",n}({}),zo=function(){return function(){}}(),Fo=new RegExp("^([-,.\"'%_!# a-zA-Z0-9]+|(?:(?:matrix|translate|scale|rotate|skew|perspective)(?:X|Y|3d)?|(?:rgb|hsl)a?|(?:repeating-)?(?:linear|radial)-gradient|(?:calc|attr))\\([-0-9.%, #a-zA-Z]+\\))$","g"),Bo=/^url\(([^)]+)\)$/;Function,String,String;var Uo="ngDebugContext",Go="ngOriginalError",Zo="ngErrorLogger";function qo(n){return n[Uo]}function $o(n){return n[Go]}function Qo(n){for(var t=[],e=1;e0&&(o=setTimeout(function(){r._callbacks=r._callbacks.filter(function(n){return n.timeoutId!==o}),n(r._didWork,r.getPendingTasks())},t)),this._callbacks.push({doneCb:n,timeoutId:o,updateCb:e})},n.prototype.whenStable=function(n,t,e){if(e&&!this.taskTrackingZone)throw new Error('Task tracking zone is required when passing an update callback to whenStable(). Is "zone.js/dist/task-tracking.js" loaded?');this.addCallback(n,t,e),this._runCallbacksIfReady()},n.prototype.getPendingRequestCount=function(){return this._pendingCount},n.prototype.findProviders=function(n,t,e){return[]},n}(),ki=function(){function n(){this._applications=new Map,Si.addToWindow(this)}return n.prototype.registerApplication=function(n,t){this._applications.set(n,t)},n.prototype.unregisterApplication=function(n){this._applications.delete(n)},n.prototype.unregisterAllApplications=function(){this._applications.clear()},n.prototype.getTestability=function(n){return this._applications.get(n)||null},n.prototype.getAllTestabilities=function(){return Array.from(this._applications.values())},n.prototype.getAllRootElements=function(){return Array.from(this._applications.keys())},n.prototype.findTestabilityInTree=function(n,t){return void 0===t&&(t=!0),Si.findTestabilityInTree(this,n,t)},l([u("design:paramtypes",[])],n)}(),Si=new(function(){function n(){}return n.prototype.addToWindow=function(n){},n.prototype.findTestabilityInTree=function(n,t,e){return null},n}()),Ai=new wn("AllowMultipleToken"),Ti=function(){return function(n,t){this.name=n,this.token=t}}();function Ii(n,t,e){void 0===e&&(e=[]);var r="Platform: "+t,o=new wn(r);return function(t){void 0===t&&(t=[]);var i=Pi();if(!i||i.injector.get(Ai,!1))if(n)n(e.concat(t).concat({provide:o,useValue:!0}));else{var l=e.concat(t).concat({provide:o,useValue:!0});!function(n){if(Ei&&!Ei.destroyed&&!Ei.injector.get(Ai,!1))throw new Error("There can be only one platform. Destroy the previous one to create a new one.");Ei=n.get(Ni);var t=n.get(ri,null);t&&t.forEach(function(n){return n()})}(Ne.create({providers:l,name:r}))}return function(n){var t=Pi();if(!t)throw new Error("No platform exists!");if(!t.injector.get(n,null))throw new Error("A platform with a different configuration has been created. Please destroy it first.");return t}(o)}}function Pi(){return Ei&&!Ei.destroyed?Ei:null}var Ni=function(){function n(n){this._injector=n,this._modules=[],this._destroyListeners=[],this._destroyed=!1}return n.prototype.bootstrapModuleFactory=function(n,t){var e,r=this,o="noop"===(e=t?t.ngZone:void 0)?new xi:("zone.js"===e?void 0:e)||new yi({enableLongStackTrace:ho()}),i=[{provide:yi,useValue:o}];return o.run(function(){var t=Ne.create({providers:i,parent:r.injector,name:n.moduleType.name}),e=n.create(t),l=e.injector.get(Wo,null);if(!l)throw new Error("No ErrorHandler. Is platform module (BrowserModule) included?");return e.onDestroy(function(){return Vi(r._modules,e)}),o.runOutsideAngular(function(){return o.onError.subscribe({next:function(n){l.handleError(n)}})}),function(n,t,o){try{var i=((l=e.injector.get(Xo)).runInitializers(),l.donePromise.then(function(){return r._moduleDoBootstrap(e),e}));return Ko(i)?i.catch(function(e){throw t.runOutsideAngular(function(){return n.handleError(e)}),e}):i}catch(u){throw t.runOutsideAngular(function(){return n.handleError(u)}),u}var l}(l,o)})},n.prototype.bootstrapModule=function(n,t){var e=this;void 0===t&&(t=[]);var r=Di({},t);return function(n,t,e){return n.get(fi).createCompiler([t]).compileModuleAsync(e)}(this.injector,r,n).then(function(n){return e.bootstrapModuleFactory(n,r)})},n.prototype._moduleDoBootstrap=function(n){var t=n.injector.get(Mi);if(n._bootstrapComponents.length>0)n._bootstrapComponents.forEach(function(n){return t.bootstrap(n)});else{if(!n.instance.ngDoBootstrap)throw new Error("The module "+Dn(n.instance.constructor)+' was bootstrapped, but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. Please define one of these.');n.instance.ngDoBootstrap(t)}this._modules.push(n)},n.prototype.onDestroy=function(n){this._destroyListeners.push(n)},Object.defineProperty(n.prototype,"injector",{get:function(){return this._injector},enumerable:!0,configurable:!0}),n.prototype.destroy=function(){if(this._destroyed)throw new Error("The platform has already been destroyed!");this._modules.slice().forEach(function(n){return n.destroy()}),this._destroyListeners.forEach(function(n){return n()}),this._destroyed=!0},Object.defineProperty(n.prototype,"destroyed",{get:function(){return this._destroyed},enumerable:!0,configurable:!0}),n}();function Di(n,t){return Array.isArray(t)?t.reduce(Di,n):i({},n,t)}var Mi=function(){function n(n,t,e,r,o,i){var l=this;this._zone=n,this._console=t,this._injector=e,this._exceptionHandler=r,this._componentFactoryResolver=o,this._initStatus=i,this._bootstrapListeners=[],this._views=[],this._runningTick=!1,this._enforceNoNewChanges=!1,this._stable=!0,this.componentTypes=[],this.components=[],this._enforceNoNewChanges=ho(),this._zone.onMicrotaskEmpty.subscribe({next:function(){l._zone.run(function(){l.tick()})}});var u=new I(function(n){l._stable=l._zone.isStable&&!l._zone.hasPendingMacrotasks&&!l._zone.hasPendingMicrotasks,l._zone.runOutsideAngular(function(){n.next(l._stable),n.complete()})}),s=new I(function(n){var t;l._zone.runOutsideAngular(function(){t=l._zone.onStable.subscribe(function(){yi.assertNotInAngularZone(),Pn(function(){l._stable||l._zone.hasPendingMacrotasks||l._zone.hasPendingMicrotasks||(l._stable=!0,n.next(!0))})})});var e=l._zone.onUnstable.subscribe(function(){yi.assertInAngularZone(),l._stable&&(l._stable=!1,l._zone.runOutsideAngular(function(){n.next(!1)}))});return function(){t.unsubscribe(),e.unsubscribe()}});this.isStable=function(){for(var n=[],t=0;t1&&"number"==typeof n[n.length-1]&&(r=n.pop())):"number"==typeof i&&(r=n.pop()),null===o&&1===n.length&&n[0]instanceof I?n[0]:function(n){return void 0===n&&(n=Number.POSITIVE_INFINITY),function n(t,e,r){return void 0===r&&(r=Number.POSITIVE_INFINITY),"function"==typeof e?function(o){return o.pipe(n(function(n,r){return nn(t(n,r)).pipe(K(function(t,o){return e(n,t,r,o)}))},r))}:("number"==typeof e&&(r=e),function(n){return n.lift(new tn(t,r))})}(rn,n)}(r)(X(n,o))}(u,s.pipe(function(n){return on()((t=fn,function(n){var e;e="function"==typeof t?t:function(){return t};var r=Object.create(n,an);return r.source=n,r.subjectFactory=e,r})(n));var t}))}var t;return t=n,n.prototype.bootstrap=function(n,t){var e,r=this;if(!this._initStatus.done)throw new Error("Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.");e=n instanceof Fr?n:this._componentFactoryResolver.resolveComponentFactory(n),this.componentTypes.push(e.componentType);var o=e instanceof $r?null:this._injector.get(qe),i=e.create(Ne.NULL,[],t||e.selector,o);i.onDestroy(function(){r._unloadComponent(i)});var l=i.injector.get(Oi,null);return l&&i.injector.get(ki).registerApplication(i.location.nativeElement,l),this._loadComponent(i),ho()&&this._console.log("Angular is running in the development mode. Call enableProdMode() to enable the production mode."),i},n.prototype.tick=function(){var n=this;if(this._runningTick)throw new Error("ApplicationRef.tick is called recursively");var e=t._tickScope();try{this._runningTick=!0,this._views.forEach(function(n){return n.detectChanges()}),this._enforceNoNewChanges&&this._views.forEach(function(n){return n.checkNoChanges()})}catch(r){this._zone.runOutsideAngular(function(){return n._exceptionHandler.handleError(r)})}finally{this._runningTick=!1,vi(e)}},n.prototype.attachView=function(n){var t=n;this._views.push(t),t.attachToAppRef(this)},n.prototype.detachView=function(n){var t=n;Vi(this._views,t),t.detachFromAppRef()},n.prototype._loadComponent=function(n){this.attachView(n.hostView),this.tick(),this.components.push(n),this._injector.get(ii,[]).concat(this._bootstrapListeners).forEach(function(t){return t(n)})},n.prototype._unloadComponent=function(n){this.detachView(n.hostView),Vi(this.components,n)},n.prototype.ngOnDestroy=function(){this._views.slice().forEach(function(n){return n.destroy()})},Object.defineProperty(n.prototype,"viewCount",{get:function(){return this._views.length},enumerable:!0,configurable:!0}),n._tickScope=gi("ApplicationRef#tick()"),n}();function Vi(n,t){var e=n.indexOf(t);e>-1&&n.splice(e,1)}var Hi,Ri=function(){function n(){this.dirty=!0,this._results=[],this.changes=new Ho,this.length=0}return n.prototype.map=function(n){return this._results.map(n)},n.prototype.filter=function(n){return this._results.filter(n)},n.prototype.find=function(n){return this._results.find(n)},n.prototype.reduce=function(n,t){return this._results.reduce(n,t)},n.prototype.forEach=function(n){this._results.forEach(n)},n.prototype.some=function(n){return this._results.some(n)},n.prototype.toArray=function(){return this._results.slice()},n.prototype[In()]=function(){return this._results[In()]()},n.prototype.toString=function(){return this._results.toString()},n.prototype.reset=function(n){this._results=function n(t){return t.reduce(function(t,e){var r=Array.isArray(e)?n(e):e;return t.concat(r)},[])}(n),this.dirty=!1,this.length=this._results.length,this.last=this._results[this.length-1],this.first=this._results[0]},n.prototype.notifyOnChanges=function(){this.changes.emit(this)},n.prototype.setDirty=function(){this.dirty=!0},n.prototype.destroy=function(){this.changes.complete(),this.changes.unsubscribe()},n}(),ji=function(){function n(){}return n.__NG_ELEMENT_ID__=function(){return Li(n,Qr)},n}(),Li=Ee,zi=function(){function n(){}return n.__NG_ELEMENT_ID__=function(){return Fi()},n}(),Fi=function(){for(var n=[],t=0;t-1}(r)||"root"===o.providedIn&&r._def.isRoot))){var c=n._providers.length;return n._def.providersByKey[t.tokenKey]={flags:5120,value:u.factory,deps:[],index:c,token:t.token},n._providers[c]=fu,n._providers[c]=yu(n,n._def.providersByKey[t.tokenKey])}return 4&t.flags?e:n._parent.get(t.token,e)}finally{Fn(i)}}function yu(n,t){var e;switch(201347067&t.flags){case 512:e=function(n,t,e){var r=e.length;switch(r){case 0:return new t;case 1:return new t(vu(n,e[0]));case 2:return new t(vu(n,e[0]),vu(n,e[1]));case 3:return new t(vu(n,e[0]),vu(n,e[1]),vu(n,e[2]));default:for(var o=new Array(r),i=0;i=e.length)&&(t=e.length-1),t<0)return null;var r=e[t];return r.viewContainerParent=null,Cu(e,t),Cl.dirtyParentQueries(r),_u(r),r}function bu(n,t,e){var r=t?Fl(t,t.def.lastRenderRootNode):n.renderElement,o=e.renderer.parentNode(r),i=e.renderer.nextSibling(r);Wl(e,2,o,i,void 0)}function _u(n){Wl(n,3,null,null,void 0)}function wu(n,t,e){t>=n.length?n.push(e):n.splice(t,0,e)}function Cu(n,t){t>=n.length-1?n.pop():n.splice(t,1)}var Eu=new Object;function xu(n,t,e,r,o,i){return new Ou(n,t,e,r,o,i)}var Ou=function(n){function t(t,e,r,o,i,l){var u=n.call(this)||this;return u.selector=t,u.componentType=e,u._inputs=o,u._outputs=i,u.ngContentSelectors=l,u.viewDefFactory=r,u}return o(t,n),Object.defineProperty(t.prototype,"inputs",{get:function(){var n=[],t=this._inputs;for(var e in t)n.push({propName:e,templateName:t[e]});return n},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"outputs",{get:function(){var n=[];for(var t in this._outputs)n.push({propName:t,templateName:this._outputs[t]});return n},enumerable:!0,configurable:!0}),t.prototype.create=function(n,t,e,r){if(!r)throw new Error("ngModule should be provided");var o=Ql(this.viewDefFactory),i=o.nodes[0].element.componentProvider.nodeIndex,l=Cl.createRootView(n,t||[],e,o,r,Eu),u=bl(l,i).instance;return e&&l.renderer.setAttribute(ml(l,0).renderElement,"ng-version",to.full),new ku(l,new Iu(l),u)},t}(Fr),ku=function(n){function t(t,e,r){var o=n.call(this)||this;return o._view=t,o._viewRef=e,o._component=r,o._elDef=o._view.def.nodes[0],o.hostView=e,o.changeDetectorRef=e,o.instance=r,o}return o(t,n),Object.defineProperty(t.prototype,"location",{get:function(){return new Qr(ml(this._view,this._elDef.nodeIndex).renderElement)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"injector",{get:function(){return new Mu(this._view,this._elDef)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"componentType",{get:function(){return this._component.constructor},enumerable:!0,configurable:!0}),t.prototype.destroy=function(){this._viewRef.destroy()},t.prototype.onDestroy=function(n){this._viewRef.onDestroy(n)},t}(zr);function Su(n,t,e){return new Au(n,t,e)}var Au=function(){function n(n,t,e){this._view=n,this._elDef=t,this._data=e,this._embeddedViews=[]}return Object.defineProperty(n.prototype,"element",{get:function(){return new Qr(this._data.renderElement)},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"injector",{get:function(){return new Mu(this._view,this._elDef)},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"parentInjector",{get:function(){for(var n=this._view,t=this._elDef.parent;!t&&n;)t=zl(n),n=n.parent;return n?new Mu(n,t):new Mu(this._view,null)},enumerable:!0,configurable:!0}),n.prototype.clear=function(){for(var n=this._embeddedViews.length-1;n>=0;n--){var t=mu(this._data,n);Cl.destroyView(t)}},n.prototype.get=function(n){var t=this._embeddedViews[n];if(t){var e=new Iu(t);return e.attachToViewContainerRef(this),e}return null},Object.defineProperty(n.prototype,"length",{get:function(){return this._embeddedViews.length},enumerable:!0,configurable:!0}),n.prototype.createEmbeddedView=function(n,t,e){var r=n.createEmbeddedView(t||{});return this.insert(r,e),r},n.prototype.createComponent=function(n,t,e,r,o){var i=e||this.parentInjector;o||n instanceof $r||(o=i.get(qe));var l=n.create(i,r,void 0,o);return this.insert(l.hostView,t),l},n.prototype.insert=function(n,t){if(n.destroyed)throw new Error("Cannot insert a destroyed View in a ViewContainer!");var e,r,o,i,l=n;return i=(e=this._data).viewContainer._embeddedViews,null==(r=t)&&(r=i.length),(o=l._view).viewContainerParent=this._view,wu(i,r,o),function(n,t){var e=Ll(t);if(e&&e!==n&&!(16&t.state)){t.state|=16;var r=e.template._projectedViews;r||(r=e.template._projectedViews=[]),r.push(t),function(n,e){if(!(4&e.flags)){t.parent.def.nodeFlags|=4,e.flags|=4;for(var r=e.parent;r;)r.childFlags|=4,r=r.parent}}(0,t.parentNodeDef)}}(e,o),Cl.dirtyParentQueries(o),bu(e,r>0?i[r-1]:null,o),l.attachToViewContainerRef(this),n},n.prototype.move=function(n,t){if(n.destroyed)throw new Error("Cannot move a destroyed View in a ViewContainer!");var e,r,o,i,l,u=this._embeddedViews.indexOf(n._view);return o=t,l=(i=(e=this._data).viewContainer._embeddedViews)[r=u],Cu(i,r),null==o&&(o=i.length),wu(i,o,l),Cl.dirtyParentQueries(l),_u(l),bu(e,o>0?i[o-1]:null,l),n},n.prototype.indexOf=function(n){return this._embeddedViews.indexOf(n._view)},n.prototype.remove=function(n){var t=mu(this._data,n);t&&Cl.destroyView(t)},n.prototype.detach=function(n){var t=mu(this._data,n);return t?new Iu(t):null},n}();function Tu(n){return new Iu(n)}var Iu=function(){function n(n){this._view=n,this._viewContainerRef=null,this._appRef=null}return Object.defineProperty(n.prototype,"rootNodes",{get:function(){return Wl(this._view,0,void 0,void 0,n=[]),n;var n},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"context",{get:function(){return this._view.context},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"destroyed",{get:function(){return 0!=(128&this._view.state)},enumerable:!0,configurable:!0}),n.prototype.markForCheck=function(){Hl(this._view)},n.prototype.detach=function(){this._view.state&=-5},n.prototype.detectChanges=function(){var n=this._view.root.rendererFactory;n.begin&&n.begin();try{Cl.checkAndUpdateView(this._view)}finally{n.end&&n.end()}},n.prototype.checkNoChanges=function(){Cl.checkNoChangesView(this._view)},n.prototype.reattach=function(){this._view.state|=4},n.prototype.onDestroy=function(n){this._view.disposables||(this._view.disposables=[]),this._view.disposables.push(n)},n.prototype.destroy=function(){this._appRef?this._appRef.detachView(this):this._viewContainerRef&&this._viewContainerRef.detach(this._viewContainerRef.indexOf(this)),Cl.destroyView(this._view)},n.prototype.detachFromAppRef=function(){this._appRef=null,_u(this._view),Cl.dirtyParentQueries(this._view)},n.prototype.attachToAppRef=function(n){if(this._viewContainerRef)throw new Error("This view is already attached to a ViewContainer!");this._appRef=n},n.prototype.attachToViewContainerRef=function(n){if(this._appRef)throw new Error("This view is already attached directly to the ApplicationRef!");this._viewContainerRef=n},n}();function Pu(n,t){return new Nu(n,t)}var Nu=function(n){function t(t,e){var r=n.call(this)||this;return r._parentView=t,r._def=e,r}return o(t,n),t.prototype.createEmbeddedView=function(n){return new Iu(Cl.createEmbeddedView(this._parentView,this._def,this._def.element.template,n))},Object.defineProperty(t.prototype,"elementRef",{get:function(){return new Qr(ml(this._parentView,this._def.nodeIndex).renderElement)},enumerable:!0,configurable:!0}),t}(Ro);function Du(n,t){return new Mu(n,t)}var Mu=function(){function n(n,t){this.view=n,this.elDef=t}return n.prototype.get=function(n,t){return void 0===t&&(t=Ne.THROW_IF_NOT_FOUND),Cl.resolveDep(this.view,this.elDef,!!this.elDef&&0!=(33554432&this.elDef.flags),{flags:0,token:n,tokenKey:Al(n)},t)},n}();function Vu(n,t){var e=n.def.nodes[t];if(1&e.flags){var r=ml(n,e.nodeIndex);return e.element.template?r.template:r.renderElement}if(2&e.flags)return yl(n,e.nodeIndex).renderText;if(20240&e.flags)return bl(n,e.nodeIndex).instance;throw new Error("Illegal state: read nodeValue for node index "+t)}function Hu(n){return new Ru(n.renderer)}var Ru=function(){function n(n){this.delegate=n}return n.prototype.selectRootElement=function(n){return this.delegate.selectRootElement(n)},n.prototype.createElement=function(n,t){var e=a(tu(t),2),r=this.delegate.createElement(e[1],e[0]);return n&&this.delegate.appendChild(n,r),r},n.prototype.createViewRoot=function(n){return n},n.prototype.createTemplateAnchor=function(n){var t=this.delegate.createComment("");return n&&this.delegate.appendChild(n,t),t},n.prototype.createText=function(n,t){var e=this.delegate.createText(t);return n&&this.delegate.appendChild(n,e),e},n.prototype.projectNodes=function(n,t){for(var e=0;e0,t.provider.value,t.provider.deps);if(t.outputs.length)for(var r=0;r0,r=t.provider;switch(201347067&t.flags){case 512:return es(n,t.parent,e,r.value,r.deps);case 1024:return function(n,t,e,r,o){var i=o.length;switch(i){case 0:return r();case 1:return r(os(n,t,e,o[0]));case 2:return r(os(n,t,e,o[0]),os(n,t,e,o[1]));case 3:return r(os(n,t,e,o[0]),os(n,t,e,o[1]),os(n,t,e,o[2]));default:for(var l=Array(i),u=0;u0)a=g,_s(g)||(c=g);else for(;a&&d===a.nodeIndex+a.childCount;){var m=a.parent;m&&(m.childFlags|=a.childFlags,m.childMatchedQueries|=a.childMatchedQueries),c=(a=m)&&_s(a)?a.renderParent:a}}return{factory:null,nodeFlags:l,rootNodeFlags:u,nodeMatchedQueries:s,flags:n,nodes:t,updateDirectives:e||kl,updateRenderer:r||kl,handleEvent:function(n,e,r,o){return t[e].element.handleEvent(n,r,o)},bindingCount:o,outputCount:i,lastRenderRootNode:p}}function _s(n){return 0!=(1&n.flags)&&null===n.element.name}function ws(n,t,e){var r=t.element&&t.element.template;if(r){if(!r.lastRenderRootNode)throw new Error("Illegal State: Embedded templates without nodes are not allowed!");if(r.lastRenderRootNode&&16777216&r.lastRenderRootNode.flags)throw new Error("Illegal State: Last root node of a template can't have embedded views, at index "+t.nodeIndex+"!")}if(20224&t.flags&&0==(1&(n?n.flags:0)))throw new Error("Illegal State: StaticProvider/Directive nodes need to be children of elements or anchors, at index "+t.nodeIndex+"!");if(t.query){if(67108864&t.flags&&(!n||0==(16384&n.flags)))throw new Error("Illegal State: Content Query nodes need to be children of directives, at index "+t.nodeIndex+"!");if(134217728&t.flags&&n)throw new Error("Illegal State: View Query nodes have to be top level nodes, at index "+t.nodeIndex+"!")}if(t.childCount){var o=n?n.nodeIndex+n.childCount:e-1;if(t.nodeIndex<=o&&t.nodeIndex+t.childCount>o)throw new Error("Illegal State: childCount of node leads outside of parent, at index "+t.nodeIndex+"!")}}function Cs(n,t,e,r){var o=Os(n.root,n.renderer,n,t,e);return ks(o,n.component,r),Ss(o),o}function Es(n,t,e){var r=Os(n,n.renderer,null,null,t);return ks(r,e,e),Ss(r),r}function xs(n,t,e,r){var o,i=t.element.componentRendererType;return o=i?n.root.rendererFactory.createRenderer(r,i):n.root.renderer,Os(n.root,o,n,t.element.componentProvider,e)}function Os(n,t,e,r,o){var i=new Array(o.nodes.length),l=o.outputCount?new Array(o.outputCount):null;return{def:o,parent:e,viewContainerParent:null,parentNodeDef:r,context:null,component:null,nodes:i,state:13,root:n,renderer:t,oldValues:new Array(o.bindingCount),disposables:l,initIndex:-1}}function ks(n,t,e){n.component=t,n.context=e}function Ss(n){var t;Bl(n)&&(t=ml(n.parent,n.parentNodeDef.parent.nodeIndex).renderElement);for(var e=n.def,r=n.nodes,o=0;o0&&cu(n,t,0,e)&&(p=!0),h>1&&cu(n,t,1,r)&&(p=!0),h>2&&cu(n,t,2,o)&&(p=!0),h>3&&cu(n,t,3,i)&&(p=!0),h>4&&cu(n,t,4,l)&&(p=!0),h>5&&cu(n,t,5,u)&&(p=!0),h>6&&cu(n,t,6,s)&&(p=!0),h>7&&cu(n,t,7,a)&&(p=!0),h>8&&cu(n,t,8,c)&&(p=!0),h>9&&cu(n,t,9,f)&&(p=!0),p}(n,t,e,r,o,i,l,u,s,a,c,f);case 2:return function(n,t,e,r,o,i,l,u,s,a,c,f){var h=!1,p=t.bindings,d=p.length;if(d>0&&Ml(n,t,0,e)&&(h=!0),d>1&&Ml(n,t,1,r)&&(h=!0),d>2&&Ml(n,t,2,o)&&(h=!0),d>3&&Ml(n,t,3,i)&&(h=!0),d>4&&Ml(n,t,4,l)&&(h=!0),d>5&&Ml(n,t,5,u)&&(h=!0),d>6&&Ml(n,t,6,s)&&(h=!0),d>7&&Ml(n,t,7,a)&&(h=!0),d>8&&Ml(n,t,8,c)&&(h=!0),d>9&&Ml(n,t,9,f)&&(h=!0),h){var g=t.text.prefix;d>0&&(g+=ms(e,p[0])),d>1&&(g+=ms(r,p[1])),d>2&&(g+=ms(o,p[2])),d>3&&(g+=ms(i,p[3])),d>4&&(g+=ms(l,p[4])),d>5&&(g+=ms(u,p[5])),d>6&&(g+=ms(s,p[6])),d>7&&(g+=ms(a,p[7])),d>8&&(g+=ms(c,p[8])),d>9&&(g+=ms(f,p[9]));var v=yl(n,t.nodeIndex).renderText;n.renderer.setValue(v,g)}return h}(n,t,e,r,o,i,l,u,s,a,c,f);case 16384:return function(n,t,e,r,o,i,l,u,s,a,c,f){var h=bl(n,t.nodeIndex),p=h.instance,d=!1,g=void 0,v=t.bindings.length;return v>0&&Dl(n,t,0,e)&&(d=!0,g=ls(n,h,t,0,e,g)),v>1&&Dl(n,t,1,r)&&(d=!0,g=ls(n,h,t,1,r,g)),v>2&&Dl(n,t,2,o)&&(d=!0,g=ls(n,h,t,2,o,g)),v>3&&Dl(n,t,3,i)&&(d=!0,g=ls(n,h,t,3,i,g)),v>4&&Dl(n,t,4,l)&&(d=!0,g=ls(n,h,t,4,l,g)),v>5&&Dl(n,t,5,u)&&(d=!0,g=ls(n,h,t,5,u,g)),v>6&&Dl(n,t,6,s)&&(d=!0,g=ls(n,h,t,6,s,g)),v>7&&Dl(n,t,7,a)&&(d=!0,g=ls(n,h,t,7,a,g)),v>8&&Dl(n,t,8,c)&&(d=!0,g=ls(n,h,t,8,c,g)),v>9&&Dl(n,t,9,f)&&(d=!0,g=ls(n,h,t,9,f,g)),g&&p.ngOnChanges(g),65536&t.flags&&vl(n,256,t.nodeIndex)&&p.ngOnInit(),262144&t.flags&&p.ngDoCheck(),d}(n,t,e,r,o,i,l,u,s,a,c,f);case 32:case 64:case 128:return function(n,t,e,r,o,i,l,u,s,a,c,f){var h=t.bindings,p=!1,d=h.length;if(d>0&&Ml(n,t,0,e)&&(p=!0),d>1&&Ml(n,t,1,r)&&(p=!0),d>2&&Ml(n,t,2,o)&&(p=!0),d>3&&Ml(n,t,3,i)&&(p=!0),d>4&&Ml(n,t,4,l)&&(p=!0),d>5&&Ml(n,t,5,u)&&(p=!0),d>6&&Ml(n,t,6,s)&&(p=!0),d>7&&Ml(n,t,7,a)&&(p=!0),d>8&&Ml(n,t,8,c)&&(p=!0),d>9&&Ml(n,t,9,f)&&(p=!0),p){var g=_l(n,t.nodeIndex),v=void 0;switch(201347067&t.flags){case 32:v=new Array(h.length),d>0&&(v[0]=e),d>1&&(v[1]=r),d>2&&(v[2]=o),d>3&&(v[3]=i),d>4&&(v[4]=l),d>5&&(v[5]=u),d>6&&(v[6]=s),d>7&&(v[7]=a),d>8&&(v[8]=c),d>9&&(v[9]=f);break;case 64:v={},d>0&&(v[h[0].name]=e),d>1&&(v[h[1].name]=r),d>2&&(v[h[2].name]=o),d>3&&(v[h[3].name]=i),d>4&&(v[h[4].name]=l),d>5&&(v[h[5].name]=u),d>6&&(v[h[6].name]=s),d>7&&(v[h[7].name]=a),d>8&&(v[h[8].name]=c),d>9&&(v[h[9].name]=f);break;case 128:var y=e;switch(d){case 1:v=y.transform(e);break;case 2:v=y.transform(r);break;case 3:v=y.transform(r,o);break;case 4:v=y.transform(r,o,i);break;case 5:v=y.transform(r,o,i,l);break;case 6:v=y.transform(r,o,i,l,u);break;case 7:v=y.transform(r,o,i,l,u,s);break;case 8:v=y.transform(r,o,i,l,u,s,a);break;case 9:v=y.transform(r,o,i,l,u,s,a,c);break;case 10:v=y.transform(r,o,i,l,u,s,a,c,f)}}g.value=v}return p}(n,t,e,r,o,i,l,u,s,a,c,f);default:throw"unreachable"}}(n,t,r,o,i,l,u,s,a,f,h,p):function(n,t,e){switch(201347067&t.flags){case 1:return function(n,t,e){for(var r=!1,o=0;o0&&Vl(n,t,0,e),h>1&&Vl(n,t,1,r),h>2&&Vl(n,t,2,o),h>3&&Vl(n,t,3,i),h>4&&Vl(n,t,4,l),h>5&&Vl(n,t,5,u),h>6&&Vl(n,t,6,s),h>7&&Vl(n,t,7,a),h>8&&Vl(n,t,8,c),h>9&&Vl(n,t,9,f)}(n,t,r,o,i,l,u,s,a,c,f,h):function(n,t,e){for(var r=0;r0){var i=new Set(n.modules);Ws.forEach(function(t,r){if(i.has(bn(r).providedIn)){var o={token:r,flags:t.flags|(e?4096:0),deps:Zl(t.deps),value:t.value,index:n.providers.length};n.providers.push(o),n.providersByKey[Al(r)]=o}})}}(n=n.factory(function(){return kl})),n):n}(r))}var Qs=new Map,Ws=new Map,Ks=new Map;function Js(n){var t;Qs.set(n.token,n),"function"==typeof n.token&&(t=bn(n.token))&&"function"==typeof t.providedIn&&Ws.set(n.token,n)}function Ys(n,t){var e=Ql(t.viewDefFactory),r=Ql(e.nodes[0].element.componentView);Ks.set(n,r)}function Xs(){Qs.clear(),Ws.clear(),Ks.clear()}function na(n){if(0===Qs.size)return n;var t=function(n){for(var t=[],e=null,r=0;r=this.currentHistoricCoverage.lcq)return!1}else if("branchCoverageIncreaseOnly"===t){var r=this.branchCoverage;if(isNaN(r)||r<=this.currentHistoricCoverage.bcq)return!1}else if("branchCoverageDecreaseOnly"===t&&(r=this.branchCoverage,isNaN(r)||r>=this.currentHistoricCoverage.bcq))return!1;return!0},t.prototype.updateCurrentHistoricCoverage=function(n){if(this.currentHistoricCoverage=null,""!==n)for(var t=0;t-1&&null===e,r}return o(t,n),t.prototype.visible=function(n,t){if(""!==n&&this.name.toLowerCase().indexOf(n.toLowerCase())>-1)return!0;for(var e=0;et&&(r[o].collapsed=n.settings.collapseStates[t]),t++,e(r[o].subElements)};e(this.codeElements)},n}(),La=new I(function(n){return n.complete()}),za=function(n){function t(t,e){var r=n.call(this,t)||this;r.sources=e,r.completed=0,r.haveValues=0;var o=e.length;r.values=new Array(o);for(var i=0;i0},t.prototype.tagName=function(n){return n.tagName},t.prototype.attributeMap=function(n){for(var t=new Map,e=n.attributes,r=0;r0;l||(l=n[i]=[]);var s=$c(t)?Zone.root:Zone.current;if(0===l.length)l.push({zone:s,handler:o});else{for(var a=!1,c=0;c-1},t}(kc),tf=["alt","control","meta","shift"],ef={alt:function(n){return n.altKey},control:function(n){return n.ctrlKey},meta:function(n){return n.metaKey},shift:function(n){return n.shiftKey}},rf=function(n){function t(t){return n.call(this,t)||this}var e;return o(t,n),e=t,t.prototype.supports=function(n){return null!=e.parseEventName(n)},t.prototype.addEventListener=function(n,t,r){var o=e.parseEventName(t),i=e.eventCallback(o.fullKey,r,this.manager.getZone());return this.manager.getZone().runOutsideAngular(function(){return uc().onAndCancel(n,o.domEventName,i)})},t.parseEventName=function(n){var t=n.toLowerCase().split("."),r=t.shift();if(0===t.length||"keydown"!==r&&"keyup"!==r)return null;var o=e._normalizeKey(t.pop()),i="";if(tf.forEach(function(n){var e=t.indexOf(n);e>-1&&(t.splice(e,1),i+=n+".")}),i+=o,0!=t.length||0===o.length)return null;var l={};return l.domEventName=r,l.fullKey=i,l},t.getEventFullKey=function(n){var t="",e=uc().getEventKey(n);return" "===(e=e.toLowerCase())?e="space":"."===e&&(e="dot"),tf.forEach(function(r){r!=e&&(0,ef[r])(n)&&(t+=r+".")}),t+=e},t.eventCallback=function(n,t,r){return function(o){e.getEventFullKey(o)===n&&r.runGuarded(function(){return t(o)})}},t._normalizeKey=function(n){switch(n){case"esc":return"escape";default:return n}},t}(kc),of=function(){return function(){}}(),lf=function(n){function t(t){var e=n.call(this)||this;return e._doc=t,e}return o(t,n),t.prototype.sanitize=function(n,t){if(null==t)return null;switch(n){case Lo.NONE:return t;case Lo.HTML:return t instanceof sf?t.changingThisBreaksApplicationSecurity:(this.checkNotSafeValue(t,"HTML"),function(n,t){var e=null;try{_o=_o||new po(n);var r=t?String(t):"";e=_o.getInertBodyElement(r);var o=5,i=r;do{if(0===o)throw new Error("Failed to sanitize html because the input is unstable");o--,r=i,i=e.innerHTML,e=_o.getInertBodyElement(r)}while(r!==i);var l=new To,u=l.sanitizeChildren(Do(e)||e);return ho()&&l.sanitizedSomething&&console.warn("WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss)."),u}finally{if(e)for(var s=Do(e)||e;s.firstChild;)s.removeChild(s.firstChild)}}(this._doc,String(t)));case Lo.STYLE:return t instanceof af?t.changingThisBreaksApplicationSecurity:(this.checkNotSafeValue(t,"Style"),function(n){if(!(n=String(n).trim()))return"";var t=n.match(Bo);return t&&yo(t[1])===t[1]||n.match(Fo)&&function(n){for(var t=!0,e=!0,r=0;rn?{max:{max:n,actual:t.value}}:null}},n.required=function(n){return mf(n.value)?{required:!0}:null},n.requiredTrue=function(n){return!0===n.value?null:{required:!0}},n.email=function(n){return mf(n.value)?null:bf.test(n.value)?null:{email:!0}},n.minLength=function(n){return function(t){if(mf(t.value))return null;var e=t.value?t.value.length:0;return en?{maxlength:{requiredLength:n,actualLength:e}}:null}},n.pattern=function(t){return t?("string"==typeof t?(r="","^"!==t.charAt(0)&&(r+="^"),r+=t,"$"!==t.charAt(t.length-1)&&(r+="$"),e=new RegExp(r)):(r=t.toString(),e=t),function(n){if(mf(n.value))return null;var t=n.value;return e.test(t)?null:{pattern:{requiredPattern:r,actualValue:t}}}):n.nullValidator;var e,r},n.nullValidator=function(n){return null},n.compose=function(n){if(!n)return null;var t=n.filter(wf);return 0==t.length?null:function(n){return Ef(function(n,e){return t.map(function(t){return t(n)})}(n))}},n.composeAsync=function(n){if(!n)return null;var t=n.filter(wf);return 0==t.length?null:function(n){return function n(){for(var t,e=[],r=0;r=0;--t)if(this._accessors[t][1]===n)return void this._accessors.splice(t,1)},n.prototype.select=function(n){var t=this;this._accessors.forEach(function(e){t._isSameGroup(e,n)&&e[1]!==n&&e[1].fireUncheck(n.value)})},n.prototype._isSameGroup=function(n,t){return!!n[0].control&&n[0]._parent===t._control._parent&&n[1].name===t.name},n}(),Mf=function(){function n(n,t,e,r){this._renderer=n,this._elementRef=t,this._registry=e,this._injector=r,this.onChange=function(){},this.onTouched=function(){}}return n.prototype.ngOnInit=function(){this._control=this._injector.get(Nf),this._checkName(),this._registry.add(this._control,this)},n.prototype.ngOnDestroy=function(){this._registry.remove(this)},n.prototype.writeValue=function(n){this._state=n===this.value,this._renderer.setProperty(this._elementRef.nativeElement,"checked",this._state)},n.prototype.registerOnChange=function(n){var t=this;this._fn=n,this.onChange=function(){n(t.value),t._registry.select(t)}},n.prototype.fireUncheck=function(n){this.writeValue(n)},n.prototype.registerOnTouched=function(n){this.onTouched=n},n.prototype.setDisabledState=function(n){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",n)},n.prototype._checkName=function(){this.name&&this.formControlName&&this.name!==this.formControlName&&this._throwNameError(),!this.name&&this.formControlName&&(this.name=this.formControlName)},n.prototype._throwNameError=function(){throw new Error('\n If you define both a name and a formControlName attribute on your radio button, their values\n must match. Ex: \n ')},n}(),Vf=function(){function n(n,t){this._renderer=n,this._elementRef=t,this.onChange=function(n){},this.onTouched=function(){}}return n.prototype.writeValue=function(n){this._renderer.setProperty(this._elementRef.nativeElement,"value",parseFloat(n))},n.prototype.registerOnChange=function(n){this.onChange=function(t){n(""==t?null:parseFloat(t))}},n.prototype.registerOnTouched=function(n){this.onTouched=n},n.prototype.setDisabledState=function(n){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",n)},n}(),Hf='\n

\n
\n \n
\n
\n\n In your class:\n\n this.myGroup = new FormGroup({\n person: new FormGroup({ firstName: new FormControl() })\n });',Rf='\n
\n
\n \n
\n
';function jf(n,t){return null==n?""+t:(t&&"object"==typeof t&&(t="Object"),(n+": "+t).slice(0,50))}var Lf=function(){function n(n,t){this._renderer=n,this._elementRef=t,this._optionMap=new Map,this._idCounter=0,this.onChange=function(n){},this.onTouched=function(){},this._compareWith=Nn}return Object.defineProperty(n.prototype,"compareWith",{set:function(n){if("function"!=typeof n)throw new Error("compareWith must be a function, but received "+JSON.stringify(n));this._compareWith=n},enumerable:!0,configurable:!0}),n.prototype.writeValue=function(n){this.value=n;var t=this._getOptionId(n);null==t&&this._renderer.setProperty(this._elementRef.nativeElement,"selectedIndex",-1);var e=jf(t,n);this._renderer.setProperty(this._elementRef.nativeElement,"value",e)},n.prototype.registerOnChange=function(n){var t=this;this.onChange=function(e){t.value=t._getOptionValue(e),n(t.value)}},n.prototype.registerOnTouched=function(n){this.onTouched=n},n.prototype.setDisabledState=function(n){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",n)},n.prototype._registerOption=function(){return(this._idCounter++).toString()},n.prototype._getOptionId=function(n){var t,e;try{for(var r=s(Array.from(this._optionMap.keys())),o=r.next();!o.done;o=r.next()){var i=o.value;if(this._compareWith(this._optionMap.get(i),n))return i}}catch(l){t={error:l}}finally{try{o&&!o.done&&(e=r.return)&&e.call(r)}finally{if(t)throw t.error}}return null},n.prototype._getOptionValue=function(n){var t=function(n){return n.split(":")[0]}(n);return this._optionMap.has(t)?this._optionMap.get(t):n},n}(),zf=function(){function n(n,t,e){this._element=n,this._renderer=t,this._select=e,this._select&&(this.id=this._select._registerOption())}return Object.defineProperty(n.prototype,"ngValue",{set:function(n){null!=this._select&&(this._select._optionMap.set(this.id,n),this._setElementValue(jf(this.id,n)),this._select.writeValue(this._select.value))},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"value",{set:function(n){this._setElementValue(n),this._select&&this._select.writeValue(this._select.value)},enumerable:!0,configurable:!0}),n.prototype._setElementValue=function(n){this._renderer.setProperty(this._element.nativeElement,"value",n)},n.prototype.ngOnDestroy=function(){this._select&&(this._select._optionMap.delete(this.id),this._select.writeValue(this._select.value))},n}();function Ff(n,t){return null==n?""+t:("string"==typeof t&&(t="'"+t+"'"),t&&"object"==typeof t&&(t="Object"),(n+": "+t).slice(0,50))}var Bf=function(){function n(n,t){this._renderer=n,this._elementRef=t,this._optionMap=new Map,this._idCounter=0,this.onChange=function(n){},this.onTouched=function(){},this._compareWith=Nn}return Object.defineProperty(n.prototype,"compareWith",{set:function(n){if("function"!=typeof n)throw new Error("compareWith must be a function, but received "+JSON.stringify(n));this._compareWith=n},enumerable:!0,configurable:!0}),n.prototype.writeValue=function(n){var t,e=this;if(this.value=n,Array.isArray(n)){var r=n.map(function(n){return e._getOptionId(n)});t=function(n,t){n._setSelected(r.indexOf(t.toString())>-1)}}else t=function(n,t){n._setSelected(!1)};this._optionMap.forEach(t)},n.prototype.registerOnChange=function(n){var t=this;this.onChange=function(e){var r=[];if(e.hasOwnProperty("selectedOptions"))for(var o=e.selectedOptions,i=0;i1?"path: '"+n.path.join(" -> ")+"'":n.path[0]?"name: '"+n.path+"'":"unspecified name attribute",new Error(t+" "+e)}function Qf(n){return null!=n?_f.compose(n.map(Af)):null}function Wf(n){return null!=n?_f.composeAsync(n.map(Tf)):null}var Kf=[Of,Vf,If,Lf,Bf,Mf],Jf=function(n){function t(){return null!==n&&n.apply(this,arguments)||this}return o(t,n),t.prototype.ngOnInit=function(){this._checkParentType(),this.formDirective.addFormGroup(this)},t.prototype.ngOnDestroy=function(){this.formDirective&&this.formDirective.removeFormGroup(this)},Object.defineProperty(t.prototype,"control",{get:function(){return this.formDirective.getFormGroup(this)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"path",{get:function(){return Gf(this.name,this._parent)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"formDirective",{get:function(){return this._parent?this._parent.formDirective:null},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"validator",{get:function(){return Qf(this._validators)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"asyncValidator",{get:function(){return Wf(this._asyncValidators)},enumerable:!0,configurable:!0}),t.prototype._checkParentType=function(){},t}(yf),Yf=function(n){function t(t){return n.call(this,t)||this}return o(t,n),t}(function(){function n(n){this._cd=n}return Object.defineProperty(n.prototype,"ngClassUntouched",{get:function(){return!!this._cd.control&&this._cd.control.untouched},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassTouched",{get:function(){return!!this._cd.control&&this._cd.control.touched},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassPristine",{get:function(){return!!this._cd.control&&this._cd.control.pristine},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassDirty",{get:function(){return!!this._cd.control&&this._cd.control.dirty},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassValid",{get:function(){return!!this._cd.control&&this._cd.control.valid},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassInvalid",{get:function(){return!!this._cd.control&&this._cd.control.invalid},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassPending",{get:function(){return!!this._cd.control&&this._cd.control.pending},enumerable:!0,configurable:!0}),n}());function Xf(n){var t=th(n)?n.validators:n;return Array.isArray(t)?Qf(t):t||null}function nh(n,t){var e=th(t)?t.asyncValidators:n;return Array.isArray(e)?Wf(e):e||null}function th(n){return null!=n&&!Array.isArray(n)&&"object"==typeof n}var eh=function(){function n(n,t){this.validator=n,this.asyncValidator=t,this._onCollectionChange=function(){},this.pristine=!0,this.touched=!1,this._onDisabledChange=[]}return Object.defineProperty(n.prototype,"parent",{get:function(){return this._parent},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"valid",{get:function(){return"VALID"===this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"invalid",{get:function(){return"INVALID"===this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"pending",{get:function(){return"PENDING"==this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"disabled",{get:function(){return"DISABLED"===this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"enabled",{get:function(){return"DISABLED"!==this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"dirty",{get:function(){return!this.pristine},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"untouched",{get:function(){return!this.touched},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"updateOn",{get:function(){return this._updateOn?this._updateOn:this.parent?this.parent.updateOn:"change"},enumerable:!0,configurable:!0}),n.prototype.setValidators=function(n){this.validator=Xf(n)},n.prototype.setAsyncValidators=function(n){this.asyncValidator=nh(n)},n.prototype.clearValidators=function(){this.validator=null},n.prototype.clearAsyncValidators=function(){this.asyncValidator=null},n.prototype.markAsTouched=function(n){void 0===n&&(n={}),this.touched=!0,this._parent&&!n.onlySelf&&this._parent.markAsTouched(n)},n.prototype.markAsUntouched=function(n){void 0===n&&(n={}),this.touched=!1,this._pendingTouched=!1,this._forEachChild(function(n){n.markAsUntouched({onlySelf:!0})}),this._parent&&!n.onlySelf&&this._parent._updateTouched(n)},n.prototype.markAsDirty=function(n){void 0===n&&(n={}),this.pristine=!1,this._parent&&!n.onlySelf&&this._parent.markAsDirty(n)},n.prototype.markAsPristine=function(n){void 0===n&&(n={}),this.pristine=!0,this._pendingDirty=!1,this._forEachChild(function(n){n.markAsPristine({onlySelf:!0})}),this._parent&&!n.onlySelf&&this._parent._updatePristine(n)},n.prototype.markAsPending=function(n){void 0===n&&(n={}),this.status="PENDING",!1!==n.emitEvent&&this.statusChanges.emit(this.status),this._parent&&!n.onlySelf&&this._parent.markAsPending(n)},n.prototype.disable=function(n){void 0===n&&(n={}),this.status="DISABLED",this.errors=null,this._forEachChild(function(t){t.disable(i({},n,{onlySelf:!0}))}),this._updateValue(),!1!==n.emitEvent&&(this.valueChanges.emit(this.value),this.statusChanges.emit(this.status)),this._updateAncestors(n),this._onDisabledChange.forEach(function(n){return n(!0)})},n.prototype.enable=function(n){void 0===n&&(n={}),this.status="VALID",this._forEachChild(function(t){t.enable(i({},n,{onlySelf:!0}))}),this.updateValueAndValidity({onlySelf:!0,emitEvent:n.emitEvent}),this._updateAncestors(n),this._onDisabledChange.forEach(function(n){return n(!1)})},n.prototype._updateAncestors=function(n){this._parent&&!n.onlySelf&&(this._parent.updateValueAndValidity(n),this._parent._updatePristine(),this._parent._updateTouched())},n.prototype.setParent=function(n){this._parent=n},n.prototype.updateValueAndValidity=function(n){void 0===n&&(n={}),this._setInitialStatus(),this._updateValue(),this.enabled&&(this._cancelExistingSubscription(),this.errors=this._runValidator(),this.status=this._calculateStatus(),"VALID"!==this.status&&"PENDING"!==this.status||this._runAsyncValidator(n.emitEvent)),!1!==n.emitEvent&&(this.valueChanges.emit(this.value),this.statusChanges.emit(this.status)),this._parent&&!n.onlySelf&&this._parent.updateValueAndValidity(n)},n.prototype._updateTreeValidity=function(n){void 0===n&&(n={emitEvent:!0}),this._forEachChild(function(t){return t._updateTreeValidity(n)}),this.updateValueAndValidity({onlySelf:!0,emitEvent:n.emitEvent})},n.prototype._setInitialStatus=function(){this.status=this._allControlsDisabled()?"DISABLED":"VALID"},n.prototype._runValidator=function(){return this.validator?this.validator(this):null},n.prototype._runAsyncValidator=function(n){var t=this;if(this.asyncValidator){this.status="PENDING";var e=Cf(this.asyncValidator(this));this._asyncValidationSubscription=e.subscribe(function(e){return t.setErrors(e,{emitEvent:n})})}},n.prototype._cancelExistingSubscription=function(){this._asyncValidationSubscription&&this._asyncValidationSubscription.unsubscribe()},n.prototype.setErrors=function(n,t){void 0===t&&(t={}),this.errors=n,this._updateControlsErrors(!1!==t.emitEvent)},n.prototype.get=function(n){return function(n,t,e){return null==t?null:(t instanceof Array||(t=t.split(".")),t instanceof Array&&0===t.length?null:t.reduce(function(n,t){return n instanceof oh?n.controls.hasOwnProperty(t)?n.controls[t]:null:n instanceof ih&&n.at(t)||null},n))}(this,n)},n.prototype.getError=function(n,t){var e=t?this.get(t):this;return e&&e.errors?e.errors[n]:null},n.prototype.hasError=function(n,t){return!!this.getError(n,t)},Object.defineProperty(n.prototype,"root",{get:function(){for(var n=this;n._parent;)n=n._parent;return n},enumerable:!0,configurable:!0}),n.prototype._updateControlsErrors=function(n){this.status=this._calculateStatus(),n&&this.statusChanges.emit(this.status),this._parent&&this._parent._updateControlsErrors(n)},n.prototype._initObservables=function(){this.valueChanges=new Ho,this.statusChanges=new Ho},n.prototype._calculateStatus=function(){return this._allControlsDisabled()?"DISABLED":this.errors?"INVALID":this._anyControlsHaveStatus("PENDING")?"PENDING":this._anyControlsHaveStatus("INVALID")?"INVALID":"VALID"},n.prototype._anyControlsHaveStatus=function(n){return this._anyControls(function(t){return t.status===n})},n.prototype._anyControlsDirty=function(){return this._anyControls(function(n){return n.dirty})},n.prototype._anyControlsTouched=function(){return this._anyControls(function(n){return n.touched})},n.prototype._updatePristine=function(n){void 0===n&&(n={}),this.pristine=!this._anyControlsDirty(),this._parent&&!n.onlySelf&&this._parent._updatePristine(n)},n.prototype._updateTouched=function(n){void 0===n&&(n={}),this.touched=this._anyControlsTouched(),this._parent&&!n.onlySelf&&this._parent._updateTouched(n)},n.prototype._isBoxedValue=function(n){return"object"==typeof n&&null!==n&&2===Object.keys(n).length&&"value"in n&&"disabled"in n},n.prototype._registerOnCollectionChange=function(n){this._onCollectionChange=n},n.prototype._setUpdateStrategy=function(n){th(n)&&null!=n.updateOn&&(this._updateOn=n.updateOn)},n}(),rh=function(n){function t(t,e,r){void 0===t&&(t=null);var o=n.call(this,Xf(e),nh(r,e))||this;return o._onChange=[],o._applyFormState(t),o._setUpdateStrategy(e),o.updateValueAndValidity({onlySelf:!0,emitEvent:!1}),o._initObservables(),o}return o(t,n),t.prototype.setValue=function(n,t){var e=this;void 0===t&&(t={}),this.value=this._pendingValue=n,this._onChange.length&&!1!==t.emitModelToViewChange&&this._onChange.forEach(function(n){return n(e.value,!1!==t.emitViewToModelChange)}),this.updateValueAndValidity(t)},t.prototype.patchValue=function(n,t){void 0===t&&(t={}),this.setValue(n,t)},t.prototype.reset=function(n,t){void 0===n&&(n=null),void 0===t&&(t={}),this._applyFormState(n),this.markAsPristine(t),this.markAsUntouched(t),this.setValue(this.value,t),this._pendingChange=!1},t.prototype._updateValue=function(){},t.prototype._anyControls=function(n){return!1},t.prototype._allControlsDisabled=function(){return this.disabled},t.prototype.registerOnChange=function(n){this._onChange.push(n)},t.prototype._clearChangeFns=function(){this._onChange=[],this._onDisabledChange=[],this._onCollectionChange=function(){}},t.prototype.registerOnDisabledChange=function(n){this._onDisabledChange.push(n)},t.prototype._forEachChild=function(n){},t.prototype._syncPendingControls=function(){return!("submit"!==this.updateOn||(this._pendingDirty&&this.markAsDirty(),this._pendingTouched&&this.markAsTouched(),!this._pendingChange)||(this.setValue(this._pendingValue,{onlySelf:!0,emitModelToViewChange:!1}),0))},t.prototype._applyFormState=function(n){this._isBoxedValue(n)?(this.value=this._pendingValue=n.value,n.disabled?this.disable({onlySelf:!0,emitEvent:!1}):this.enable({onlySelf:!0,emitEvent:!1})):this.value=this._pendingValue=n},t}(eh),oh=function(n){function t(t,e,r){var o=n.call(this,Xf(e),nh(r,e))||this;return o.controls=t,o._initObservables(),o._setUpdateStrategy(e),o._setUpControls(),o.updateValueAndValidity({onlySelf:!0,emitEvent:!1}),o}return o(t,n),t.prototype.registerControl=function(n,t){return this.controls[n]?this.controls[n]:(this.controls[n]=t,t.setParent(this),t._registerOnCollectionChange(this._onCollectionChange),t)},t.prototype.addControl=function(n,t){this.registerControl(n,t),this.updateValueAndValidity(),this._onCollectionChange()},t.prototype.removeControl=function(n){this.controls[n]&&this.controls[n]._registerOnCollectionChange(function(){}),delete this.controls[n],this.updateValueAndValidity(),this._onCollectionChange()},t.prototype.setControl=function(n,t){this.controls[n]&&this.controls[n]._registerOnCollectionChange(function(){}),delete this.controls[n],t&&this.registerControl(n,t),this.updateValueAndValidity(),this._onCollectionChange()},t.prototype.contains=function(n){return this.controls.hasOwnProperty(n)&&this.controls[n].enabled},t.prototype.setValue=function(n,t){var e=this;void 0===t&&(t={}),this._checkAllValuesPresent(n),Object.keys(n).forEach(function(r){e._throwIfControlMissing(r),e.controls[r].setValue(n[r],{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)},t.prototype.patchValue=function(n,t){var e=this;void 0===t&&(t={}),Object.keys(n).forEach(function(r){e.controls[r]&&e.controls[r].patchValue(n[r],{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)},t.prototype.reset=function(n,t){void 0===n&&(n={}),void 0===t&&(t={}),this._forEachChild(function(e,r){e.reset(n[r],{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t),this._updatePristine(t),this._updateTouched(t)},t.prototype.getRawValue=function(){return this._reduceChildren({},function(n,t,e){return n[e]=t instanceof rh?t.value:t.getRawValue(),n})},t.prototype._syncPendingControls=function(){var n=this._reduceChildren(!1,function(n,t){return!!t._syncPendingControls()||n});return n&&this.updateValueAndValidity({onlySelf:!0}),n},t.prototype._throwIfControlMissing=function(n){if(!Object.keys(this.controls).length)throw new Error("\n There are no form controls registered with this group yet. If you're using ngModel,\n you may want to check next tick (e.g. use setTimeout).\n ");if(!this.controls[n])throw new Error("Cannot find form control with name: "+n+".")},t.prototype._forEachChild=function(n){var t=this;Object.keys(this.controls).forEach(function(e){return n(t.controls[e],e)})},t.prototype._setUpControls=function(){var n=this;this._forEachChild(function(t){t.setParent(n),t._registerOnCollectionChange(n._onCollectionChange)})},t.prototype._updateValue=function(){this.value=this._reduceValue()},t.prototype._anyControls=function(n){var t=this,e=!1;return this._forEachChild(function(r,o){e=e||t.contains(o)&&n(r)}),e},t.prototype._reduceValue=function(){var n=this;return this._reduceChildren({},function(t,e,r){return(e.enabled||n.disabled)&&(t[r]=e.value),t})},t.prototype._reduceChildren=function(n,t){var e=n;return this._forEachChild(function(n,r){e=t(e,n,r)}),e},t.prototype._allControlsDisabled=function(){var n,t;try{for(var e=s(Object.keys(this.controls)),r=e.next();!r.done;r=e.next())if(this.controls[r.value].enabled)return!1}catch(o){n={error:o}}finally{try{r&&!r.done&&(t=e.return)&&t.call(e)}finally{if(n)throw n.error}}return Object.keys(this.controls).length>0||this.disabled},t.prototype._checkAllValuesPresent=function(n){this._forEachChild(function(t,e){if(void 0===n[e])throw new Error("Must supply a value for form control with name: '"+e+"'.")})},t}(eh),ih=function(n){function t(t,e,r){var o=n.call(this,Xf(e),nh(r,e))||this;return o.controls=t,o._initObservables(),o._setUpdateStrategy(e),o._setUpControls(),o.updateValueAndValidity({onlySelf:!0,emitEvent:!1}),o}return o(t,n),t.prototype.at=function(n){return this.controls[n]},t.prototype.push=function(n){this.controls.push(n),this._registerControl(n),this.updateValueAndValidity(),this._onCollectionChange()},t.prototype.insert=function(n,t){this.controls.splice(n,0,t),this._registerControl(t),this.updateValueAndValidity()},t.prototype.removeAt=function(n){this.controls[n]&&this.controls[n]._registerOnCollectionChange(function(){}),this.controls.splice(n,1),this.updateValueAndValidity()},t.prototype.setControl=function(n,t){this.controls[n]&&this.controls[n]._registerOnCollectionChange(function(){}),this.controls.splice(n,1),t&&(this.controls.splice(n,0,t),this._registerControl(t)),this.updateValueAndValidity(),this._onCollectionChange()},Object.defineProperty(t.prototype,"length",{get:function(){return this.controls.length},enumerable:!0,configurable:!0}),t.prototype.setValue=function(n,t){var e=this;void 0===t&&(t={}),this._checkAllValuesPresent(n),n.forEach(function(n,r){e._throwIfControlMissing(r),e.at(r).setValue(n,{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)},t.prototype.patchValue=function(n,t){var e=this;void 0===t&&(t={}),n.forEach(function(n,r){e.at(r)&&e.at(r).patchValue(n,{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)},t.prototype.reset=function(n,t){void 0===n&&(n=[]),void 0===t&&(t={}),this._forEachChild(function(e,r){e.reset(n[r],{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t),this._updatePristine(t),this._updateTouched(t)},t.prototype.getRawValue=function(){return this.controls.map(function(n){return n instanceof rh?n.value:n.getRawValue()})},t.prototype._syncPendingControls=function(){var n=this.controls.reduce(function(n,t){return!!t._syncPendingControls()||n},!1);return n&&this.updateValueAndValidity({onlySelf:!0}),n},t.prototype._throwIfControlMissing=function(n){if(!this.controls.length)throw new Error("\n There are no form controls registered with this array yet. If you're using ngModel,\n you may want to check next tick (e.g. use setTimeout).\n ");if(!this.at(n))throw new Error("Cannot find form control at index "+n)},t.prototype._forEachChild=function(n){this.controls.forEach(function(t,e){n(t,e)})},t.prototype._updateValue=function(){var n=this;this.value=this.controls.filter(function(t){return t.enabled||n.disabled}).map(function(n){return n.value})},t.prototype._anyControls=function(n){return this.controls.some(function(t){return t.enabled&&n(t)})},t.prototype._setUpControls=function(){var n=this;this._forEachChild(function(t){return n._registerControl(t)})},t.prototype._checkAllValuesPresent=function(n){this._forEachChild(function(t,e){if(void 0===n[e])throw new Error("Must supply a value for form control at index: "+e+".")})},t.prototype._allControlsDisabled=function(){var n,t;try{for(var e=s(this.controls),r=e.next();!r.done;r=e.next())if(r.value.enabled)return!1}catch(o){n={error:o}}finally{try{r&&!r.done&&(t=e.return)&&t.call(e)}finally{if(n)throw n.error}}return this.controls.length>0||this.disabled},t.prototype._registerControl=function(n){n.setParent(this),n._registerOnCollectionChange(this._onCollectionChange)},t}(eh),lh=Promise.resolve(null),uh=function(n){function t(t,e){var r=n.call(this)||this;return r.submitted=!1,r._directives=[],r.ngSubmit=new Ho,r.form=new oh({},Qf(t),Wf(e)),r}return o(t,n),t.prototype.ngAfterViewInit=function(){this._setUpdateStrategy()},Object.defineProperty(t.prototype,"formDirective",{get:function(){return this},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"control",{get:function(){return this.form},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"path",{get:function(){return[]},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"controls",{get:function(){return this.form.controls},enumerable:!0,configurable:!0}),t.prototype.addControl=function(n){var t=this;lh.then(function(){var e=t._findContainer(n.path);n.control=e.registerControl(n.name,n.control),Zf(n.control,n),n.control.updateValueAndValidity({emitEvent:!1}),t._directives.push(n)})},t.prototype.getControl=function(n){return this.form.get(n.path)},t.prototype.removeControl=function(n){var t=this;lh.then(function(){var e,r,o=t._findContainer(n.path);o&&o.removeControl(n.name),(r=(e=t._directives).indexOf(n))>-1&&e.splice(r,1)})},t.prototype.addFormGroup=function(n){var t=this;lh.then(function(){var e=t._findContainer(n.path),r=new oh({});(function(n,t){null==n&&$f(t,"Cannot find control with"),n.validator=_f.compose([n.validator,t.validator]),n.asyncValidator=_f.composeAsync([n.asyncValidator,t.asyncValidator])})(r,n),e.registerControl(n.name,r),r.updateValueAndValidity({emitEvent:!1})})},t.prototype.removeFormGroup=function(n){var t=this;lh.then(function(){var e=t._findContainer(n.path);e&&e.removeControl(n.name)})},t.prototype.getFormGroup=function(n){return this.form.get(n.path)},t.prototype.updateModel=function(n,t){var e=this;lh.then(function(){e.form.get(n.path).setValue(t)})},t.prototype.setValue=function(n){this.control.setValue(n)},t.prototype.onSubmit=function(n){return this.submitted=!0,t=this._directives,this.form._syncPendingControls(),t.forEach(function(n){var t=n.control;"submit"===t.updateOn&&t._pendingChange&&(n.viewToModelUpdate(t._pendingValue),t._pendingChange=!1)}),this.ngSubmit.emit(n),!1;var t},t.prototype.onReset=function(){this.resetForm()},t.prototype.resetForm=function(n){void 0===n&&(n=void 0),this.form.reset(n),this.submitted=!1},t.prototype._setUpdateStrategy=function(){this.options&&null!=this.options.updateOn&&(this.form._updateOn=this.options.updateOn)},t.prototype._findContainer=function(n){return n.pop(),n.length?this.form.get(n):this.form},t}(yf),sh=function(){function n(){}return n.modelParentException=function(){throw new Error('\n ngModel cannot be used to register form controls with a parent formGroup directive. Try using\n formGroup\'s partner directive "formControlName" instead. Example:\n\n \n
\n \n
\n\n In your class:\n\n this.myGroup = new FormGroup({\n firstName: new FormControl()\n });\n\n Or, if you\'d like to avoid registering this form control, indicate that it\'s standalone in ngModelOptions:\n\n Example:\n\n \n
\n \n \n
\n ')},n.formGroupNameException=function(){throw new Error("\n ngModel cannot be used to register form controls with a parent formGroupName or formArrayName directive.\n\n Option 1: Use formControlName instead of ngModel (reactive strategy):\n\n "+Hf+"\n\n Option 2: Update ngModel's parent be ngModelGroup (template-driven strategy):\n\n "+Rf)},n.missingNameException=function(){throw new Error('If ngModel is used within a form tag, either the name attribute must be set or the form\n control must be defined as \'standalone\' in ngModelOptions.\n\n Example 1: \n Example 2: ')},n.modelGroupParentException=function(){throw new Error("\n ngModelGroup cannot be used with a parent formGroup directive.\n\n Option 1: Use formGroupName instead of ngModelGroup (reactive strategy):\n\n "+Hf+"\n\n Option 2: Use a regular form tag instead of the formGroup directive (template-driven strategy):\n\n "+Rf)},n.ngFormWarning=function(){console.warn("\n It looks like you're using 'ngForm'.\n\n Support for using the 'ngForm' element selector has been deprecated in Angular v6 and will be removed\n in Angular v9.\n\n Use 'ng-form' instead.\n\n Before:\n \n\n After:\n \n ")},n}(),ah=new wn("NgFormSelectorWarning"),ch=function(n){function t(t,e,r){var o=n.call(this)||this;return o._parent=t,o._validators=e,o._asyncValidators=r,o}var e;return o(t,n),e=t,t.prototype._checkParentType=function(){this._parent instanceof e||this._parent instanceof uh||sh.modelGroupParentException()},t}(Jf),fh=Promise.resolve(null),hh=function(n){function t(t,e,r,o){var i=n.call(this)||this;return i.control=new rh,i._registered=!1,i.update=new Ho,i._parent=t,i._rawValidators=e||[],i._rawAsyncValidators=r||[],i.valueAccessor=function(n,t){if(!t)return null;Array.isArray(t)||$f(n,"Value accessor was not provided as an array for form control with");var e=void 0,r=void 0,o=void 0;return t.forEach(function(t){var i;t.constructor===Sf?e=t:(i=t,Kf.some(function(n){return i.constructor===n})?(r&&$f(n,"More than one built-in value accessor matches form control with"),r=t):(o&&$f(n,"More than one custom value accessor matches form control with"),o=t))}),o||r||e||($f(n,"No valid value accessor for form control with"),null)}(i,o),i}return o(t,n),t.prototype.ngOnChanges=function(n){this._checkForErrors(),this._registered||this._setUpControl(),"isDisabled"in n&&this._updateDisabled(n),function(n,t){if(!n.hasOwnProperty("model"))return!1;var e=n.model;return!!e.isFirstChange()||!Nn(t,e.currentValue)}(n,this.viewModel)&&(this._updateValue(this.model),this.viewModel=this.model)},t.prototype.ngOnDestroy=function(){this.formDirective&&this.formDirective.removeControl(this)},Object.defineProperty(t.prototype,"path",{get:function(){return this._parent?Gf(this.name,this._parent):[this.name]},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"formDirective",{get:function(){return this._parent?this._parent.formDirective:null},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"validator",{get:function(){return Qf(this._rawValidators)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"asyncValidator",{get:function(){return Wf(this._rawAsyncValidators)},enumerable:!0,configurable:!0}),t.prototype.viewToModelUpdate=function(n){this.viewModel=n,this.update.emit(n)},t.prototype._setUpControl=function(){this._setUpdateStrategy(),this._isStandalone()?this._setUpStandalone():this.formDirective.addControl(this),this._registered=!0},t.prototype._setUpdateStrategy=function(){this.options&&null!=this.options.updateOn&&(this.control._updateOn=this.options.updateOn)},t.prototype._isStandalone=function(){return!this._parent||!(!this.options||!this.options.standalone)},t.prototype._setUpStandalone=function(){Zf(this.control,this),this.control.updateValueAndValidity({emitEvent:!1})},t.prototype._checkForErrors=function(){this._isStandalone()||this._checkParentType(),this._checkName()},t.prototype._checkParentType=function(){!(this._parent instanceof ch)&&this._parent instanceof Jf?sh.formGroupNameException():this._parent instanceof ch||this._parent instanceof uh||sh.modelParentException()},t.prototype._checkName=function(){this.options&&this.options.name&&(this.name=this.options.name),this._isStandalone()||this.name||sh.missingNameException()},t.prototype._updateValue=function(n){var t=this;fh.then(function(){t.control.setValue(n,{emitViewToModelChange:!1})})},t.prototype._updateDisabled=function(n){var t=this,e=n.isDisabled.currentValue,r=""===e||e&&"false"!==e;fh.then(function(){r&&!t.control.disabled?t.control.disable():!r&&t.control.disabled&&t.control.enable()})},t}(Nf),ph=function(){return function(){}}(),dh=function(){function n(){}var t;return t=n,n.withConfig=function(n){return{ngModule:t,providers:[{provide:ah,useValue:n.warnOnDeprecatedNgFormSelector}]}},n}(),gh=Pl({encapsulation:2,styles:[],data:{}});function vh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,["",""]))],function(n,t){n(t,1,0,t.context.$implicit),n(t,2,0,t.context.$implicit)},function(n,t){n(t,3,0,t.context.$implicit)})}function yh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","20"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(-1,null,["20"]))],function(n,t){n(t,1,0,"20"),n(t,2,0,"20")},null)}function mh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","50"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(-1,null,["50"]))],function(n,t){n(t,1,0,"50"),n(t,2,0,"50")},null)}function bh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","100"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(-1,null,["100"]))],function(n,t){n(t,1,0,"100"),n(t,2,0,"100")},null)}function _h(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,["",""]))],function(n,t){var e=t.component;n(t,1,0,e.totalNumberOfRiskHotspots),n(t,2,0,e.totalNumberOfRiskHotspots)},function(n,t){n(t,3,0,t.component.translations.all)})}function wh(n){return bs(0,[(n()(),lu(0,0,null,null,17,"select",[],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"change"],[null,"blur"]],function(n,t,e){var r=!0,o=n.component;return"change"===t&&(r=!1!==Vu(n,1).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,1).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.numberOfRiskHotspots=e)&&r),r},null,null)),Qu(1,16384,null,0,Lf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n){return[n]},[Lf]),Qu(3,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(5,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(6,0,null,null,3,"option",[["value","10"]],null,null,null,null,null)),Qu(7,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(8,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(-1,null,["10"])),(n()(),iu(16777216,null,null,1,null,yh)),Qu(11,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,mh)),Qu(13,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,bh)),Qu(15,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,_h)),Qu(17,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component;n(t,3,0,e.settings.numberOfRiskHotspots),n(t,7,0,"10"),n(t,8,0,"10"),n(t,11,0,e.totalNumberOfRiskHotspots>10),n(t,13,0,e.totalNumberOfRiskHotspots>20),n(t,15,0,e.totalNumberOfRiskHotspots>50),n(t,17,0,e.totalNumberOfRiskHotspots>100)},function(n,t){n(t,0,0,Vu(t,5).ngClassUntouched,Vu(t,5).ngClassTouched,Vu(t,5).ngClassPristine,Vu(t,5).ngClassDirty,Vu(t,5).ngClassValid,Vu(t,5).ngClassInvalid,Vu(t,5).ngClassPending)})}function Ch(n){return bs(0,[(n()(),lu(0,0,null,null,0,"col",[["class","column105"]],null,null,null,null,null))],null,null)}function Eh(n){return bs(0,[(n()(),lu(0,0,null,null,7,"th",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting(""+n.context.index,e)&&r),r},null,null)),(n()(),lu(2,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(3,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(4,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(5,null,["",""])),(n()(),lu(6,0,null,null,1,"a",[],[[8,"href",4]],null,null,null,null)),(n()(),lu(7,0,null,null,0,"i",[["class","icon-info-circled"]],null,null,null,null,null))],function(n,t){var e=t.component,r=n(t,4,0,e.settings.sortBy===""+t.context.index&&"desc"===e.settings.sortOrder,e.settings.sortBy===""+t.context.index&&"asc"===e.settings.sortOrder,e.settings.sortBy!==""+t.context.index);n(t,3,0,"icon-down-dir",r)},function(n,t){n(t,5,0,t.context.$implicit.name),n(t,6,0,ru(1,"",t.context.$implicit.explanationUrl,""))})}function xh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"td",[["class","right"]],null,null,null,null,null)),Qu(1,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(2,{lightred:0,lightgreen:1}),(n()(),vs(3,null,["",""]))],function(n,t){var e=n(t,2,0,t.context.$implicit.exceeded,!t.context.$implicit.exceeded);n(t,1,0,"right",e)},function(n,t){n(t,3,0,t.context.$implicit.value)})}function Oh(n){return bs(0,[(n()(),lu(0,0,null,null,10,"tr",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,1,"td",[],null,null,null,null,null)),(n()(),vs(2,null,["",""])),(n()(),lu(3,0,null,null,2,"td",[],null,null,null,null,null)),(n()(),lu(4,0,null,null,1,"a",[],[[8,"href",4]],null,null,null,null)),(n()(),vs(5,null,["",""])),(n()(),lu(6,0,null,null,2,"td",[],[[8,"title",0]],null,null,null,null)),(n()(),lu(7,0,null,null,1,"a",[],[[8,"href",4]],null,null,null,null)),(n()(),vs(8,null,[" "," "])),(n()(),iu(16777216,null,null,1,null,xh)),Qu(10,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null)],function(n,t){n(t,10,0,t.context.$implicit.metrics)},function(n,t){n(t,2,0,t.context.$implicit.assembly),n(t,4,0,t.context.$implicit.reportPath),n(t,5,0,t.context.$implicit.class),n(t,6,0,t.context.$implicit.methodName),n(t,7,0,t.context.$implicit.reportPath+"#file"+t.context.$implicit.fileIndex+"_line"+t.context.$implicit.line),n(t,8,0,t.context.$implicit.methodShortName)})}function kh(n){return bs(0,[(n()(),lu(0,0,null,null,62,"div",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,28,"div",[["class","customizebox"]],null,null,null,null,null)),(n()(),lu(2,0,null,null,12,"div",[],null,null,null,null,null)),(n()(),lu(3,0,null,null,11,"select",[["name","assembly"]],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"change"],[null,"blur"]],function(n,t,e){var r=!0,o=n.component;return"change"===t&&(r=!1!==Vu(n,4).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,4).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.assembly=e)&&r),"ngModelChange"===t&&(r=!1!==o.updateRiskHotpots()&&r),r},null,null)),Qu(4,16384,null,0,Lf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n){return[n]},[Lf]),Qu(6,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{name:[0,"name"],model:[1,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(8,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(9,0,null,null,3,"option",[["value",""]],null,null,null,null,null)),Qu(10,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(11,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(12,null,["",""])),(n()(),iu(16777216,null,null,1,null,vh)),Qu(14,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),lu(15,0,null,null,4,"div",[["class","center"]],null,null,null,null,null)),(n()(),lu(16,0,null,null,1,"span",[],null,null,null,null,null)),(n()(),vs(17,null,["",""])),(n()(),iu(16777216,null,null,1,null,wh)),Qu(19,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(20,0,null,null,0,"div",[["class","center"]],null,null,null,null,null)),(n()(),lu(21,0,null,null,8,"div",[["class","right"]],null,null,null,null,null)),(n()(),lu(22,0,null,null,1,"span",[],null,null,null,null,null)),(n()(),vs(23,null,[""," "])),(n()(),lu(24,0,null,null,5,"input",[["type","text"]],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"input"],[null,"blur"],[null,"compositionstart"],[null,"compositionend"]],function(n,t,e){var r=!0,o=n.component;return"input"===t&&(r=!1!==Vu(n,25)._handleInput(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,25).onTouched()&&r),"compositionstart"===t&&(r=!1!==Vu(n,25)._compositionStart()&&r),"compositionend"===t&&(r=!1!==Vu(n,25)._compositionEnd(e.target.value)&&r),"ngModelChange"===t&&(r=!1!==(o.settings.filter=e)&&r),"ngModelChange"===t&&(r=!1!==o.updateRiskHotpots()&&r),r},null,null)),Qu(25,16384,null,0,Sf,[Xr,Qr,[2,kf]],null,null),Wu(1024,null,xf,function(n){return[n]},[Sf]),Qu(27,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(29,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(30,0,null,null,32,"table",[["class","overview table-fixed stripped"]],null,null,null,null,null)),(n()(),lu(31,0,null,null,5,"colgroup",[],null,null,null,null,null)),(n()(),lu(32,0,null,null,0,"col",[],null,null,null,null,null)),(n()(),lu(33,0,null,null,0,"col",[],null,null,null,null,null)),(n()(),lu(34,0,null,null,0,"col",[],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Ch)),Qu(36,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),lu(37,0,null,null,21,"thead",[],null,null,null,null,null)),(n()(),lu(38,0,null,null,20,"tr",[],null,null,null,null,null)),(n()(),lu(39,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(40,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("assembly",e)&&r),r},null,null)),(n()(),lu(41,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(42,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(43,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(44,null,["",""])),(n()(),lu(45,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(46,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("class",e)&&r),r},null,null)),(n()(),lu(47,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(48,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(49,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(50,null,["",""])),(n()(),lu(51,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(52,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("method",e)&&r),r},null,null)),(n()(),lu(53,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(54,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(55,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(56,null,["",""])),(n()(),iu(16777216,null,null,1,null,Eh)),Qu(58,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),lu(59,0,null,null,3,"tbody",[],null,null,null,null,null)),(n()(),iu(16777216,null,null,2,null,Oh)),Qu(61,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(t=0,e=ec,r=[],Ku(-1,t|=16,null,0,e,e,r))],function(n,t){var e=t.component;n(t,6,0,"assembly",e.settings.assembly),n(t,10,0,""),n(t,11,0,""),n(t,14,0,e.assemblies),n(t,19,0,e.totalNumberOfRiskHotspots>10),n(t,27,0,e.settings.filter),n(t,36,0,e.riskHotspotMetrics);var r=n(t,43,0,"assembly"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"assembly"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"assembly"!==e.settings.sortBy);n(t,42,0,"icon-down-dir",r);var o=n(t,49,0,"class"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"class"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"class"!==e.settings.sortBy);n(t,48,0,"icon-down-dir",o);var i=n(t,55,0,"method"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"method"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"method"!==e.settings.sortBy);n(t,54,0,"icon-down-dir",i),n(t,58,0,e.riskHotspotMetrics),n(t,61,0,function(n,t,e,r){if(_t.isWrapped(r)){r=_t.unwrap(r);var o=n.def.nodes[61].bindingIndex+0,i=_t.unwrap(n.oldValues[o]);n.oldValues[o]=new _t(i)}return r}(t,0,0,Vu(t,62).transform(e.riskHotspots,0,e.settings.numberOfRiskHotspots)))},function(n,t){var e=t.component;n(t,3,0,Vu(t,8).ngClassUntouched,Vu(t,8).ngClassTouched,Vu(t,8).ngClassPristine,Vu(t,8).ngClassDirty,Vu(t,8).ngClassValid,Vu(t,8).ngClassInvalid,Vu(t,8).ngClassPending),n(t,12,0,e.translations.assembly),n(t,17,0,e.translations.top),n(t,23,0,e.translations.filter),n(t,24,0,Vu(t,29).ngClassUntouched,Vu(t,29).ngClassTouched,Vu(t,29).ngClassPristine,Vu(t,29).ngClassDirty,Vu(t,29).ngClassValid,Vu(t,29).ngClassInvalid,Vu(t,29).ngClassPending),n(t,44,0,e.translations.assembly),n(t,50,0,e.translations.class),n(t,56,0,e.translations.method)});var t,e,r}function Sh(n){return bs(0,[(n()(),iu(16777216,null,null,1,null,kh)),Qu(1,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){n(t,1,0,t.component.totalNumberOfRiskHotspots>0)},null)}function Ah(n){return bs(0,[(n()(),lu(0,0,null,null,1,"risk-hotspots",[],null,[["window","beforeunload"]],function(n,t,e){var r=!0;return"window:beforeunload"===t&&(r=!1!==Vu(n,1).onDonBeforeUnlodad()&&r),r},Sh,gh)),Qu(1,114688,null,0,Na,[Ta],null,null)],function(n,t){n(t,1,0)},null)}var Th=xu("risk-hotspots",Na,Ah,{},{},[]),Ih=function(){function n(){this.grayVisible=!0,this.greenVisible=!1,this.redVisible=!1,this.greenClass="",this.redClass="",this._percentage=NaN}return Object.defineProperty(n.prototype,"percentage",{get:function(){return this._percentage},set:function(n){this._percentage=n,this.grayVisible=isNaN(n),this.greenVisible=!isNaN(n)&&Math.round(n)>0,this.redVisible=!isNaN(n)&&100-Math.round(n)>0,this.greenClass="covered"+Math.round(n),this.redClass="covered"+(100-Math.round(n))},enumerable:!0,configurable:!0}),n}(),Ph=Pl({encapsulation:2,styles:[],data:{}});function Nh(n){return bs(0,[(n()(),lu(0,0,null,null,0,"td",[["class","gray covered100"]],null,null,null,null,null))],null,null)}function Dh(n){return bs(0,[(n()(),lu(0,0,null,null,0,"td",[],[[8,"className",0]],null,null,null,null))],null,function(n,t){n(t,0,0,ru(1,"green ",t.component.greenClass,""))})}function Mh(n){return bs(0,[(n()(),lu(0,0,null,null,0,"td",[],[[8,"className",0]],null,null,null,null))],null,function(n,t){n(t,0,0,ru(1,"red ",t.component.redClass,""))})}function Vh(n){return bs(2,[(n()(),lu(0,0,null,null,6,"table",[["class","coverage"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Nh)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Dh)),Qu(4,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Mh)),Qu(6,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component;n(t,2,0,e.grayVisible),n(t,4,0,e.greenVisible),n(t,6,0,e.redVisible)},null)}var Hh=function(){return function(){this.element=null,this.collapsed=!1,this.branchCoverageAvailable=!1}}(),Rh=Pl({encapsulation:2,styles:[],data:{}});function jh(n){return bs(0,[(n()(),lu(0,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(1,null,["",""]))],null,function(n,t){n(t,1,0,t.component.element.branchCoveragePercentage)})}function Lh(n){return bs(0,[(n()(),lu(0,0,null,null,2,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(1,0,null,null,1,"coverage-bar",[],null,null,null,Vh,Ph)),Qu(2,49152,null,0,Ih,[],{percentage:[0,"percentage"]},null)],function(n,t){n(t,2,0,t.component.element.branchCoverage)},null)}function zh(n){return bs(2,[(n()(),lu(0,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.element.toggleCollapse(e)&&r),r},null,null)),(n()(),lu(2,0,null,null,2,"i",[],null,null,null,null,null)),Qu(3,278528,null,0,Wa,[ol,il,Qr,Xr],{ngClass:[0,"ngClass"]},null),gs(4,{"icon-plus":0,"icon-minus":1}),(n()(),vs(5,null,[" ",""])),(n()(),lu(6,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(7,null,["",""])),(n()(),lu(8,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(9,null,["",""])),(n()(),lu(10,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(11,null,["",""])),(n()(),lu(12,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(13,null,["",""])),(n()(),lu(14,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(15,null,["",""])),(n()(),lu(16,0,null,null,2,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(17,0,null,null,1,"coverage-bar",[],null,null,null,Vh,Ph)),Qu(18,49152,null,0,Ih,[],{percentage:[0,"percentage"]},null),(n()(),iu(16777216,null,null,1,null,jh)),Qu(20,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Lh)),Qu(22,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component,r=n(t,4,0,e.element.collapsed,!e.element.collapsed);n(t,3,0,r),n(t,18,0,e.element.coverage),n(t,20,0,e.branchCoverageAvailable),n(t,22,0,e.branchCoverageAvailable)},function(n,t){var e=t.component;n(t,5,0,e.element.name),n(t,7,0,e.element.coveredLines),n(t,9,0,e.element.uncoveredLines),n(t,11,0,e.element.coverableLines),n(t,13,0,e.element.totalLines),n(t,15,0,e.element.coveragePercentage)})}var Fh=function(){function n(){this.path=null,this._historicCoverages=[]}return Object.defineProperty(n.prototype,"historicCoverages",{get:function(){return this._historicCoverages},set:function(n){if(this._historicCoverages=n,n.length>1){for(var t="",e=0;et?"lightgreen":n1),n(t,4,0,null!==e.clazz.currentHistoricCoverage),n(t,6,0,null===e.clazz.currentHistoricCoverage)},null)}function ap(n){return bs(0,[(n()(),lu(0,0,null,null,2,"td",[["class","right"]],null,null,null,null,null)),(n()(),lu(1,0,null,null,1,"coverage-bar",[],null,null,null,Vh,Ph)),Qu(2,49152,null,0,Ih,[],{percentage:[0,"percentage"]},null)],function(n,t){n(t,2,0,t.component.clazz.branchCoverage)},null)}function cp(n){return bs(2,[(n()(),lu(0,0,null,null,4,"td",[],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,qh)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,$h)),Qu(4,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(5,0,null,null,4,"td",[["class","right"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Qh)),Qu(7,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Wh)),Qu(9,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(10,0,null,null,4,"td",[["class","right"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Kh)),Qu(12,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Jh)),Qu(14,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(15,0,null,null,4,"td",[["class","right"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Yh)),Qu(17,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Xh)),Qu(19,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(20,0,null,null,4,"td",[["class","right"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,np)),Qu(22,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,tp)),Qu(24,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(25,0,null,null,6,"td",[["class","right"]],[[8,"title",0]],null,null,null,null)),(n()(),iu(16777216,null,null,1,null,ep)),Qu(27,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,rp)),Qu(29,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,op)),Qu(31,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(32,0,null,null,2,"td",[["class","right"]],null,null,null,null,null)),(n()(),lu(33,0,null,null,1,"coverage-bar",[],null,null,null,Vh,Ph)),Qu(34,49152,null,0,Ih,[],{percentage:[0,"percentage"]},null),(n()(),iu(16777216,null,null,1,null,sp)),Qu(36,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,ap)),Qu(38,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component;n(t,2,0,""!==e.clazz.reportPath),n(t,4,0,""===e.clazz.reportPath),n(t,7,0,null!==e.clazz.currentHistoricCoverage),n(t,9,0,null===e.clazz.currentHistoricCoverage),n(t,12,0,null!==e.clazz.currentHistoricCoverage),n(t,14,0,null===e.clazz.currentHistoricCoverage),n(t,17,0,null!==e.clazz.currentHistoricCoverage),n(t,19,0,null===e.clazz.currentHistoricCoverage),n(t,22,0,null!==e.clazz.currentHistoricCoverage),n(t,24,0,null===e.clazz.currentHistoricCoverage),n(t,27,0,e.clazz.lineCoverageHistory.length>1),n(t,29,0,null!==e.clazz.currentHistoricCoverage),n(t,31,0,null===e.clazz.currentHistoricCoverage),n(t,34,0,e.clazz.coverage),n(t,36,0,e.branchCoverageAvailable),n(t,38,0,e.branchCoverageAvailable)},function(n,t){n(t,25,0,t.component.clazz.coverageType)})}var fp=Pl({encapsulation:2,styles:[],data:{}});function hp(n){return bs(0,[(n()(),lu(0,0,null,null,1,null,null,null,null,null,null,null)),(n()(),vs(1,null,["",""]))],null,function(n,t){n(t,1,0,t.component.translations.noGrouping)})}function pp(n){return bs(0,[(n()(),lu(0,0,null,null,1,null,null,null,null,null,null,null)),(n()(),vs(1,null,["",""]))],null,function(n,t){n(t,1,0,t.component.translations.byAssembly)})}function dp(n){return bs(0,[(n()(),lu(0,0,null,null,1,null,null,null,null,null,null,null)),(n()(),vs(1,null,["",""]))],null,function(n,t){var e=t.component;n(t,1,0,e.translations.byNamespace+" "+e.settings.grouping)})}function gp(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,["",""]))],function(n,t){n(t,1,0,t.context.$implicit),n(t,2,0,t.context.$implicit)},function(n,t){n(t,3,0,t.context.$implicit)})}function vp(n){return bs(0,[(n()(),lu(0,0,null,null,0,"br",[],null,null,null,null,null))],null,null)}function yp(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","branchCoverageIncreaseOnly"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,[" "," "]))],function(n,t){n(t,1,0,"branchCoverageIncreaseOnly"),n(t,2,0,"branchCoverageIncreaseOnly")},function(n,t){n(t,3,0,t.component.translations.branchCoverageIncreaseOnly)})}function mp(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","branchCoverageDecreaseOnly"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,[" "," "]))],function(n,t){n(t,1,0,"branchCoverageDecreaseOnly"),n(t,2,0,"branchCoverageDecreaseOnly")},function(n,t){n(t,3,0,t.component.translations.branchCoverageDecreaseOnly)})}function bp(n){return bs(0,[(n()(),lu(0,0,null,null,26,"div",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,25,"select",[],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"change"],[null,"blur"]],function(n,t,e){var r=!0,o=n.component;return"change"===t&&(r=!1!==Vu(n,2).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,2).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.historyComparisionType=e)&&r),r},null,null)),Qu(2,16384,null,0,Lf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n){return[n]},[Lf]),Qu(4,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(6,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(7,0,null,null,3,"option",[["value",""]],null,null,null,null,null)),Qu(8,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(9,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(10,null,["",""])),(n()(),lu(11,0,null,null,3,"option",[["value","allChanges"]],null,null,null,null,null)),Qu(12,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(13,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(14,null,["",""])),(n()(),lu(15,0,null,null,3,"option",[["value","lineCoverageIncreaseOnly"]],null,null,null,null,null)),Qu(16,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(17,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(18,null,["",""])),(n()(),lu(19,0,null,null,3,"option",[["value","lineCoverageDecreaseOnly"]],null,null,null,null,null)),Qu(20,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(21,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(22,null,["",""])),(n()(),iu(16777216,null,null,1,null,yp)),Qu(24,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,mp)),Qu(26,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component;n(t,4,0,e.settings.historyComparisionType),n(t,8,0,""),n(t,9,0,""),n(t,12,0,"allChanges"),n(t,13,0,"allChanges"),n(t,16,0,"lineCoverageIncreaseOnly"),n(t,17,0,"lineCoverageIncreaseOnly"),n(t,20,0,"lineCoverageDecreaseOnly"),n(t,21,0,"lineCoverageDecreaseOnly"),n(t,24,0,e.branchCoverageAvailable),n(t,26,0,e.branchCoverageAvailable)},function(n,t){var e=t.component;n(t,1,0,Vu(t,6).ngClassUntouched,Vu(t,6).ngClassTouched,Vu(t,6).ngClassPristine,Vu(t,6).ngClassDirty,Vu(t,6).ngClassValid,Vu(t,6).ngClassInvalid,Vu(t,6).ngClassPending),n(t,10,0,e.translations.filter),n(t,14,0,e.translations.allChanges),n(t,18,0,e.translations.lineCoverageIncreaseOnly),n(t,22,0,e.translations.lineCoverageDecreaseOnly)})}function _p(n){return bs(0,[(n()(),lu(0,0,null,null,18,null,null,null,null,null,null,null)),(n()(),lu(1,0,null,null,13,"div",[],null,null,null,null,null)),(n()(),vs(2,null,[" "," "])),(n()(),lu(3,0,null,null,11,"select",[],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"change"],[null,"blur"]],function(n,t,e){var r=!0,o=n.component;return"change"===t&&(r=!1!==Vu(n,4).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,4).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.historyComparisionDate=e)&&r),"ngModelChange"===t&&(r=!1!==o.updateCurrentHistoricCoverage()&&r),r},null,null)),Qu(4,16384,null,0,Lf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n){return[n]},[Lf]),Qu(6,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(8,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(9,0,null,null,3,"option",[["value",""]],null,null,null,null,null)),Qu(10,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(11,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(12,null,["",""])),(n()(),iu(16777216,null,null,1,null,gp)),Qu(14,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),iu(16777216,null,null,1,null,vp)),Qu(16,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,bp)),Qu(18,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component;n(t,6,0,e.settings.historyComparisionDate),n(t,10,0,""),n(t,11,0,""),n(t,14,0,e.historicCoverageExecutionTimes),n(t,16,0,""!==e.settings.historyComparisionDate),n(t,18,0,""!==e.settings.historyComparisionDate)},function(n,t){var e=t.component;n(t,2,0,e.translations.compareHistory),n(t,3,0,Vu(t,8).ngClassUntouched,Vu(t,8).ngClassTouched,Vu(t,8).ngClassPristine,Vu(t,8).ngClassDirty,Vu(t,8).ngClassValid,Vu(t,8).ngClassInvalid,Vu(t,8).ngClassPending),n(t,12,0,e.translations.date)})}function wp(n){return bs(0,[(n()(),lu(0,0,null,null,0,"col",[["class","column98"]],null,null,null,null,null))],null,null)}function Cp(n){return bs(0,[(n()(),lu(0,0,null,null,0,"col",[["class","column112"]],null,null,null,null,null))],null,null)}function Ep(n){return bs(0,[(n()(),lu(0,0,null,null,5,"th",[["class","center"],["colspan","2"]],null,null,null,null,null)),(n()(),lu(1,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("coverage",e)&&r),r},null,null)),(n()(),lu(2,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(3,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(4,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(5,null,["",""]))],function(n,t){var e=t.component,r=n(t,4,0,"coverage"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"coverage"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"coverage"!==e.settings.sortBy);n(t,3,0,"icon-down-dir",r)},function(n,t){n(t,5,0,t.component.translations.coverage)})}function xp(n){return bs(0,[(n()(),lu(0,0,null,null,5,"th",[["class","center"],["colspan","2"]],null,null,null,null,null)),(n()(),lu(1,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("branchcoverage",e)&&r),r},null,null)),(n()(),lu(2,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(3,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(4,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(5,null,["",""]))],function(n,t){var e=t.component,r=n(t,4,0,"branchcoverage"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"branchcoverage"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"branchcoverage"!==e.settings.sortBy);n(t,3,0,"icon-down-dir",r)},function(n,t){n(t,5,0,t.component.translations.branchCoverage)})}function Op(n){return bs(0,[(n()(),lu(0,0,null,null,1,"tr",[["codeelement-row",""]],null,null,null,zh,Rh)),Qu(1,49152,null,0,Hh,[],{element:[0,"element"],collapsed:[1,"collapsed"],branchCoverageAvailable:[2,"branchCoverageAvailable"]},null)],function(n,t){n(t,1,0,t.parent.context.$implicit,t.parent.context.$implicit.collapsed,t.component.branchCoverageAvailable)},null)}function kp(n){return bs(0,[(n()(),lu(0,0,null,null,1,"tr",[["class-row",""]],null,null,null,cp,Zh)),Qu(1,49152,null,0,Gh,[],{clazz:[0,"clazz"],translations:[1,"translations"],branchCoverageAvailable:[2,"branchCoverageAvailable"],historyComparisionDate:[3,"historyComparisionDate"]},null)],function(n,t){var e=t.component;n(t,1,0,t.parent.context.$implicit,e.translations,e.branchCoverageAvailable,e.settings.historyComparisionDate)},null)}function Sp(n){return bs(0,[(n()(),lu(0,0,null,null,2,null,null,null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,kp)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component,r=!t.parent.context.$implicit.collapsed&&t.context.$implicit.visible(e.settings.filter,e.settings.historyComparisionType);n(t,2,0,r)},null)}function Ap(n){return bs(0,[(n()(),lu(0,0,null,null,1,"tr",[["class","namespace"],["class-row",""]],null,null,null,cp,Zh)),Qu(1,49152,null,0,Gh,[],{clazz:[0,"clazz"],translations:[1,"translations"],branchCoverageAvailable:[2,"branchCoverageAvailable"],historyComparisionDate:[3,"historyComparisionDate"]},null)],function(n,t){var e=t.component;n(t,1,0,t.parent.context.$implicit,e.translations,e.branchCoverageAvailable,e.settings.historyComparisionDate)},null)}function Tp(n){return bs(0,[(n()(),lu(0,0,null,null,2,null,null,null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Ap)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component,r=!t.parent.parent.context.$implicit.collapsed&&t.context.$implicit.visible(e.settings.filter,e.settings.historyComparisionType);n(t,2,0,r)},null)}function Ip(n){return bs(0,[(n()(),lu(0,0,null,null,4,null,null,null,null,null,null,null)),(n()(),lu(1,0,null,null,1,"tr",[["class","namespace"],["codeelement-row",""]],null,null,null,zh,Rh)),Qu(2,49152,null,0,Hh,[],{element:[0,"element"],collapsed:[1,"collapsed"],branchCoverageAvailable:[2,"branchCoverageAvailable"]},null),(n()(),iu(16777216,null,null,1,null,Tp)),Qu(4,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),iu(0,null,null,0))],function(n,t){n(t,2,0,t.parent.context.$implicit,t.parent.context.$implicit.collapsed,t.component.branchCoverageAvailable),n(t,4,0,t.parent.context.$implicit.classes)},null)}function Pp(n){return bs(0,[(n()(),lu(0,0,null,null,2,null,null,null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Ip)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component,r=!t.parent.context.$implicit.collapsed&&t.context.$implicit.visible(e.settings.filter,e.settings.historyComparisionType);n(t,2,0,r)},null)}function Np(n){return bs(0,[(n()(),lu(0,0,null,null,6,null,null,null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Op)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Sp)),Qu(4,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),iu(16777216,null,null,1,null,Pp)),Qu(6,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component,r=t.context.$implicit.visible(e.settings.filter,e.settings.historyComparisionType);n(t,2,0,r),n(t,4,0,t.context.$implicit.classes),n(t,6,0,t.context.$implicit.subElements)},null)}function Dp(n){return bs(0,[(n()(),lu(0,0,null,null,87,"div",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,34,"div",[["class","customizebox"]],null,null,null,null,null)),(n()(),lu(2,0,null,null,5,"div",[],null,null,null,null,null)),(n()(),lu(3,0,null,null,1,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.collapseAll(e)&&r),r},null,null)),(n()(),vs(4,null,["",""])),(n()(),vs(-1,null,[" | "])),(n()(),lu(6,0,null,null,1,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.expandAll(e)&&r),r},null,null)),(n()(),vs(7,null,["",""])),(n()(),lu(8,0,null,null,15,"div",[["class","center"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,hp)),Qu(10,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,pp)),Qu(12,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,dp)),Qu(14,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(15,0,null,null,0,"br",[],null,null,null,null,null)),(n()(),vs(16,null,[" "," "])),(n()(),lu(17,0,null,null,6,"input",[["min","-1"],["step","1"],["type","range"]],[[8,"max",0],[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"input"],[null,"blur"],[null,"compositionstart"],[null,"compositionend"],[null,"change"]],function(n,t,e){var r=!0,o=n.component;return"input"===t&&(r=!1!==Vu(n,18)._handleInput(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,18).onTouched()&&r),"compositionstart"===t&&(r=!1!==Vu(n,18)._compositionStart()&&r),"compositionend"===t&&(r=!1!==Vu(n,18)._compositionEnd(e.target.value)&&r),"change"===t&&(r=!1!==Vu(n,19).onChange(e.target.value)&&r),"input"===t&&(r=!1!==Vu(n,19).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,19).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.grouping=e)&&r),"ngModelChange"===t&&(r=!1!==o.updateCoverageInfo()&&r),r},null,null)),Qu(18,16384,null,0,Sf,[Xr,Qr,[2,kf]],null,null),Qu(19,16384,null,0,Vf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n,t){return[n,t]},[Sf,Vf]),Qu(21,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(23,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(24,0,null,null,2,"div",[["class","center"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,_p)),Qu(26,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(27,0,null,null,8,"div",[["class","right"]],null,null,null,null,null)),(n()(),lu(28,0,null,null,1,"span",[],null,null,null,null,null)),(n()(),vs(29,null,[""," "])),(n()(),lu(30,0,null,null,5,"input",[["type","text"]],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"input"],[null,"blur"],[null,"compositionstart"],[null,"compositionend"]],function(n,t,e){var r=!0,o=n.component;return"input"===t&&(r=!1!==Vu(n,31)._handleInput(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,31).onTouched()&&r),"compositionstart"===t&&(r=!1!==Vu(n,31)._compositionStart()&&r),"compositionend"===t&&(r=!1!==Vu(n,31)._compositionEnd(e.target.value)&&r),"ngModelChange"===t&&(r=!1!==(o.settings.filter=e)&&r),r},null,null)),Qu(31,16384,null,0,Sf,[Xr,Qr,[2,kf]],null,null),Wu(1024,null,xf,function(n){return[n]},[Sf]),Qu(33,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(35,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(36,0,null,null,51,"table",[["class","overview table-fixed stripped"]],null,null,null,null,null)),(n()(),lu(37,0,null,null,11,"colgroup",[],null,null,null,null,null)),(n()(),lu(38,0,null,null,0,"col",[],null,null,null,null,null)),(n()(),lu(39,0,null,null,0,"col",[["class","column90"]],null,null,null,null,null)),(n()(),lu(40,0,null,null,0,"col",[["class","column105"]],null,null,null,null,null)),(n()(),lu(41,0,null,null,0,"col",[["class","column100"]],null,null,null,null,null)),(n()(),lu(42,0,null,null,0,"col",[["class","column70"]],null,null,null,null,null)),(n()(),lu(43,0,null,null,0,"col",[["class","column98"]],null,null,null,null,null)),(n()(),lu(44,0,null,null,0,"col",[["class","column112"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,wp)),Qu(46,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Cp)),Qu(48,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(49,0,null,null,35,"thead",[],null,null,null,null,null)),(n()(),lu(50,0,null,null,34,"tr",[],null,null,null,null,null)),(n()(),lu(51,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(52,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("name",e)&&r),r},null,null)),(n()(),lu(53,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(54,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(55,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(56,null,["",""])),(n()(),lu(57,0,null,null,5,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(58,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("covered",e)&&r),r},null,null)),(n()(),lu(59,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(60,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(61,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(62,null,["",""])),(n()(),lu(63,0,null,null,5,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(64,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("uncovered",e)&&r),r},null,null)),(n()(),lu(65,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(66,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(67,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(68,null,["",""])),(n()(),lu(69,0,null,null,5,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(70,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("coverable",e)&&r),r},null,null)),(n()(),lu(71,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(72,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(73,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(74,null,["",""])),(n()(),lu(75,0,null,null,5,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(76,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("total",e)&&r),r},null,null)),(n()(),lu(77,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(78,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(79,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(80,null,["",""])),(n()(),iu(16777216,null,null,1,null,Ep)),Qu(82,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,xp)),Qu(84,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(85,0,null,null,2,"tbody",[],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Np)),Qu(87,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null)],function(n,t){var e=t.component;n(t,10,0,-1===e.settings.grouping),n(t,12,0,0===e.settings.grouping),n(t,14,0,e.settings.grouping>0),n(t,21,0,e.settings.grouping),n(t,26,0,e.historicCoverageExecutionTimes.length>0),n(t,33,0,e.settings.filter),n(t,46,0,e.branchCoverageAvailable),n(t,48,0,e.branchCoverageAvailable);var r=n(t,55,0,"name"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"name"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"name"!==e.settings.sortBy);n(t,54,0,"icon-down-dir",r);var o=n(t,61,0,"covered"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"covered"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"covered"!==e.settings.sortBy);n(t,60,0,"icon-down-dir",o);var i=n(t,67,0,"uncovered"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"uncovered"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"uncovered"!==e.settings.sortBy);n(t,66,0,"icon-down-dir",i);var l=n(t,73,0,"coverable"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"coverable"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"coverable"!==e.settings.sortBy);n(t,72,0,"icon-down-dir",l);var u=n(t,79,0,"total"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"total"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"total"!==e.settings.sortBy);n(t,78,0,"icon-down-dir",u),n(t,82,0,e.branchCoverageAvailable),n(t,84,0,e.branchCoverageAvailable),n(t,87,0,e.codeElements)},function(n,t){var e=t.component;n(t,4,0,e.translations.collapseAll),n(t,7,0,e.translations.expandAll),n(t,16,0,e.translations.grouping),n(t,17,0,e.settings.groupingMaximum,Vu(t,23).ngClassUntouched,Vu(t,23).ngClassTouched,Vu(t,23).ngClassPristine,Vu(t,23).ngClassDirty,Vu(t,23).ngClassValid,Vu(t,23).ngClassInvalid,Vu(t,23).ngClassPending),n(t,29,0,e.translations.filter),n(t,30,0,Vu(t,35).ngClassUntouched,Vu(t,35).ngClassTouched,Vu(t,35).ngClassPristine,Vu(t,35).ngClassDirty,Vu(t,35).ngClassValid,Vu(t,35).ngClassInvalid,Vu(t,35).ngClassPending),n(t,56,0,e.translations.name),n(t,62,0,e.translations.covered),n(t,68,0,e.translations.uncovered),n(t,74,0,e.translations.coverable),n(t,80,0,e.translations.total)})}function Mp(n){return bs(0,[(n()(),iu(16777216,null,null,1,null,Dp)),Qu(1,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){n(t,1,0,t.component.codeElements.length>0)},null)}function Vp(n){return bs(0,[(n()(),lu(0,0,null,null,1,"coverage-info",[],null,[["window","beforeunload"]],function(n,t,e){var r=!0;return"window:beforeunload"===t&&(r=!1!==Vu(n,1).onDonBeforeUnlodad()&&r),r},Mp,fp)),Qu(1,114688,null,0,ja,[Ta],null,null)],function(n,t){n(t,1,0)},null)}var Hp=xu("coverage-info",ja,Vp,{},{},[]),Rp=ka(Aa,[Na,ja],function(n){return function(n){for(var t={},e=[],r=!1,o=0;odiv { width: 25%; display: inline-block; } -.customizebox div.right input { font-size: 0.8em; width: 150px; } -#namespaceslider { width: 200px; display: inline-block; margin-left: 8px; } - -.percentagebarundefined { - border-left: 2px solid #fff; - padding-left: 3px; -} -.percentagebar0 { - border-left: 2px solid #c10909; - padding-left: 3px; -} -.percentagebar10 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 90%, #0aad0a 90%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar20 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 80%, #0aad0a 80%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar30 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 70%, #0aad0a 70%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar40 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 60%, #0aad0a 60%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar50 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 50%, #0aad0a 50%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar60 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 40%, #0aad0a 40%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar70 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 30%, #0aad0a 30%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar80 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 20%, #0aad0a 20%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar90 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 10%, #0aad0a 10%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar100 { - border-left: 2px solid #0aad0a; - padding-left: 3px; -} - -.hidden, .ng-hide { display: none; } -.right { text-align: right; } -.center { text-align: center; } -.rightmargin { padding-right: 8px; } -.leftmargin { padding-left: 5px; } -.green { background-color: #0aad0a; } -.lightgreen { background-color: #dcf4dc; } -.red { background-color: #c10909; } -.lightred { background-color: #f7dede; } -.orange { background-color: #FFA500; } -.lightorange { background-color: #FFEFD5; } -.gray { background-color: #dcdcdc; } -.lightgray { color: #888888; } -.lightgraybg { background-color: #dadada; } - -.toggleZoom { text-align:right; } - -.ct-chart { position: relative; } -.ct-chart .ct-line { stroke-width: 2px !important; } -.ct-chart .ct-point { stroke-width: 6px !important; transition: stroke-width .2s; } -.ct-chart .ct-point:hover { stroke-width: 10px !important; } -.ct-chart .ct-series.ct-series-a .ct-line, .ct-chart .ct-series.ct-series-a .ct-point { stroke: #c00 !important;} -.ct-chart .ct-series.ct-series-b .ct-line, .ct-chart .ct-series.ct-series-b .ct-point { stroke: #1c2298 !important;} - -.tinylinecoveragechart, .tinybranchcoveragechart { background-color: #fff; margin-left: -3px; float: left; border: solid 1px #c1c1c1; width: 30px; height: 18px; } -.historiccoverageoffset { margin-top: 7px; } - -.tinylinecoveragechart .ct-line, .tinybranchcoveragechart .ct-line { stroke-width: 1px !important; } -.tinybranchcoveragechart .ct-series.ct-series-a .ct-line { stroke: #1c2298 !important; } - -.linecoverage { background-color: #c00; width: 10px; height: 8px; border: 1px solid #000; display: inline-block; } -.branchcoverage { background-color: #1c2298; width: 10px; height: 8px; border: 1px solid #000; display: inline-block; } - -.tooltip { position: absolute; display: none; padding: 5px; background: #F4C63D;color: #453D3F; pointer-events: none; z-index: 1; } -.tooltip:after { content: ""; position: absolute; top: 100%; left: 50%; width: 0; height: 0; margin-left: -15px; border: 15px solid transparent; border-top-color: #F4C63D; } - -.column1324 { max-width: 1324px; } -.column674 { max-width: 674px; } -.column60 { width: 60px; } -.column70 { width: 70px; } -.column90 { width: 90px; } -.column98 { width: 98px; } -.column100 { width: 100px; } -.column105 { width: 105px; } -.column112 { width: 112px; } -.column135 { width: 135px; } -.column150 { width: 150px; } - -.covered0 { width: 0px; } -.covered1 { width: 1px; } -.covered2 { width: 2px; } -.covered3 { width: 3px; } -.covered4 { width: 4px; } -.covered5 { width: 5px; } -.covered6 { width: 6px; } -.covered7 { width: 7px; } -.covered8 { width: 8px; } -.covered9 { width: 9px; } -.covered10 { width: 10px; } -.covered11 { width: 11px; } -.covered12 { width: 12px; } -.covered13 { width: 13px; } -.covered14 { width: 14px; } -.covered15 { width: 15px; } -.covered16 { width: 16px; } -.covered17 { width: 17px; } -.covered18 { width: 18px; } -.covered19 { width: 19px; } -.covered20 { width: 20px; } -.covered21 { width: 21px; } -.covered22 { width: 22px; } -.covered23 { width: 23px; } -.covered24 { width: 24px; } -.covered25 { width: 25px; } -.covered26 { width: 26px; } -.covered27 { width: 27px; } -.covered28 { width: 28px; } -.covered29 { width: 29px; } -.covered30 { width: 30px; } -.covered31 { width: 31px; } -.covered32 { width: 32px; } -.covered33 { width: 33px; } -.covered34 { width: 34px; } -.covered35 { width: 35px; } -.covered36 { width: 36px; } -.covered37 { width: 37px; } -.covered38 { width: 38px; } -.covered39 { width: 39px; } -.covered40 { width: 40px; } -.covered41 { width: 41px; } -.covered42 { width: 42px; } -.covered43 { width: 43px; } -.covered44 { width: 44px; } -.covered45 { width: 45px; } -.covered46 { width: 46px; } -.covered47 { width: 47px; } -.covered48 { width: 48px; } -.covered49 { width: 49px; } -.covered50 { width: 50px; } -.covered51 { width: 51px; } -.covered52 { width: 52px; } -.covered53 { width: 53px; } -.covered54 { width: 54px; } -.covered55 { width: 55px; } -.covered56 { width: 56px; } -.covered57 { width: 57px; } -.covered58 { width: 58px; } -.covered59 { width: 59px; } -.covered60 { width: 60px; } -.covered61 { width: 61px; } -.covered62 { width: 62px; } -.covered63 { width: 63px; } -.covered64 { width: 64px; } -.covered65 { width: 65px; } -.covered66 { width: 66px; } -.covered67 { width: 67px; } -.covered68 { width: 68px; } -.covered69 { width: 69px; } -.covered70 { width: 70px; } -.covered71 { width: 71px; } -.covered72 { width: 72px; } -.covered73 { width: 73px; } -.covered74 { width: 74px; } -.covered75 { width: 75px; } -.covered76 { width: 76px; } -.covered77 { width: 77px; } -.covered78 { width: 78px; } -.covered79 { width: 79px; } -.covered80 { width: 80px; } -.covered81 { width: 81px; } -.covered82 { width: 82px; } -.covered83 { width: 83px; } -.covered84 { width: 84px; } -.covered85 { width: 85px; } -.covered86 { width: 86px; } -.covered87 { width: 87px; } -.covered88 { width: 88px; } -.covered89 { width: 89px; } -.covered90 { width: 90px; } -.covered91 { width: 91px; } -.covered92 { width: 92px; } -.covered93 { width: 93px; } -.covered94 { width: 94px; } -.covered95 { width: 95px; } -.covered96 { width: 96px; } -.covered97 { width: 97px; } -.covered98 { width: 98px; } -.covered99 { width: 99px; } -.covered100 { width: 100px; } - - @media print { - html, body { background-color: #fff; } - .container { max-width: 100%; width: 100%; padding: 0; } - .overview colgroup col:first-child { width: 300px; } -} - -.icon-up-dir_active { - background-image: url(icon_up-dir.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiNjMDAiIGQ9Ik0xNDA4IDEyMTZxMCAyNi0xOSA0NXQtNDUgMTloLTg5NnEtMjYgMC00NS0xOXQtMTktNDUgMTktNDVsNDQ4LTQ0OHExOS0xOSA0NS0xOXQ0NSAxOWw0NDggNDQ4cTE5IDE5IDE5IDQ1eiIvPjwvc3ZnPg==); - background-repeat: no-repeat; - background-size: contain; - padding-left: 15px; - height: 0.9em; - display: inline-block; - position: relative; - top: 3px; -} -.icon-down-dir_active { - background-image: url(icon_up-dir_active.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiNjMDAiIGQ9Ik0xNDA4IDcwNHEwIDI2LTE5IDQ1bC00NDggNDQ4cS0xOSAxOS00NSAxOXQtNDUtMTlsLTQ0OC00NDhxLTE5LTE5LTE5LTQ1dDE5LTQ1IDQ1LTE5aDg5NnEyNiAwIDQ1IDE5dDE5IDQ1eiIvPjwvc3ZnPg==); - background-repeat: no-repeat; - background-size: contain; - padding-left: 15px; - height: 0.9em; - display: inline-block; - position: relative; - top: 3px; -} -.icon-down-dir { - background-image: url(icon_down-dir_active.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNDA4IDcwNHEwIDI2LTE5IDQ1bC00NDggNDQ4cS0xOSAxOS00NSAxOXQtNDUtMTlsLTQ0OC00NDhxLTE5LTE5LTE5LTQ1dDE5LTQ1IDQ1LTE5aDg5NnEyNiAwIDQ1IDE5dDE5IDQ1eiIvPjwvc3ZnPg==); - background-repeat: no-repeat; - background-size: contain; - padding-left: 15px; - height: 0.9em; - display: inline-block; - position: relative; - top: 3px; -} -.icon-info-circled { - background-image: url(icon_info-circled.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxjaXJjbGUgY3g9Ijg5NiIgY3k9Ijg5NiIgcj0iNzUwIiBmaWxsPSIjZmZmIiAvPjxwYXRoIGZpbGw9IiMyOEE1RkYiIGQ9Ik0xMTUyIDEzNzZ2LTE2MHEwLTE0LTktMjN0LTIzLTloLTk2di01MTJxMC0xNC05LTIzdC0yMy05aC0zMjBxLTE0IDAtMjMgOXQtOSAyM3YxNjBxMCAxNCA5IDIzdDIzIDloOTZ2MzIwaC05NnEtMTQgMC0yMyA5dC05IDIzdjE2MHEwIDE0IDkgMjN0MjMgOWg0NDhxMTQgMCAyMy05dDktMjN6bS0xMjgtODk2di0xNjBxMC0xNC05LTIzdC0yMy05aC0xOTJxLTE0IDAtMjMgOXQtOSAyM3YxNjBxMCAxNCA5IDIzdDIzIDloMTkycTE0IDAgMjMtOXQ5LTIzem02NDAgNDE2cTAgMjA5LTEwMyAzODUuNXQtMjc5LjUgMjc5LjUtMzg1LjUgMTAzLTM4NS41LTEwMy0yNzkuNS0yNzkuNS0xMDMtMzg1LjUgMTAzLTM4NS41IDI3OS41LTI3OS41IDM4NS41LTEwMyAzODUuNSAxMDMgMjc5LjUgMjc5LjUgMTAzIDM4NS41eiIvPjwvc3ZnPg==); - background-repeat: no-repeat; - background-size: contain; - padding-left: 15px; - height: 0.9em; - display: inline-block; -} -.icon-plus { - background-image: url(icon_plus.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNjAwIDczNnYxOTJxMCA0MC0yOCA2OHQtNjggMjhoLTQxNnY0MTZxMCA0MC0yOCA2OHQtNjggMjhoLTE5MnEtNDAgMC02OC0yOHQtMjgtNjh2LTQxNmgtNDE2cS00MCAwLTY4LTI4dC0yOC02OHYtMTkycTAtNDAgMjgtNjh0NjgtMjhoNDE2di00MTZxMC00MCAyOC02OHQ2OC0yOGgxOTJxNDAgMCA2OCAyOHQyOCA2OHY0MTZoNDE2cTQwIDAgNjggMjh0MjggNjh6Ii8+PC9zdmc+); - background-repeat: no-repeat; - background-size: contain; - padding-left: 15px; - height: 0.9em; - display: inline-block; - position: relative; - top: 3px; -} -.icon-minus { - background-image: url(icon_minus.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiNjMDAiIGQ9Ik0xNjAwIDczNnYxOTJxMCA0MC0yOCA2OHQtNjggMjhoLTEyMTZxLTQwIDAtNjgtMjh0LTI4LTY4di0xOTJxMC00MCAyOC02OHQ2OC0yOGgxMjE2cTQwIDAgNjggMjh0MjggNjh6Ii8+PC9zdmc+); - background-repeat: no-repeat; - background-size: contain; - padding-left: 15px; - height: 0.9em; - display: inline-block; - position: relative; - top: 3px; -} -.icon-wrench { - background-image: url(icon_wrench.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxNzkyIiBoZWlnaHQ9IjE3OTIiIHN0eWxlPSJmaWxsOiNlNWU1ZTUiIC8+PHBhdGggZD0iTTQ0OCAxNDcycTAtMjYtMTktNDV0LTQ1LTE5LTQ1IDE5LTE5IDQ1IDE5IDQ1IDQ1IDE5IDQ1LTE5IDE5LTQ1em02NDQtNDIwbC02ODIgNjgycS0zNyAzNy05MCAzNy01MiAwLTkxLTM3bC0xMDYtMTA4cS0zOC0zNi0zOC05MCAwLTUzIDM4LTkxbDY4MS02ODFxMzkgOTggMTE0LjUgMTczLjV0MTczLjUgMTE0LjV6bTYzNC00MzVxMCAzOS0yMyAxMDYtNDcgMTM0LTE2NC41IDIxNy41dC0yNTguNSA4My41cS0xODUgMC0zMTYuNS0xMzEuNXQtMTMxLjUtMzE2LjUgMTMxLjUtMzE2LjUgMzE2LjUtMTMxLjVxNTggMCAxMjEuNSAxNi41dDEwNy41IDQ2LjVxMTYgMTEgMTYgMjh0LTE2IDI4bC0yOTMgMTY5djIyNGwxOTMgMTA3cTUtMyA3OS00OC41dDEzNS41LTgxIDcwLjUtMzUuNXExNSAwIDIzLjUgMTB0OC41IDI1eiIvPjwvc3ZnPg==); - background-repeat: no-repeat; - background-size: contain; - padding-left: 20px; - height: 0.9em; - display: inline-block; -} -.icon-fork { - background-image: url(icon_fork.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxNzkyIiBoZWlnaHQ9IjE3OTIiIHN0eWxlPSJmaWxsOiNmZmYiIC8+PHBhdGggZD0iTTY3MiAxNDcycTAtNDAtMjgtNjh0LTY4LTI4LTY4IDI4LTI4IDY4IDI4IDY4IDY4IDI4IDY4LTI4IDI4LTY4em0wLTExNTJxMC00MC0yOC02OHQtNjgtMjgtNjggMjgtMjggNjggMjggNjggNjggMjggNjgtMjggMjgtNjh6bTY0MCAxMjhxMC00MC0yOC02OHQtNjgtMjgtNjggMjgtMjggNjggMjggNjggNjggMjggNjgtMjggMjgtNjh6bTk2IDBxMCA1Mi0yNiA5Ni41dC03MCA2OS41cS0yIDI4Ny0yMjYgNDE0LTY3IDM4LTIwMyA4MS0xMjggNDAtMTY5LjUgNzF0LTQxLjUgMTAwdjI2cTQ0IDI1IDcwIDY5LjV0MjYgOTYuNXEwIDgwLTU2IDEzNnQtMTM2IDU2LTEzNi01Ni01Ni0xMzZxMC01MiAyNi05Ni41dDcwLTY5LjV2LTgyMHEtNDQtMjUtNzAtNjkuNXQtMjYtOTYuNXEwLTgwIDU2LTEzNnQxMzYtNTYgMTM2IDU2IDU2IDEzNnEwIDUyLTI2IDk2LjV0LTcwIDY5LjV2NDk3cTU0LTI2IDE1NC01NyA1NS0xNyA4Ny41LTI5LjV0NzAuNS0zMSA1OS0zOS41IDQwLjUtNTEgMjgtNjkuNSA4LjUtOTEuNXEtNDQtMjUtNzAtNjkuNXQtMjYtOTYuNXEwLTgwIDU2LTEzNnQxMzYtNTYgMTM2IDU2IDU2IDEzNnoiLz48L3N2Zz4=); - background-repeat: no-repeat; - background-size: contain; - padding-left: 20px; - height: 0.9em; - display: inline-block; -} -.icon-cube { - background-image: url(icon_cube.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxNzkyIiBoZWlnaHQ9IjE3OTIiIHN0eWxlPSJmaWxsOiNlNWU1ZTUiIC8+PHBhdGggZD0iTTg5NiAxNjI5bDY0MC0zNDl2LTYzNmwtNjQwIDIzM3Y3NTJ6bS02NC04NjVsNjk4LTI1NC02OTgtMjU0LTY5OCAyNTR6bTgzMi0yNTJ2NzY4cTAgMzUtMTggNjV0LTQ5IDQ3bC03MDQgMzg0cS0yOCAxNi02MSAxNnQtNjEtMTZsLTcwNC0zODRxLTMxLTE3LTQ5LTQ3dC0xOC02NXYtNzY4cTAtNDAgMjMtNzN0NjEtNDdsNzA0LTI1NnEyMi04IDQ0LTh0NDQgOGw3MDQgMjU2cTM4IDE0IDYxIDQ3dDIzIDczeiIvPjwvc3ZnPg==); - background-repeat: no-repeat; - background-size: contain; - padding-left: 20px; - height: 0.9em; - display: inline-block; -} -.icon-search-plus { - background-image: url(icon_search-plus.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiM2ZjZmNmYiIGQ9Ik0xMDg4IDgwMHY2NHEwIDEzLTkuNSAyMi41dC0yMi41IDkuNWgtMjI0djIyNHEwIDEzLTkuNSAyMi41dC0yMi41IDkuNWgtNjRxLTEzIDAtMjIuNS05LjV0LTkuNS0yMi41di0yMjRoLTIyNHEtMTMgMC0yMi41LTkuNXQtOS41LTIyLjV2LTY0cTAtMTMgOS41LTIyLjV0MjIuNS05LjVoMjI0di0yMjRxMC0xMyA5LjUtMjIuNXQyMi41LTkuNWg2NHExMyAwIDIyLjUgOS41dDkuNSAyMi41djIyNGgyMjRxMTMgMCAyMi41IDkuNXQ5LjUgMjIuNXptMTI4IDMycTAtMTg1LTEzMS41LTMxNi41dC0zMTYuNS0xMzEuNS0zMTYuNSAxMzEuNS0xMzEuNSAzMTYuNSAxMzEuNSAzMTYuNSAzMTYuNSAxMzEuNSAzMTYuNS0xMzEuNSAxMzEuNS0zMTYuNXptNTEyIDgzMnEwIDUzLTM3LjUgOTAuNXQtOTAuNSAzNy41cS01NCAwLTkwLTM4bC0zNDMtMzQycS0xNzkgMTI0LTM5OSAxMjQtMTQzIDAtMjczLjUtNTUuNXQtMjI1LTE1MC0xNTAtMjI1LTU1LjUtMjczLjUgNTUuNS0yNzMuNSAxNTAtMjI1IDIyNS0xNTAgMjczLjUtNTUuNSAyNzMuNSA1NS41IDIyNSAxNTAgMTUwIDIyNSA1NS41IDI3My41cTAgMjIwLTEyNCAzOTlsMzQzIDM0M3EzNyAzNyAzNyA5MHoiLz48L3N2Zz4=); - background-repeat: no-repeat; - background-size: contain; - padding-left: 20px; - height: 0.9em; - display: inline-block; -} -.icon-search-minus { - background-image: url(icon_search-minus.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiM2ZjZmNmYiIGQ9Ik0xMDg4IDgwMHY2NHEwIDEzLTkuNSAyMi41dC0yMi41IDkuNWgtNTc2cS0xMyAwLTIyLjUtOS41dC05LjUtMjIuNXYtNjRxMC0xMyA5LjUtMjIuNXQyMi41LTkuNWg1NzZxMTMgMCAyMi41IDkuNXQ5LjUgMjIuNXptMTI4IDMycTAtMTg1LTEzMS41LTMxNi41dC0zMTYuNS0xMzEuNS0zMTYuNSAxMzEuNS0xMzEuNSAzMTYuNSAxMzEuNSAzMTYuNSAzMTYuNSAxMzEuNSAzMTYuNS0xMzEuNSAxMzEuNS0zMTYuNXptNTEyIDgzMnEwIDUzLTM3LjUgOTAuNXQtOTAuNSAzNy41cS01NCAwLTkwLTM4bC0zNDMtMzQycS0xNzkgMTI0LTM5OSAxMjQtMTQzIDAtMjczLjUtNTUuNXQtMjI1LTE1MC0xNTAtMjI1LTU1LjUtMjczLjUgNTUuNS0yNzMuNSAxNTAtMjI1IDIyNS0xNTAgMjczLjUtNTUuNSAyNzMuNSA1NS41IDIyNSAxNTAgMTUwIDIyNSA1NS41IDI3My41cTAgMjIwLTEyNCAzOTlsMzQzIDM0M3EzNyAzNyAzNyA5MHoiLz48L3N2Zz4=); - background-repeat: no-repeat; - background-size: contain; - padding-left: 20px; - height: 0.9em; - display: inline-block; -} - -.ct-double-octave:after,.ct-major-eleventh:after,.ct-major-second:after,.ct-major-seventh:after,.ct-major-sixth:after,.ct-major-tenth:after,.ct-major-third:after,.ct-major-twelfth:after,.ct-minor-second:after,.ct-minor-seventh:after,.ct-minor-sixth:after,.ct-minor-third:after,.ct-octave:after,.ct-perfect-fifth:after,.ct-perfect-fourth:after,.ct-square:after{content:"";clear:both}.ct-label{fill:rgba(0,0,0,.4);color:rgba(0,0,0,.4);font-size:.75rem;line-height:1}.ct-grid-background,.ct-line{fill:none}.ct-chart-bar .ct-label,.ct-chart-line .ct-label{display:block;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}.ct-chart-donut .ct-label,.ct-chart-pie .ct-label{dominant-baseline:central}.ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label.ct-vertical.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-end;-webkit-justify-content:flex-end;-ms-flex-pack:flex-end;justify-content:flex-end;text-align:right;text-anchor:end}.ct-label.ct-vertical.ct-end{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar .ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;text-anchor:start}.ct-chart-bar .ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-start{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:flex-end;-webkit-justify-content:flex-end;-ms-flex-pack:flex-end;justify-content:flex-end;text-align:right;text-anchor:end}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-end{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:end}.ct-grid{stroke:rgba(0,0,0,.2);stroke-width:1px;stroke-dasharray:2px}.ct-point{stroke-width:10px;stroke-linecap:round}.ct-line{stroke-width:4px}.ct-area{stroke:none;fill-opacity:.1}.ct-bar{fill:none;stroke-width:10px}.ct-slice-donut{fill:none;stroke-width:60px}.ct-series-a .ct-bar,.ct-series-a .ct-line,.ct-series-a .ct-point,.ct-series-a .ct-slice-donut{stroke:#d70206}.ct-series-a .ct-area,.ct-series-a .ct-slice-donut-solid,.ct-series-a .ct-slice-pie{fill:#d70206}.ct-series-b .ct-bar,.ct-series-b .ct-line,.ct-series-b .ct-point,.ct-series-b .ct-slice-donut{stroke:#f05b4f}.ct-series-b .ct-area,.ct-series-b .ct-slice-donut-solid,.ct-series-b .ct-slice-pie{fill:#f05b4f}.ct-series-c .ct-bar,.ct-series-c .ct-line,.ct-series-c .ct-point,.ct-series-c .ct-slice-donut{stroke:#f4c63d}.ct-series-c .ct-area,.ct-series-c .ct-slice-donut-solid,.ct-series-c .ct-slice-pie{fill:#f4c63d}.ct-series-d .ct-bar,.ct-series-d .ct-line,.ct-series-d .ct-point,.ct-series-d .ct-slice-donut{stroke:#d17905}.ct-series-d .ct-area,.ct-series-d .ct-slice-donut-solid,.ct-series-d .ct-slice-pie{fill:#d17905}.ct-series-e .ct-bar,.ct-series-e .ct-line,.ct-series-e .ct-point,.ct-series-e .ct-slice-donut{stroke:#453d3f}.ct-series-e .ct-area,.ct-series-e .ct-slice-donut-solid,.ct-series-e .ct-slice-pie{fill:#453d3f}.ct-series-f .ct-bar,.ct-series-f .ct-line,.ct-series-f .ct-point,.ct-series-f .ct-slice-donut{stroke:#59922b}.ct-series-f .ct-area,.ct-series-f .ct-slice-donut-solid,.ct-series-f .ct-slice-pie{fill:#59922b}.ct-series-g .ct-bar,.ct-series-g .ct-line,.ct-series-g .ct-point,.ct-series-g .ct-slice-donut{stroke:#0544d3}.ct-series-g .ct-area,.ct-series-g .ct-slice-donut-solid,.ct-series-g .ct-slice-pie{fill:#0544d3}.ct-series-h .ct-bar,.ct-series-h .ct-line,.ct-series-h .ct-point,.ct-series-h .ct-slice-donut{stroke:#6b0392}.ct-series-h .ct-area,.ct-series-h .ct-slice-donut-solid,.ct-series-h .ct-slice-pie{fill:#6b0392}.ct-series-i .ct-bar,.ct-series-i .ct-line,.ct-series-i .ct-point,.ct-series-i .ct-slice-donut{stroke:#f05b4f}.ct-series-i .ct-area,.ct-series-i .ct-slice-donut-solid,.ct-series-i .ct-slice-pie{fill:#f05b4f}.ct-series-j .ct-bar,.ct-series-j .ct-line,.ct-series-j .ct-point,.ct-series-j .ct-slice-donut{stroke:#dda458}.ct-series-j .ct-area,.ct-series-j .ct-slice-donut-solid,.ct-series-j .ct-slice-pie{fill:#dda458}.ct-series-k .ct-bar,.ct-series-k .ct-line,.ct-series-k .ct-point,.ct-series-k .ct-slice-donut{stroke:#eacf7d}.ct-series-k .ct-area,.ct-series-k .ct-slice-donut-solid,.ct-series-k .ct-slice-pie{fill:#eacf7d}.ct-series-l .ct-bar,.ct-series-l .ct-line,.ct-series-l .ct-point,.ct-series-l .ct-slice-donut{stroke:#86797d}.ct-series-l .ct-area,.ct-series-l .ct-slice-donut-solid,.ct-series-l .ct-slice-pie{fill:#86797d}.ct-series-m .ct-bar,.ct-series-m .ct-line,.ct-series-m .ct-point,.ct-series-m .ct-slice-donut{stroke:#b2c326}.ct-series-m .ct-area,.ct-series-m .ct-slice-donut-solid,.ct-series-m .ct-slice-pie{fill:#b2c326}.ct-series-n .ct-bar,.ct-series-n .ct-line,.ct-series-n .ct-point,.ct-series-n .ct-slice-donut{stroke:#6188e2}.ct-series-n .ct-area,.ct-series-n .ct-slice-donut-solid,.ct-series-n .ct-slice-pie{fill:#6188e2}.ct-series-o .ct-bar,.ct-series-o .ct-line,.ct-series-o .ct-point,.ct-series-o .ct-slice-donut{stroke:#a748ca}.ct-series-o .ct-area,.ct-series-o .ct-slice-donut-solid,.ct-series-o .ct-slice-pie{fill:#a748ca}.ct-square{display:block;position:relative;width:100%}.ct-square:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:100%}.ct-square:after{display:table}.ct-square>svg{display:block;position:absolute;top:0;left:0}.ct-minor-second{display:block;position:relative;width:100%}.ct-minor-second:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:93.75%}.ct-minor-second:after{display:table}.ct-minor-second>svg{display:block;position:absolute;top:0;left:0}.ct-major-second{display:block;position:relative;width:100%}.ct-major-second:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:88.8888888889%}.ct-major-second:after{display:table}.ct-major-second>svg{display:block;position:absolute;top:0;left:0}.ct-minor-third{display:block;position:relative;width:100%}.ct-minor-third:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:83.3333333333%}.ct-minor-third:after{display:table}.ct-minor-third>svg{display:block;position:absolute;top:0;left:0}.ct-major-third{display:block;position:relative;width:100%}.ct-major-third:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:80%}.ct-major-third:after{display:table}.ct-major-third>svg{display:block;position:absolute;top:0;left:0}.ct-perfect-fourth{display:block;position:relative;width:100%}.ct-perfect-fourth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:75%}.ct-perfect-fourth:after{display:table}.ct-perfect-fourth>svg{display:block;position:absolute;top:0;left:0}.ct-perfect-fifth{display:block;position:relative;width:100%}.ct-perfect-fifth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:66.6666666667%}.ct-perfect-fifth:after{display:table}.ct-perfect-fifth>svg{display:block;position:absolute;top:0;left:0}.ct-minor-sixth{display:block;position:relative;width:100%}.ct-minor-sixth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:62.5%}.ct-minor-sixth:after{display:table}.ct-minor-sixth>svg{display:block;position:absolute;top:0;left:0}.ct-golden-section{display:block;position:relative;width:100%}.ct-golden-section:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:61.804697157%}.ct-golden-section:after{content:"";display:table;clear:both}.ct-golden-section>svg{display:block;position:absolute;top:0;left:0}.ct-major-sixth{display:block;position:relative;width:100%}.ct-major-sixth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:60%}.ct-major-sixth:after{display:table}.ct-major-sixth>svg{display:block;position:absolute;top:0;left:0}.ct-minor-seventh{display:block;position:relative;width:100%}.ct-minor-seventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:56.25%}.ct-minor-seventh:after{display:table}.ct-minor-seventh>svg{display:block;position:absolute;top:0;left:0}.ct-major-seventh{display:block;position:relative;width:100%}.ct-major-seventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:53.3333333333%}.ct-major-seventh:after{display:table}.ct-major-seventh>svg{display:block;position:absolute;top:0;left:0}.ct-octave{display:block;position:relative;width:100%}.ct-octave:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:50%}.ct-octave:after{display:table}.ct-octave>svg{display:block;position:absolute;top:0;left:0}.ct-major-tenth{display:block;position:relative;width:100%}.ct-major-tenth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:40%}.ct-major-tenth:after{display:table}.ct-major-tenth>svg{display:block;position:absolute;top:0;left:0}.ct-major-eleventh{display:block;position:relative;width:100%}.ct-major-eleventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:37.5%}.ct-major-eleventh:after{display:table}.ct-major-eleventh>svg{display:block;position:absolute;top:0;left:0}.ct-major-twelfth{display:block;position:relative;width:100%}.ct-major-twelfth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:33.3333333333%}.ct-major-twelfth:after{display:table}.ct-major-twelfth>svg{display:block;position:absolute;top:0;left:0}.ct-double-octave{display:block;position:relative;width:100%}.ct-double-octave:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:25%}.ct-double-octave:after{display:table}.ct-double-octave>svg{display:block;position:absolute;top:0;left:0} \ No newline at end of file From f6c0619b074340b366f0d0fb6208ba3c0ce2aac0 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 7 Apr 2021 14:22:36 +0800 Subject: [PATCH 041/424] delete --- .../2021-03-26_16-34-47_CoverageHistory.xml | 12 - .../report/ClassLibrary1_Calculation.htm | 82 -- .../Lab.OpenCoverDemo/report/Cobertura.xml | 105 -- .../report/UnitTestProject1_UnitTest1.htm | 88 -- .../report/UnitTestProject2_UnitTest1.htm | 75 - .../report/badge_linecoverage.png | Bin 2925 -> 0 bytes .../report/badge_linecoverage.svg | 88 -- .../Lab.OpenCoverDemo/report/class.js | 207 --- .../Lab.OpenCoverDemo/report/coverage.xml | 1270 ----------------- .../Lab.OpenCoverDemo/report/icon_cube.svg | 2 - .../report/icon_down-dir_active.svg | 2 - .../Lab.OpenCoverDemo/report/icon_fork.svg | 2 - .../report/icon_info-circled.svg | 2 - .../Lab.OpenCoverDemo/report/icon_minus.svg | 2 - .../Lab.OpenCoverDemo/report/icon_plus.svg | 2 - .../report/icon_search-minus.svg | 2 - .../report/icon_search-plus.svg | 2 - .../Lab.OpenCoverDemo/report/icon_up-dir.svg | 2 - .../report/icon_up-dir_active.svg | 2 - .../Lab.OpenCoverDemo/report/icon_wrench.svg | 2 - .../Lab.OpenCoverDemo/report/index.htm | 71 - .../Lab.OpenCoverDemo/report/main.js | 274 ---- .../Lab.OpenCoverDemo/report/report.css | 357 ----- 23 files changed, 2651 deletions(-) delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/history/2021-03-26_16-34-47_CoverageHistory.xml delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/ClassLibrary1_Calculation.htm delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/Cobertura.xml delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject1_UnitTest1.htm delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject2_UnitTest1.htm delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.png delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/class.js delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/coverage.xml delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_cube.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_down-dir_active.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_fork.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_info-circled.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_minus.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_plus.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-minus.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-plus.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir_active.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_wrench.svg delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/index.htm delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/main.js delete mode 100644 CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/report.css diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/history/2021-03-26_16-34-47_CoverageHistory.xml b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/history/2021-03-26_16-34-47_CoverageHistory.xml deleted file mode 100644 index 7531ba24..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/history/2021-03-26_16-34-47_CoverageHistory.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/ClassLibrary1_Calculation.htm b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/ClassLibrary1_Calculation.htm deleted file mode 100644 index 6976b753..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/ClassLibrary1_Calculation.htm +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - -ClassLibrary1.Calculation - Coverage Report - -
-

Summary

- ---- - - - - - - - - - - - - - -
Class:ClassLibrary1.Calculation
Assembly:ClassLibrary1
File(s):E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\ClassLibrary1\Calculation.cs
Covered lines:3
Uncovered lines:6
Coverable lines:9
Total lines:18
Line coverage:33.3% (3 of 9)
Covered branches:0
Total branches:0
Tag:Build.2019.11.11
-

Coverage History

-
- -

Metrics

- - - - - - - -
MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage Crap Score
Add(...)10100%100%1
Sub(...)100%0%2
Sub1(...)100%0%2
-

File(s)

-

E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\ClassLibrary1\Calculation.cs

- - - - - - - - - - - - - - - - - - - - - - -
#LineLine coverage
 1namespace ClassLibrary1
 2{
 3    public class Calculation
 4    {
 5        public int Add(int firstNumber, int secondNumber)
 26        {
 27            return firstNumber + secondNumber;
 28        }
 9        public int Sub(int firstNumber, int secondNumber)
 010        {
 011            return firstNumber + secondNumber;
 012        }
 13        public int Sub1(int firstNumber, int secondNumber)
 014        {
 015            return firstNumber + secondNumber;
 016        }
 17    }
 18}
-
-
- - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/Cobertura.xml b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/Cobertura.xml deleted file mode 100644 index 105f9442..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/Cobertura.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject1_UnitTest1.htm b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject1_UnitTest1.htm deleted file mode 100644 index 1ec7556c..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject1_UnitTest1.htm +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - -UnitTestProject1.UnitTest1 - Coverage Report - -
-

Summary

- ---- - - - - - - - - - - - - - -
Class:UnitTestProject1.UnitTest1
Assembly:UnitTestProject1
File(s):E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\UnitTest1.cs
Covered lines:9
Uncovered lines:1
Coverable lines:10
Total lines:26
Line coverage:90% (9 of 10)
Covered branches:0
Total branches:0
Tag:Build.2019.11.11
-

Coverage History

-
- -

Metrics

- - - - - - -
MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage Crap Score
TestMethod1()10100%100%1
TestMethod2()1080%100%1.01
-

File(s)

-

E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\UnitTest1.cs

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#LineLine coverage
 1using System;
 2using ClassLibrary1;
 3using Microsoft.VisualStudio.TestTools.UnitTesting;
 4
 5namespace UnitTestProject1
 6{
 7    [TestClass]
 8    public class UnitTest1
 9    {
 10        [TestMethod]
 11        public void TestMethod1()
 112        {
 113            var calculation = new Calculation();
 114            var actual = calculation.Add(1, 1);
 115            Assert.AreEqual(2,actual);
 116        }
 17
 18        [TestMethod]
 19        public void TestMethod2()
 120        {
 121            var calculation = new Calculation();
 122            var actual = calculation.Add(1, 1);
 123            Assert.AreEqual(1,actual);
 024        }
 25    }
 26}
-
-
-
-

Methods/Properties

-TestMethod1()
-TestMethod2()
-
-
- - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject2_UnitTest1.htm b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject2_UnitTest1.htm deleted file mode 100644 index 7d13ebe2..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/UnitTestProject2_UnitTest1.htm +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - -UnitTestProject2.UnitTest1 - Coverage Report - -
-

Summary

- ---- - - - - - - - - - - - - - -
Class:UnitTestProject2.UnitTest1
Assembly:UnitTestProject2
File(s):E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject2\UnitTest1.cs
Covered lines:3
Uncovered lines:0
Coverable lines:3
Total lines:15
Line coverage:100% (3 of 3)
Covered branches:0
Total branches:0
Tag:Build.2019.11.11
-

Coverage History

-
- -

Metrics

- - - - - -
MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage Crap Score
TestMethod1()10100%100%1
-

File(s)

-

E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject2\UnitTest1.cs

- - - - - - - - - - - - - - - - - - - -
#LineLine coverage
 1using System;
 2using Microsoft.VisualStudio.TestTools.UnitTesting;
 3
 4namespace UnitTestProject2
 5{
 6    [TestClass]
 7    public class UnitTest1
 8    {
 9        [TestMethod]
 10        public void TestMethod1()
 111        {
 112            Assert.AreEqual(1,1);
 113        }
 14    }
 15}
-
-
-
-

Methods/Properties

-TestMethod1()
-
-
- - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.png b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.png deleted file mode 100644 index 1d4585190f78c440e6cda1f35623d1780d05fbd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2925 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 diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.svg deleted file mode 100644 index f3f4a54c..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/badge_linecoverage.svg +++ /dev/null @@ -1,88 +0,0 @@ - - - Code coverage - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Generated by: ReportGenerator 4.1.2.0 - - - - Coverage - Coverage - 68.1%68.1% - - - - - Line coverage - - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/class.js b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/class.js deleted file mode 100644 index b82bca96..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/class.js +++ /dev/null @@ -1,207 +0,0 @@ -/* Chartist.js 0.11.0 - * Copyright © 2017 Gion Kunz - * Free to use under either the WTFPL license or the MIT license. - * https://raw.githubusercontent.com/gionkunz/chartist-js/master/LICENSE-WTFPL - * https://raw.githubusercontent.com/gionkunz/chartist-js/master/LICENSE-MIT - */ - -!function (a, b) { "function" == typeof define && define.amd ? define("Chartist", [], function () { return a.Chartist = b() }) : "object" == typeof module && module.exports ? module.exports = b() : a.Chartist = b() }(this, function () { - var a = { version: "0.11.0" }; return function (a, b, c) { "use strict"; c.namespaces = { svg: "http://www.w3.org/2000/svg", xmlns: "http://www.w3.org/2000/xmlns/", xhtml: "http://www.w3.org/1999/xhtml", xlink: "http://www.w3.org/1999/xlink", ct: "http://gionkunz.github.com/chartist-js/ct" }, c.noop = function (a) { return a }, c.alphaNumerate = function (a) { return String.fromCharCode(97 + a % 26) }, c.extend = function (a) { var b, d, e; for (a = a || {}, b = 1; b < arguments.length; b++) { d = arguments[b]; for (var f in d) e = d[f], "object" != typeof e || null === e || e instanceof Array ? a[f] = e : a[f] = c.extend(a[f], e) } return a }, c.replaceAll = function (a, b, c) { return a.replace(new RegExp(b, "g"), c) }, c.ensureUnit = function (a, b) { return "number" == typeof a && (a += b), a }, c.quantity = function (a) { if ("string" == typeof a) { var b = /^(\d+)\s*(.*)$/g.exec(a); return { value: +b[1], unit: b[2] || void 0 } } return { value: a } }, c.querySelector = function (a) { return a instanceof Node ? a : b.querySelector(a) }, c.times = function (a) { return Array.apply(null, new Array(a)) }, c.sum = function (a, b) { return a + (b ? b : 0) }, c.mapMultiply = function (a) { return function (b) { return b * a } }, c.mapAdd = function (a) { return function (b) { return b + a } }, c.serialMap = function (a, b) { var d = [], e = Math.max.apply(null, a.map(function (a) { return a.length })); return c.times(e).forEach(function (c, e) { var f = a.map(function (a) { return a[e] }); d[e] = b.apply(null, f) }), d }, c.roundWithPrecision = function (a, b) { var d = Math.pow(10, b || c.precision); return Math.round(a * d) / d }, c.precision = 8, c.escapingMap = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }, c.serialize = function (a) { return null === a || void 0 === a ? a : ("number" == typeof a ? a = "" + a : "object" == typeof a && (a = JSON.stringify({ data: a })), Object.keys(c.escapingMap).reduce(function (a, b) { return c.replaceAll(a, b, c.escapingMap[b]) }, a)) }, c.deserialize = function (a) { if ("string" != typeof a) return a; a = Object.keys(c.escapingMap).reduce(function (a, b) { return c.replaceAll(a, c.escapingMap[b], b) }, a); try { a = JSON.parse(a), a = void 0 !== a.data ? a.data : a } catch (b) { } return a }, c.createSvg = function (a, b, d, e) { var f; return b = b || "100%", d = d || "100%", Array.prototype.slice.call(a.querySelectorAll("svg")).filter(function (a) { return a.getAttributeNS(c.namespaces.xmlns, "ct") }).forEach(function (b) { a.removeChild(b) }), f = new c.Svg("svg").attr({ width: b, height: d }).addClass(e), f._node.style.width = b, f._node.style.height = d, a.appendChild(f._node), f }, c.normalizeData = function (a, b, d) { var e, f = { raw: a, normalized: {} }; return f.normalized.series = c.getDataArray({ series: a.series || [] }, b, d), e = f.normalized.series.every(function (a) { return a instanceof Array }) ? Math.max.apply(null, f.normalized.series.map(function (a) { return a.length })) : f.normalized.series.length, f.normalized.labels = (a.labels || []).slice(), Array.prototype.push.apply(f.normalized.labels, c.times(Math.max(0, e - f.normalized.labels.length)).map(function () { return "" })), b && c.reverseData(f.normalized), f }, c.safeHasProperty = function (a, b) { return null !== a && "object" == typeof a && a.hasOwnProperty(b) }, c.isDataHoleValue = function (a) { return null === a || void 0 === a || "number" == typeof a && isNaN(a) }, c.reverseData = function (a) { a.labels.reverse(), a.series.reverse(); for (var b = 0; b < a.series.length; b++)"object" == typeof a.series[b] && void 0 !== a.series[b].data ? a.series[b].data.reverse() : a.series[b] instanceof Array && a.series[b].reverse() }, c.getDataArray = function (a, b, d) { function e(a) { if (c.safeHasProperty(a, "value")) return e(a.value); if (c.safeHasProperty(a, "data")) return e(a.data); if (a instanceof Array) return a.map(e); if (!c.isDataHoleValue(a)) { if (d) { var b = {}; return "string" == typeof d ? b[d] = c.getNumberOrUndefined(a) : b.y = c.getNumberOrUndefined(a), b.x = a.hasOwnProperty("x") ? c.getNumberOrUndefined(a.x) : b.x, b.y = a.hasOwnProperty("y") ? c.getNumberOrUndefined(a.y) : b.y, b } return c.getNumberOrUndefined(a) } } return a.series.map(e) }, c.normalizePadding = function (a, b) { return b = b || 0, "number" == typeof a ? { top: a, right: a, bottom: a, left: a } : { top: "number" == typeof a.top ? a.top : b, right: "number" == typeof a.right ? a.right : b, bottom: "number" == typeof a.bottom ? a.bottom : b, left: "number" == typeof a.left ? a.left : b } }, c.getMetaData = function (a, b) { var c = a.data ? a.data[b] : a[b]; return c ? c.meta : void 0 }, c.orderOfMagnitude = function (a) { return Math.floor(Math.log(Math.abs(a)) / Math.LN10) }, c.projectLength = function (a, b, c) { return b / c.range * a }, c.getAvailableHeight = function (a, b) { return Math.max((c.quantity(b.height).value || a.height()) - (b.chartPadding.top + b.chartPadding.bottom) - b.axisX.offset, 0) }, c.getHighLow = function (a, b, d) { function e(a) { if (void 0 !== a) if (a instanceof Array) for (var b = 0; b < a.length; b++)e(a[b]); else { var c = d ? +a[d] : +a; g && c > f.high && (f.high = c), h && c < f.low && (f.low = c) } } b = c.extend({}, b, d ? b["axis" + d.toUpperCase()] : {}); var f = { high: void 0 === b.high ? -Number.MAX_VALUE : +b.high, low: void 0 === b.low ? Number.MAX_VALUE : +b.low }, g = void 0 === b.high, h = void 0 === b.low; return (g || h) && e(a), (b.referenceValue || 0 === b.referenceValue) && (f.high = Math.max(b.referenceValue, f.high), f.low = Math.min(b.referenceValue, f.low)), f.high <= f.low && (0 === f.low ? f.high = 1 : f.low < 0 ? f.high = 0 : f.high > 0 ? f.low = 0 : (f.high = 1, f.low = 0)), f }, c.isNumeric = function (a) { return null !== a && isFinite(a) }, c.isFalseyButZero = function (a) { return !a && 0 !== a }, c.getNumberOrUndefined = function (a) { return c.isNumeric(a) ? +a : void 0 }, c.isMultiValue = function (a) { return "object" == typeof a && ("x" in a || "y" in a) }, c.getMultiValue = function (a, b) { return c.isMultiValue(a) ? c.getNumberOrUndefined(a[b || "y"]) : c.getNumberOrUndefined(a) }, c.rho = function (a) { function b(a, c) { return a % c === 0 ? c : b(c, a % c) } function c(a) { return a * a + 1 } if (1 === a) return a; var d, e = 2, f = 2; if (a % 2 === 0) return 2; do e = c(e) % a, f = c(c(f)) % a, d = b(Math.abs(e - f), a); while (1 === d); return d }, c.getBounds = function (a, b, d, e) { function f(a, b) { return a === (a += b) && (a *= 1 + (b > 0 ? o : -o)), a } var g, h, i, j = 0, k = { high: b.high, low: b.low }; k.valueRange = k.high - k.low, k.oom = c.orderOfMagnitude(k.valueRange), k.step = Math.pow(10, k.oom), k.min = Math.floor(k.low / k.step) * k.step, k.max = Math.ceil(k.high / k.step) * k.step, k.range = k.max - k.min, k.numberOfSteps = Math.round(k.range / k.step); var l = c.projectLength(a, k.step, k), m = l < d, n = e ? c.rho(k.range) : 0; if (e && c.projectLength(a, 1, k) >= d) k.step = 1; else if (e && n < k.step && c.projectLength(a, n, k) >= d) k.step = n; else for (; ;) { if (m && c.projectLength(a, k.step, k) <= d) k.step *= 2; else { if (m || !(c.projectLength(a, k.step / 2, k) >= d)) break; if (k.step /= 2, e && k.step % 1 !== 0) { k.step *= 2; break } } if (j++ > 1e3) throw new Error("Exceeded maximum number of iterations while optimizing scale step!") } var o = 2.221e-16; for (k.step = Math.max(k.step, o), h = k.min, i = k.max; h + k.step <= k.low;)h = f(h, k.step); for (; i - k.step >= k.high;)i = f(i, -k.step); k.min = h, k.max = i, k.range = k.max - k.min; var p = []; for (g = k.min; g <= k.max; g = f(g, k.step)) { var q = c.roundWithPrecision(g); q !== p[p.length - 1] && p.push(q) } return k.values = p, k }, c.polarToCartesian = function (a, b, c, d) { var e = (d - 90) * Math.PI / 180; return { x: a + c * Math.cos(e), y: b + c * Math.sin(e) } }, c.createChartRect = function (a, b, d) { var e = !(!b.axisX && !b.axisY), f = e ? b.axisY.offset : 0, g = e ? b.axisX.offset : 0, h = a.width() || c.quantity(b.width).value || 0, i = a.height() || c.quantity(b.height).value || 0, j = c.normalizePadding(b.chartPadding, d); h = Math.max(h, f + j.left + j.right), i = Math.max(i, g + j.top + j.bottom); var k = { padding: j, width: function () { return this.x2 - this.x1 }, height: function () { return this.y1 - this.y2 } }; return e ? ("start" === b.axisX.position ? (k.y2 = j.top + g, k.y1 = Math.max(i - j.bottom, k.y2 + 1)) : (k.y2 = j.top, k.y1 = Math.max(i - j.bottom - g, k.y2 + 1)), "start" === b.axisY.position ? (k.x1 = j.left + f, k.x2 = Math.max(h - j.right, k.x1 + 1)) : (k.x1 = j.left, k.x2 = Math.max(h - j.right - f, k.x1 + 1))) : (k.x1 = j.left, k.x2 = Math.max(h - j.right, k.x1 + 1), k.y2 = j.top, k.y1 = Math.max(i - j.bottom, k.y2 + 1)), k }, c.createGrid = function (a, b, d, e, f, g, h, i) { var j = {}; j[d.units.pos + "1"] = a, j[d.units.pos + "2"] = a, j[d.counterUnits.pos + "1"] = e, j[d.counterUnits.pos + "2"] = e + f; var k = g.elem("line", j, h.join(" ")); i.emit("draw", c.extend({ type: "grid", axis: d, index: b, group: g, element: k }, j)) }, c.createGridBackground = function (a, b, c, d) { var e = a.elem("rect", { x: b.x1, y: b.y2, width: b.width(), height: b.height() }, c, !0); d.emit("draw", { type: "gridBackground", group: a, element: e }) }, c.createLabel = function (a, d, e, f, g, h, i, j, k, l, m) { var n, o = {}; if (o[g.units.pos] = a + i[g.units.pos], o[g.counterUnits.pos] = i[g.counterUnits.pos], o[g.units.len] = d, o[g.counterUnits.len] = Math.max(0, h - 10), l) { var p = b.createElement("span"); p.className = k.join(" "), p.setAttribute("xmlns", c.namespaces.xhtml), p.innerText = f[e], p.style[g.units.len] = Math.round(o[g.units.len]) + "px", p.style[g.counterUnits.len] = Math.round(o[g.counterUnits.len]) + "px", n = j.foreignObject(p, c.extend({ style: "overflow: visible;" }, o)) } else n = j.elem("text", o, k.join(" ")).text(f[e]); m.emit("draw", c.extend({ type: "label", axis: g, index: e, group: j, element: n, text: f[e] }, o)) }, c.getSeriesOption = function (a, b, c) { if (a.name && b.series && b.series[a.name]) { var d = b.series[a.name]; return d.hasOwnProperty(c) ? d[c] : b[c] } return b[c] }, c.optionsProvider = function (b, d, e) { function f(b) { var f = h; if (h = c.extend({}, j), d) for (i = 0; i < d.length; i++) { var g = a.matchMedia(d[i][0]); g.matches && (h = c.extend(h, d[i][1])) } e && b && e.emit("optionsChanged", { previousOptions: f, currentOptions: h }) } function g() { k.forEach(function (a) { a.removeListener(f) }) } var h, i, j = c.extend({}, b), k = []; if (!a.matchMedia) throw "window.matchMedia not found! Make sure you're using a polyfill."; if (d) for (i = 0; i < d.length; i++) { var l = a.matchMedia(d[i][0]); l.addListener(f), k.push(l) } return f(), { removeMediaQueryListeners: g, getCurrentOptions: function () { return c.extend({}, h) } } }, c.splitIntoSegments = function (a, b, d) { var e = { increasingX: !1, fillHoles: !1 }; d = c.extend({}, e, d); for (var f = [], g = !0, h = 0; h < a.length; h += 2)void 0 === c.getMultiValue(b[h / 2].value) ? d.fillHoles || (g = !0) : (d.increasingX && h >= 2 && a[h] <= a[h - 2] && (g = !0), g && (f.push({ pathCoordinates: [], valueData: [] }), g = !1), f[f.length - 1].pathCoordinates.push(a[h], a[h + 1]), f[f.length - 1].valueData.push(b[h / 2])); return f } }(window, document, a), function (a, b, c) { "use strict"; c.Interpolation = {}, c.Interpolation.none = function (a) { var b = { fillHoles: !1 }; return a = c.extend({}, b, a), function (b, d) { for (var e = new c.Svg.Path, f = !0, g = 0; g < b.length; g += 2) { var h = b[g], i = b[g + 1], j = d[g / 2]; void 0 !== c.getMultiValue(j.value) ? (f ? e.move(h, i, !1, j) : e.line(h, i, !1, j), f = !1) : a.fillHoles || (f = !0) } return e } }, c.Interpolation.simple = function (a) { var b = { divisor: 2, fillHoles: !1 }; a = c.extend({}, b, a); var d = 1 / Math.max(1, a.divisor); return function (b, e) { for (var f, g, h, i = new c.Svg.Path, j = 0; j < b.length; j += 2) { var k = b[j], l = b[j + 1], m = (k - f) * d, n = e[j / 2]; void 0 !== n.value ? (void 0 === h ? i.move(k, l, !1, n) : i.curve(f + m, g, k - m, l, k, l, !1, n), f = k, g = l, h = n) : a.fillHoles || (f = k = h = void 0) } return i } }, c.Interpolation.cardinal = function (a) { var b = { tension: 1, fillHoles: !1 }; a = c.extend({}, b, a); var d = Math.min(1, Math.max(0, a.tension)), e = 1 - d; return function f(b, g) { var h = c.splitIntoSegments(b, g, { fillHoles: a.fillHoles }); if (h.length) { if (h.length > 1) { var i = []; return h.forEach(function (a) { i.push(f(a.pathCoordinates, a.valueData)) }), c.Svg.Path.join(i) } if (b = h[0].pathCoordinates, g = h[0].valueData, b.length <= 4) return c.Interpolation.none()(b, g); for (var j, k = (new c.Svg.Path).move(b[0], b[1], !1, g[0]), l = 0, m = b.length; m - 2 * !j > l; l += 2) { var n = [{ x: +b[l - 2], y: +b[l - 1] }, { x: +b[l], y: +b[l + 1] }, { x: +b[l + 2], y: +b[l + 3] }, { x: +b[l + 4], y: +b[l + 5] }]; j ? l ? m - 4 === l ? n[3] = { x: +b[0], y: +b[1] } : m - 2 === l && (n[2] = { x: +b[0], y: +b[1] }, n[3] = { x: +b[2], y: +b[3] }) : n[0] = { x: +b[m - 2], y: +b[m - 1] } : m - 4 === l ? n[3] = n[2] : l || (n[0] = { x: +b[l], y: +b[l + 1] }), k.curve(d * (-n[0].x + 6 * n[1].x + n[2].x) / 6 + e * n[2].x, d * (-n[0].y + 6 * n[1].y + n[2].y) / 6 + e * n[2].y, d * (n[1].x + 6 * n[2].x - n[3].x) / 6 + e * n[2].x, d * (n[1].y + 6 * n[2].y - n[3].y) / 6 + e * n[2].y, n[2].x, n[2].y, !1, g[(l + 2) / 2]) } return k } return c.Interpolation.none()([]) } }, c.Interpolation.monotoneCubic = function (a) { var b = { fillHoles: !1 }; return a = c.extend({}, b, a), function d(b, e) { var f = c.splitIntoSegments(b, e, { fillHoles: a.fillHoles, increasingX: !0 }); if (f.length) { if (f.length > 1) { var g = []; return f.forEach(function (a) { g.push(d(a.pathCoordinates, a.valueData)) }), c.Svg.Path.join(g) } if (b = f[0].pathCoordinates, e = f[0].valueData, b.length <= 4) return c.Interpolation.none()(b, e); var h, i, j = [], k = [], l = b.length / 2, m = [], n = [], o = [], p = []; for (h = 0; h < l; h++)j[h] = b[2 * h], k[h] = b[2 * h + 1]; for (h = 0; h < l - 1; h++)o[h] = k[h + 1] - k[h], p[h] = j[h + 1] - j[h], n[h] = o[h] / p[h]; for (m[0] = n[0], m[l - 1] = n[l - 2], h = 1; h < l - 1; h++)0 === n[h] || 0 === n[h - 1] || n[h - 1] > 0 != n[h] > 0 ? m[h] = 0 : (m[h] = 3 * (p[h - 1] + p[h]) / ((2 * p[h] + p[h - 1]) / n[h - 1] + (p[h] + 2 * p[h - 1]) / n[h]), isFinite(m[h]) || (m[h] = 0)); for (i = (new c.Svg.Path).move(j[0], k[0], !1, e[0]), h = 0; h < l - 1; h++)i.curve(j[h] + p[h] / 3, k[h] + m[h] * p[h] / 3, j[h + 1] - p[h] / 3, k[h + 1] - m[h + 1] * p[h] / 3, j[h + 1], k[h + 1], !1, e[h + 1]); return i } return c.Interpolation.none()([]) } }, c.Interpolation.step = function (a) { var b = { postpone: !0, fillHoles: !1 }; return a = c.extend({}, b, a), function (b, d) { for (var e, f, g, h = new c.Svg.Path, i = 0; i < b.length; i += 2) { var j = b[i], k = b[i + 1], l = d[i / 2]; void 0 !== l.value ? (void 0 === g ? h.move(j, k, !1, l) : (a.postpone ? h.line(j, f, !1, g) : h.line(e, k, !1, l), h.line(j, k, !1, l)), e = j, f = k, g = l) : a.fillHoles || (e = f = g = void 0) } return h } } }(window, document, a), function (a, b, c) { "use strict"; c.EventEmitter = function () { function a(a, b) { d[a] = d[a] || [], d[a].push(b) } function b(a, b) { d[a] && (b ? (d[a].splice(d[a].indexOf(b), 1), 0 === d[a].length && delete d[a]) : delete d[a]) } function c(a, b) { d[a] && d[a].forEach(function (a) { a(b) }), d["*"] && d["*"].forEach(function (c) { c(a, b) }) } var d = []; return { addEventHandler: a, removeEventHandler: b, emit: c } } }(window, document, a), function (a, b, c) { "use strict"; function d(a) { var b = []; if (a.length) for (var c = 0; c < a.length; c++)b.push(a[c]); return b } function e(a, b) { var d = b || this.prototype || c.Class, e = Object.create(d); c.Class.cloneDefinitions(e, a); var f = function () { var a, b = e.constructor || function () { }; return a = this === c ? Object.create(e) : this, b.apply(a, Array.prototype.slice.call(arguments, 0)), a }; return f.prototype = e, f["super"] = d, f.extend = this.extend, f } function f() { var a = d(arguments), b = a[0]; return a.splice(1, a.length - 1).forEach(function (a) { Object.getOwnPropertyNames(a).forEach(function (c) { delete b[c], Object.defineProperty(b, c, Object.getOwnPropertyDescriptor(a, c)) }) }), b } c.Class = { extend: e, cloneDefinitions: f } }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d) { return a && (this.data = a || {}, this.data.labels = this.data.labels || [], this.data.series = this.data.series || [], this.eventEmitter.emit("data", { type: "update", data: this.data })), b && (this.options = c.extend({}, d ? this.options : this.defaultOptions, b), this.initializeTimeoutId || (this.optionsProvider.removeMediaQueryListeners(), this.optionsProvider = c.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter))), this.initializeTimeoutId || this.createChart(this.optionsProvider.getCurrentOptions()), this } function e() { return this.initializeTimeoutId ? a.clearTimeout(this.initializeTimeoutId) : (a.removeEventListener("resize", this.resizeListener), this.optionsProvider.removeMediaQueryListeners()), this } function f(a, b) { return this.eventEmitter.addEventHandler(a, b), this } function g(a, b) { return this.eventEmitter.removeEventHandler(a, b), this } function h() { a.addEventListener("resize", this.resizeListener), this.optionsProvider = c.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter), this.eventEmitter.addEventHandler("optionsChanged", function () { this.update() }.bind(this)), this.options.plugins && this.options.plugins.forEach(function (a) { a instanceof Array ? a[0](this, a[1]) : a(this) }.bind(this)), this.eventEmitter.emit("data", { type: "initial", data: this.data }), this.createChart(this.optionsProvider.getCurrentOptions()), this.initializeTimeoutId = void 0 } function i(a, b, d, e, f) { this.container = c.querySelector(a), this.data = b || {}, this.data.labels = this.data.labels || [], this.data.series = this.data.series || [], this.defaultOptions = d, this.options = e, this.responsiveOptions = f, this.eventEmitter = c.EventEmitter(), this.supportsForeignObject = c.Svg.isSupported("Extensibility"), this.supportsAnimations = c.Svg.isSupported("AnimationEventsAttribute"), this.resizeListener = function () { this.update() }.bind(this), this.container && (this.container.__chartist__ && this.container.__chartist__.detach(), this.container.__chartist__ = this), this.initializeTimeoutId = setTimeout(h.bind(this), 0) } c.Base = c.Class.extend({ constructor: i, optionsProvider: void 0, container: void 0, svg: void 0, eventEmitter: void 0, createChart: function () { throw new Error("Base chart type can't be instantiated!") }, update: d, detach: e, on: f, off: g, version: c.version, supportsForeignObject: !1 }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, d, e, f, g) { a instanceof Element ? this._node = a : (this._node = b.createElementNS(c.namespaces.svg, a), "svg" === a && this.attr({ "xmlns:ct": c.namespaces.ct })), d && this.attr(d), e && this.addClass(e), f && (g && f._node.firstChild ? f._node.insertBefore(this._node, f._node.firstChild) : f._node.appendChild(this._node)) } function e(a, b) { return "string" == typeof a ? b ? this._node.getAttributeNS(b, a) : this._node.getAttribute(a) : (Object.keys(a).forEach(function (b) { if (void 0 !== a[b]) if (b.indexOf(":") !== -1) { var d = b.split(":"); this._node.setAttributeNS(c.namespaces[d[0]], b, a[b]) } else this._node.setAttribute(b, a[b]) }.bind(this)), this) } function f(a, b, d, e) { return new c.Svg(a, b, d, this, e) } function g() { return this._node.parentNode instanceof SVGElement ? new c.Svg(this._node.parentNode) : null } function h() { for (var a = this._node; "svg" !== a.nodeName;)a = a.parentNode; return new c.Svg(a) } function i(a) { var b = this._node.querySelector(a); return b ? new c.Svg(b) : null } function j(a) { var b = this._node.querySelectorAll(a); return b.length ? new c.Svg.List(b) : null } function k() { return this._node } function l(a, d, e, f) { if ("string" == typeof a) { var g = b.createElement("div"); g.innerHTML = a, a = g.firstChild } a.setAttribute("xmlns", c.namespaces.xmlns); var h = this.elem("foreignObject", d, e, f); return h._node.appendChild(a), h } function m(a) { return this._node.appendChild(b.createTextNode(a)), this } function n() { for (; this._node.firstChild;)this._node.removeChild(this._node.firstChild); return this } function o() { return this._node.parentNode.removeChild(this._node), this.parent() } function p(a) { return this._node.parentNode.replaceChild(a._node, this._node), a } function q(a, b) { return b && this._node.firstChild ? this._node.insertBefore(a._node, this._node.firstChild) : this._node.appendChild(a._node), this } function r() { return this._node.getAttribute("class") ? this._node.getAttribute("class").trim().split(/\s+/) : [] } function s(a) { return this._node.setAttribute("class", this.classes(this._node).concat(a.trim().split(/\s+/)).filter(function (a, b, c) { return c.indexOf(a) === b }).join(" ")), this } function t(a) { var b = a.trim().split(/\s+/); return this._node.setAttribute("class", this.classes(this._node).filter(function (a) { return b.indexOf(a) === -1 }).join(" ")), this } function u() { return this._node.setAttribute("class", ""), this } function v() { return this._node.getBoundingClientRect().height } function w() { return this._node.getBoundingClientRect().width } function x(a, b, d) { return void 0 === b && (b = !0), Object.keys(a).forEach(function (e) { function f(a, b) { var f, g, h, i = {}; a.easing && (h = a.easing instanceof Array ? a.easing : c.Svg.Easing[a.easing], delete a.easing), a.begin = c.ensureUnit(a.begin, "ms"), a.dur = c.ensureUnit(a.dur, "ms"), h && (a.calcMode = "spline", a.keySplines = h.join(" "), a.keyTimes = "0;1"), b && (a.fill = "freeze", i[e] = a.from, this.attr(i), g = c.quantity(a.begin || 0).value, a.begin = "indefinite"), f = this.elem("animate", c.extend({ attributeName: e }, a)), b && setTimeout(function () { try { f._node.beginElement() } catch (b) { i[e] = a.to, this.attr(i), f.remove() } }.bind(this), g), d && f._node.addEventListener("beginEvent", function () { d.emit("animationBegin", { element: this, animate: f._node, params: a }) }.bind(this)), f._node.addEventListener("endEvent", function () { d && d.emit("animationEnd", { element: this, animate: f._node, params: a }), b && (i[e] = a.to, this.attr(i), f.remove()) }.bind(this)) } a[e] instanceof Array ? a[e].forEach(function (a) { f.bind(this)(a, !1) }.bind(this)) : f.bind(this)(a[e], b) }.bind(this)), this } function y(a) { var b = this; this.svgElements = []; for (var d = 0; d < a.length; d++)this.svgElements.push(new c.Svg(a[d])); Object.keys(c.Svg.prototype).filter(function (a) { return ["constructor", "parent", "querySelector", "querySelectorAll", "replace", "append", "classes", "height", "width"].indexOf(a) === -1 }).forEach(function (a) { b[a] = function () { var d = Array.prototype.slice.call(arguments, 0); return b.svgElements.forEach(function (b) { c.Svg.prototype[a].apply(b, d) }), b } }) } c.Svg = c.Class.extend({ constructor: d, attr: e, elem: f, parent: g, root: h, querySelector: i, querySelectorAll: j, getNode: k, foreignObject: l, text: m, empty: n, remove: o, replace: p, append: q, classes: r, addClass: s, removeClass: t, removeAllClasses: u, height: v, width: w, animate: x }), c.Svg.isSupported = function (a) { return b.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#" + a, "1.1") }; var z = { easeInSine: [.47, 0, .745, .715], easeOutSine: [.39, .575, .565, 1], easeInOutSine: [.445, .05, .55, .95], easeInQuad: [.55, .085, .68, .53], easeOutQuad: [.25, .46, .45, .94], easeInOutQuad: [.455, .03, .515, .955], easeInCubic: [.55, .055, .675, .19], easeOutCubic: [.215, .61, .355, 1], easeInOutCubic: [.645, .045, .355, 1], easeInQuart: [.895, .03, .685, .22], easeOutQuart: [.165, .84, .44, 1], easeInOutQuart: [.77, 0, .175, 1], easeInQuint: [.755, .05, .855, .06], easeOutQuint: [.23, 1, .32, 1], easeInOutQuint: [.86, 0, .07, 1], easeInExpo: [.95, .05, .795, .035], easeOutExpo: [.19, 1, .22, 1], easeInOutExpo: [1, 0, 0, 1], easeInCirc: [.6, .04, .98, .335], easeOutCirc: [.075, .82, .165, 1], easeInOutCirc: [.785, .135, .15, .86], easeInBack: [.6, -.28, .735, .045], easeOutBack: [.175, .885, .32, 1.275], easeInOutBack: [.68, -.55, .265, 1.55] }; c.Svg.Easing = z, c.Svg.List = c.Class.extend({ constructor: y }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e, f, g) { var h = c.extend({ command: f ? a.toLowerCase() : a.toUpperCase() }, b, g ? { data: g } : {}); d.splice(e, 0, h) } function e(a, b) { a.forEach(function (c, d) { u[c.command.toLowerCase()].forEach(function (e, f) { b(c, e, d, f, a) }) }) } function f(a, b) { this.pathElements = [], this.pos = 0, this.close = a, this.options = c.extend({}, v, b) } function g(a) { return void 0 !== a ? (this.pos = Math.max(0, Math.min(this.pathElements.length, a)), this) : this.pos } function h(a) { return this.pathElements.splice(this.pos, a), this } function i(a, b, c, e) { return d("M", { x: +a, y: +b }, this.pathElements, this.pos++, c, e), this } function j(a, b, c, e) { return d("L", { x: +a, y: +b }, this.pathElements, this.pos++, c, e), this } function k(a, b, c, e, f, g, h, i) { return d("C", { x1: +a, y1: +b, x2: +c, y2: +e, x: +f, y: +g }, this.pathElements, this.pos++, h, i), this } function l(a, b, c, e, f, g, h, i, j) { return d("A", { rx: +a, ry: +b, xAr: +c, lAf: +e, sf: +f, x: +g, y: +h }, this.pathElements, this.pos++, i, j), this } function m(a) { var b = a.replace(/([A-Za-z])([0-9])/g, "$1 $2").replace(/([0-9])([A-Za-z])/g, "$1 $2").split(/[\s,]+/).reduce(function (a, b) { return b.match(/[A-Za-z]/) && a.push([]), a[a.length - 1].push(b), a }, []); "Z" === b[b.length - 1][0].toUpperCase() && b.pop(); var d = b.map(function (a) { var b = a.shift(), d = u[b.toLowerCase()]; return c.extend({ command: b }, d.reduce(function (b, c, d) { return b[c] = +a[d], b }, {})) }), e = [this.pos, 0]; return Array.prototype.push.apply(e, d), Array.prototype.splice.apply(this.pathElements, e), this.pos += d.length, this } function n() { var a = Math.pow(10, this.options.accuracy); return this.pathElements.reduce(function (b, c) { var d = u[c.command.toLowerCase()].map(function (b) { return this.options.accuracy ? Math.round(c[b] * a) / a : c[b] }.bind(this)); return b + c.command + d.join(",") }.bind(this), "") + (this.close ? "Z" : "") } function o(a, b) { return e(this.pathElements, function (c, d) { c[d] *= "x" === d[0] ? a : b }), this } function p(a, b) { return e(this.pathElements, function (c, d) { c[d] += "x" === d[0] ? a : b }), this } function q(a) { return e(this.pathElements, function (b, c, d, e, f) { var g = a(b, c, d, e, f); (g || 0 === g) && (b[c] = g) }), this } function r(a) { var b = new c.Svg.Path(a || this.close); return b.pos = this.pos, b.pathElements = this.pathElements.slice().map(function (a) { return c.extend({}, a) }), b.options = c.extend({}, this.options), b } function s(a) { var b = [new c.Svg.Path]; return this.pathElements.forEach(function (d) { d.command === a.toUpperCase() && 0 !== b[b.length - 1].pathElements.length && b.push(new c.Svg.Path), b[b.length - 1].pathElements.push(d) }), b } function t(a, b, d) { for (var e = new c.Svg.Path(b, d), f = 0; f < a.length; f++)for (var g = a[f], h = 0; h < g.pathElements.length; h++)e.pathElements.push(g.pathElements[h]); return e } var u = { m: ["x", "y"], l: ["x", "y"], c: ["x1", "y1", "x2", "y2", "x", "y"], a: ["rx", "ry", "xAr", "lAf", "sf", "x", "y"] }, v = { accuracy: 3 }; c.Svg.Path = c.Class.extend({ constructor: f, position: g, remove: h, move: i, line: j, curve: k, arc: l, scale: o, translate: p, transform: q, parse: m, stringify: n, clone: r, splitByCommand: s }), c.Svg.Path.elementDescriptions = u, c.Svg.Path.join = t }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, c, d) { this.units = a, this.counterUnits = a === f.x ? f.y : f.x, this.chartRect = b, this.axisLength = b[a.rectEnd] - b[a.rectStart], this.gridOffset = b[a.rectOffset], this.ticks = c, this.options = d } function e(a, b, d, e, f) { var g = e["axis" + this.units.pos.toUpperCase()], h = this.ticks.map(this.projectValue.bind(this)), i = this.ticks.map(g.labelInterpolationFnc); h.forEach(function (j, k) { var l, m = { x: 0, y: 0 }; l = h[k + 1] ? h[k + 1] - j : Math.max(this.axisLength - j, 30), c.isFalseyButZero(i[k]) && "" !== i[k] || ("x" === this.units.pos ? (j = this.chartRect.x1 + j, m.x = e.axisX.labelOffset.x, "start" === e.axisX.position ? m.y = this.chartRect.padding.top + e.axisX.labelOffset.y + (d ? 5 : 20) : m.y = this.chartRect.y1 + e.axisX.labelOffset.y + (d ? 5 : 20)) : (j = this.chartRect.y1 - j, m.y = e.axisY.labelOffset.y - (d ? l : 0), "start" === e.axisY.position ? m.x = d ? this.chartRect.padding.left + e.axisY.labelOffset.x : this.chartRect.x1 - 10 : m.x = this.chartRect.x2 + e.axisY.labelOffset.x + 10), g.showGrid && c.createGrid(j, k, this, this.gridOffset, this.chartRect[this.counterUnits.len](), a, [e.classNames.grid, e.classNames[this.units.dir]], f), g.showLabel && c.createLabel(j, l, k, i, this, g.offset, m, b, [e.classNames.label, e.classNames[this.units.dir], "start" === g.position ? e.classNames[g.position] : e.classNames.end], d, f)) }.bind(this)) } var f = { x: { pos: "x", len: "width", dir: "horizontal", rectStart: "x1", rectEnd: "x2", rectOffset: "y2" }, y: { pos: "y", len: "height", dir: "vertical", rectStart: "y2", rectEnd: "y1", rectOffset: "x1" } }; c.Axis = c.Class.extend({ constructor: d, createGridAndLabels: e, projectValue: function (a, b, c) { throw new Error("Base axis can't be instantiated!") } }), c.Axis.units = f }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { var f = e.highLow || c.getHighLow(b, e, a.pos); this.bounds = c.getBounds(d[a.rectEnd] - d[a.rectStart], f, e.scaleMinSpace || 20, e.onlyInteger), this.range = { min: this.bounds.min, max: this.bounds.max }, c.AutoScaleAxis["super"].constructor.call(this, a, d, this.bounds.values, e) } function e(a) { return this.axisLength * (+c.getMultiValue(a, this.units.pos) - this.bounds.min) / this.bounds.range } c.AutoScaleAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { var f = e.highLow || c.getHighLow(b, e, a.pos); this.divisor = e.divisor || 1, this.ticks = e.ticks || c.times(this.divisor).map(function (a, b) { return f.low + (f.high - f.low) / this.divisor * b }.bind(this)), this.ticks.sort(function (a, b) { return a - b }), this.range = { min: f.low, max: f.high }, c.FixedScaleAxis["super"].constructor.call(this, a, d, this.ticks, e), this.stepLength = this.axisLength / this.divisor } function e(a) { return this.axisLength * (+c.getMultiValue(a, this.units.pos) - this.range.min) / (this.range.max - this.range.min) } c.FixedScaleAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { c.StepAxis["super"].constructor.call(this, a, d, e.ticks, e); var f = Math.max(1, e.ticks.length - (e.stretch ? 1 : 0)); this.stepLength = this.axisLength / f } function e(a, b) { return this.stepLength * b } c.StepAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a) { var b = c.normalizeData(this.data, a.reverseData, !0); this.svg = c.createSvg(this.container, a.width, a.height, a.classNames.chart); var d, e, g = this.svg.elem("g").addClass(a.classNames.gridGroup), h = this.svg.elem("g"), i = this.svg.elem("g").addClass(a.classNames.labelGroup), j = c.createChartRect(this.svg, a, f.padding); d = void 0 === a.axisX.type ? new c.StepAxis(c.Axis.units.x, b.normalized.series, j, c.extend({}, a.axisX, { ticks: b.normalized.labels, stretch: a.fullWidth })) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, j, a.axisX), e = void 0 === a.axisY.type ? new c.AutoScaleAxis(c.Axis.units.y, b.normalized.series, j, c.extend({}, a.axisY, { high: c.isNumeric(a.high) ? a.high : a.axisY.high, low: c.isNumeric(a.low) ? a.low : a.axisY.low })) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, j, a.axisY), d.createGridAndLabels(g, i, this.supportsForeignObject, a, this.eventEmitter), e.createGridAndLabels(g, i, this.supportsForeignObject, a, this.eventEmitter), a.showGridBackground && c.createGridBackground(g, j, a.classNames.gridBackground, this.eventEmitter), b.raw.series.forEach(function (f, g) { var i = h.elem("g"); i.attr({ "ct:series-name": f.name, "ct:meta": c.serialize(f.meta) }), i.addClass([a.classNames.series, f.className || a.classNames.series + "-" + c.alphaNumerate(g)].join(" ")); var k = [], l = []; b.normalized.series[g].forEach(function (a, h) { var i = { x: j.x1 + d.projectValue(a, h, b.normalized.series[g]), y: j.y1 - e.projectValue(a, h, b.normalized.series[g]) }; k.push(i.x, i.y), l.push({ value: a, valueIndex: h, meta: c.getMetaData(f, h) }) }.bind(this)); var m = { lineSmooth: c.getSeriesOption(f, a, "lineSmooth"), showPoint: c.getSeriesOption(f, a, "showPoint"), showLine: c.getSeriesOption(f, a, "showLine"), showArea: c.getSeriesOption(f, a, "showArea"), areaBase: c.getSeriesOption(f, a, "areaBase") }, n = "function" == typeof m.lineSmooth ? m.lineSmooth : m.lineSmooth ? c.Interpolation.monotoneCubic() : c.Interpolation.none(), o = n(k, l); if (m.showPoint && o.pathElements.forEach(function (b) { var h = i.elem("line", { x1: b.x, y1: b.y, x2: b.x + .01, y2: b.y }, a.classNames.point).attr({ "ct:value": [b.data.value.x, b.data.value.y].filter(c.isNumeric).join(","), "ct:meta": c.serialize(b.data.meta) }); this.eventEmitter.emit("draw", { type: "point", value: b.data.value, index: b.data.valueIndex, meta: b.data.meta, series: f, seriesIndex: g, axisX: d, axisY: e, group: i, element: h, x: b.x, y: b.y }) }.bind(this)), m.showLine) { var p = i.elem("path", { d: o.stringify() }, a.classNames.line, !0); this.eventEmitter.emit("draw", { type: "line", values: b.normalized.series[g], path: o.clone(), chartRect: j, index: g, series: f, seriesIndex: g, seriesMeta: f.meta, axisX: d, axisY: e, group: i, element: p }) } if (m.showArea && e.range) { var q = Math.max(Math.min(m.areaBase, e.range.max), e.range.min), r = j.y1 - e.projectValue(q); o.splitByCommand("M").filter(function (a) { return a.pathElements.length > 1 }).map(function (a) { var b = a.pathElements[0], c = a.pathElements[a.pathElements.length - 1]; return a.clone(!0).position(0).remove(1).move(b.x, r).line(b.x, b.y).position(a.pathElements.length + 1).line(c.x, r) }).forEach(function (c) { var h = i.elem("path", { d: c.stringify() }, a.classNames.area, !0); this.eventEmitter.emit("draw", { type: "area", values: b.normalized.series[g], path: c.clone(), series: f, seriesIndex: g, axisX: d, axisY: e, chartRect: j, index: g, group: i, element: h }) }.bind(this)) } }.bind(this)), this.eventEmitter.emit("created", { bounds: e.bounds, chartRect: j, axisX: d, axisY: e, svg: this.svg, options: a }) } function e(a, b, d, e) { c.Line["super"].constructor.call(this, a, b, f, c.extend({}, f, d), e) } var f = { axisX: { offset: 30, position: "end", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, type: void 0 }, axisY: { offset: 40, position: "start", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, type: void 0, scaleMinSpace: 20, onlyInteger: !1 }, width: void 0, height: void 0, showLine: !0, showPoint: !0, showArea: !1, areaBase: 0, lineSmooth: !0, showGridBackground: !1, low: void 0, high: void 0, chartPadding: { top: 15, right: 15, bottom: 5, left: 10 }, fullWidth: !1, reverseData: !1, classNames: { chart: "ct-chart-line", label: "ct-label", labelGroup: "ct-labels", series: "ct-series", line: "ct-line", point: "ct-point", area: "ct-area", grid: "ct-grid", gridGroup: "ct-grids", gridBackground: "ct-grid-background", vertical: "ct-vertical", horizontal: "ct-horizontal", start: "ct-start", end: "ct-end" } }; c.Line = c.Base.extend({ constructor: e, createChart: d }) }(window, document, a), function (a, b, c) { - "use strict"; function d(a) { - var b, d; a.distributeSeries ? (b = c.normalizeData(this.data, a.reverseData, a.horizontalBars ? "x" : "y"), b.normalized.series = b.normalized.series.map(function (a) { return [a] })) : b = c.normalizeData(this.data, a.reverseData, a.horizontalBars ? "x" : "y"), this.svg = c.createSvg(this.container, a.width, a.height, a.classNames.chart + (a.horizontalBars ? " " + a.classNames.horizontalBars : "")); var e = this.svg.elem("g").addClass(a.classNames.gridGroup), g = this.svg.elem("g"), h = this.svg.elem("g").addClass(a.classNames.labelGroup); if (a.stackBars && 0 !== b.normalized.series.length) { - var i = c.serialMap(b.normalized.series, function () { - return Array.prototype.slice.call(arguments).map(function (a) { return a }).reduce(function (a, b) { return { x: a.x + (b && b.x) || 0, y: a.y + (b && b.y) || 0 } }, { x: 0, y: 0 }) - }); d = c.getHighLow([i], a, a.horizontalBars ? "x" : "y") - } else d = c.getHighLow(b.normalized.series, a, a.horizontalBars ? "x" : "y"); d.high = +a.high || (0 === a.high ? 0 : d.high), d.low = +a.low || (0 === a.low ? 0 : d.low); var j, k, l, m, n, o = c.createChartRect(this.svg, a, f.padding); k = a.distributeSeries && a.stackBars ? b.normalized.labels.slice(0, 1) : b.normalized.labels, a.horizontalBars ? (j = m = void 0 === a.axisX.type ? new c.AutoScaleAxis(c.Axis.units.x, b.normalized.series, o, c.extend({}, a.axisX, { highLow: d, referenceValue: 0 })) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, o, c.extend({}, a.axisX, { highLow: d, referenceValue: 0 })), l = n = void 0 === a.axisY.type ? new c.StepAxis(c.Axis.units.y, b.normalized.series, o, { ticks: k }) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, o, a.axisY)) : (l = m = void 0 === a.axisX.type ? new c.StepAxis(c.Axis.units.x, b.normalized.series, o, { ticks: k }) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, o, a.axisX), j = n = void 0 === a.axisY.type ? new c.AutoScaleAxis(c.Axis.units.y, b.normalized.series, o, c.extend({}, a.axisY, { highLow: d, referenceValue: 0 })) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, o, c.extend({}, a.axisY, { highLow: d, referenceValue: 0 }))); var p = a.horizontalBars ? o.x1 + j.projectValue(0) : o.y1 - j.projectValue(0), q = []; l.createGridAndLabels(e, h, this.supportsForeignObject, a, this.eventEmitter), j.createGridAndLabels(e, h, this.supportsForeignObject, a, this.eventEmitter), a.showGridBackground && c.createGridBackground(e, o, a.classNames.gridBackground, this.eventEmitter), b.raw.series.forEach(function (d, e) { var f, h, i = e - (b.raw.series.length - 1) / 2; f = a.distributeSeries && !a.stackBars ? l.axisLength / b.normalized.series.length / 2 : a.distributeSeries && a.stackBars ? l.axisLength / 2 : l.axisLength / b.normalized.series[e].length / 2, h = g.elem("g"), h.attr({ "ct:series-name": d.name, "ct:meta": c.serialize(d.meta) }), h.addClass([a.classNames.series, d.className || a.classNames.series + "-" + c.alphaNumerate(e)].join(" ")), b.normalized.series[e].forEach(function (g, k) { var r, s, t, u; if (u = a.distributeSeries && !a.stackBars ? e : a.distributeSeries && a.stackBars ? 0 : k, r = a.horizontalBars ? { x: o.x1 + j.projectValue(g && g.x ? g.x : 0, k, b.normalized.series[e]), y: o.y1 - l.projectValue(g && g.y ? g.y : 0, u, b.normalized.series[e]) } : { x: o.x1 + l.projectValue(g && g.x ? g.x : 0, u, b.normalized.series[e]), y: o.y1 - j.projectValue(g && g.y ? g.y : 0, k, b.normalized.series[e]) }, l instanceof c.StepAxis && (l.options.stretch || (r[l.units.pos] += f * (a.horizontalBars ? -1 : 1)), r[l.units.pos] += a.stackBars || a.distributeSeries ? 0 : i * a.seriesBarDistance * (a.horizontalBars ? -1 : 1)), t = q[k] || p, q[k] = t - (p - r[l.counterUnits.pos]), void 0 !== g) { var v = {}; v[l.units.pos + "1"] = r[l.units.pos], v[l.units.pos + "2"] = r[l.units.pos], !a.stackBars || "accumulate" !== a.stackMode && a.stackMode ? (v[l.counterUnits.pos + "1"] = p, v[l.counterUnits.pos + "2"] = r[l.counterUnits.pos]) : (v[l.counterUnits.pos + "1"] = t, v[l.counterUnits.pos + "2"] = q[k]), v.x1 = Math.min(Math.max(v.x1, o.x1), o.x2), v.x2 = Math.min(Math.max(v.x2, o.x1), o.x2), v.y1 = Math.min(Math.max(v.y1, o.y2), o.y1), v.y2 = Math.min(Math.max(v.y2, o.y2), o.y1); var w = c.getMetaData(d, k); s = h.elem("line", v, a.classNames.bar).attr({ "ct:value": [g.x, g.y].filter(c.isNumeric).join(","), "ct:meta": c.serialize(w) }), this.eventEmitter.emit("draw", c.extend({ type: "bar", value: g, index: k, meta: w, series: d, seriesIndex: e, axisX: m, axisY: n, chartRect: o, group: h, element: s }, v)) } }.bind(this)) }.bind(this)), this.eventEmitter.emit("created", { bounds: j.bounds, chartRect: o, axisX: m, axisY: n, svg: this.svg, options: a }) - } function e(a, b, d, e) { c.Bar["super"].constructor.call(this, a, b, f, c.extend({}, f, d), e) } var f = { axisX: { offset: 30, position: "end", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, scaleMinSpace: 30, onlyInteger: !1 }, axisY: { offset: 40, position: "start", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, scaleMinSpace: 20, onlyInteger: !1 }, width: void 0, height: void 0, high: void 0, low: void 0, referenceValue: 0, chartPadding: { top: 15, right: 15, bottom: 5, left: 10 }, seriesBarDistance: 15, stackBars: !1, stackMode: "accumulate", horizontalBars: !1, distributeSeries: !1, reverseData: !1, showGridBackground: !1, classNames: { chart: "ct-chart-bar", horizontalBars: "ct-horizontal-bars", label: "ct-label", labelGroup: "ct-labels", series: "ct-series", bar: "ct-bar", grid: "ct-grid", gridGroup: "ct-grids", gridBackground: "ct-grid-background", vertical: "ct-vertical", horizontal: "ct-horizontal", start: "ct-start", end: "ct-end" } }; c.Bar = c.Base.extend({ constructor: e, createChart: d }) - }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, c) { var d = b.x > a.x; return d && "explode" === c || !d && "implode" === c ? "start" : d && "implode" === c || !d && "explode" === c ? "end" : "middle" } function e(a) { var b, e, f, h, i, j = c.normalizeData(this.data), k = [], l = a.startAngle; this.svg = c.createSvg(this.container, a.width, a.height, a.donut ? a.classNames.chartDonut : a.classNames.chartPie), e = c.createChartRect(this.svg, a, g.padding), f = Math.min(e.width() / 2, e.height() / 2), i = a.total || j.normalized.series.reduce(function (a, b) { return a + b }, 0); var m = c.quantity(a.donutWidth); "%" === m.unit && (m.value *= f / 100), f -= a.donut && !a.donutSolid ? m.value / 2 : 0, h = "outside" === a.labelPosition || a.donut && !a.donutSolid ? f : "center" === a.labelPosition ? 0 : a.donutSolid ? f - m.value / 2 : f / 2, h += a.labelOffset; var n = { x: e.x1 + e.width() / 2, y: e.y2 + e.height() / 2 }, o = 1 === j.raw.series.filter(function (a) { return a.hasOwnProperty("value") ? 0 !== a.value : 0 !== a }).length; j.raw.series.forEach(function (a, b) { k[b] = this.svg.elem("g", null, null) }.bind(this)), a.showLabel && (b = this.svg.elem("g", null, null)), j.raw.series.forEach(function (e, g) { if (0 !== j.normalized.series[g] || !a.ignoreEmptyValues) { k[g].attr({ "ct:series-name": e.name }), k[g].addClass([a.classNames.series, e.className || a.classNames.series + "-" + c.alphaNumerate(g)].join(" ")); var p = i > 0 ? l + j.normalized.series[g] / i * 360 : 0, q = Math.max(0, l - (0 === g || o ? 0 : .2)); p - q >= 359.99 && (p = q + 359.99); var r, s, t, u = c.polarToCartesian(n.x, n.y, f, q), v = c.polarToCartesian(n.x, n.y, f, p), w = new c.Svg.Path(!a.donut || a.donutSolid).move(v.x, v.y).arc(f, f, 0, p - l > 180, 0, u.x, u.y); a.donut ? a.donutSolid && (t = f - m.value, r = c.polarToCartesian(n.x, n.y, t, l - (0 === g || o ? 0 : .2)), s = c.polarToCartesian(n.x, n.y, t, p), w.line(r.x, r.y), w.arc(t, t, 0, p - l > 180, 1, s.x, s.y)) : w.line(n.x, n.y); var x = a.classNames.slicePie; a.donut && (x = a.classNames.sliceDonut, a.donutSolid && (x = a.classNames.sliceDonutSolid)); var y = k[g].elem("path", { d: w.stringify() }, x); if (y.attr({ "ct:value": j.normalized.series[g], "ct:meta": c.serialize(e.meta) }), a.donut && !a.donutSolid && (y._node.style.strokeWidth = m.value + "px"), this.eventEmitter.emit("draw", { type: "slice", value: j.normalized.series[g], totalDataSum: i, index: g, meta: e.meta, series: e, group: k[g], element: y, path: w.clone(), center: n, radius: f, startAngle: l, endAngle: p }), a.showLabel) { var z; z = 1 === j.raw.series.length ? { x: n.x, y: n.y } : c.polarToCartesian(n.x, n.y, h, l + (p - l) / 2); var A; A = j.normalized.labels && !c.isFalseyButZero(j.normalized.labels[g]) ? j.normalized.labels[g] : j.normalized.series[g]; var B = a.labelInterpolationFnc(A, g); if (B || 0 === B) { var C = b.elem("text", { dx: z.x, dy: z.y, "text-anchor": d(n, z, a.labelDirection) }, a.classNames.label).text("" + B); this.eventEmitter.emit("draw", { type: "label", index: g, group: b, element: C, text: "" + B, x: z.x, y: z.y }) } } l = p } }.bind(this)), this.eventEmitter.emit("created", { chartRect: e, svg: this.svg, options: a }) } function f(a, b, d, e) { c.Pie["super"].constructor.call(this, a, b, g, c.extend({}, g, d), e) } var g = { width: void 0, height: void 0, chartPadding: 5, classNames: { chartPie: "ct-chart-pie", chartDonut: "ct-chart-donut", series: "ct-series", slicePie: "ct-slice-pie", sliceDonut: "ct-slice-donut", sliceDonutSolid: "ct-slice-donut-solid", label: "ct-label" }, startAngle: 0, total: void 0, donut: !1, donutSolid: !1, donutWidth: 60, showLabel: !0, labelOffset: 0, labelPosition: "inside", labelInterpolationFnc: c.noop, labelDirection: "neutral", reverseData: !1, ignoreEmptyValues: !1 }; c.Pie = c.Base.extend({ constructor: f, createChart: e, determineAnchorPosition: d }) }(window, document, a), a -}); - -var i, l, selectedLine = null; - -/* Navigate to hash without browser history entry */ -var navigateToHash = function () { - if (window.history !== undefined && window.history.replaceState !== undefined) { - window.history.replaceState(undefined, undefined, this.getAttribute("href")); - } -}; - -var hashLinks = document.getElementsByClassName('navigatetohash'); -for (i = 0, l = hashLinks.length; i < l; i++) { - hashLinks[i].addEventListener('click', navigateToHash); -} - -/* Switch test method */ -var switchTestMethod = function () { - var method = this.getAttribute("value"); - console.log("Selected test method: " + method); - - var lines, i, l, coverageData, lineAnalysis, cells; - - lines = document.querySelectorAll('.lineAnalysis tr'); - - for (i = 1, l = lines.length; i < l; i++) { - coverageData = JSON.parse(lines[i].getAttribute('data-coverage').replace(/'/g, '"')); - lineAnalysis = coverageData[method]; - cells = lines[i].querySelectorAll('td'); - if (lineAnalysis === undefined) { - lineAnalysis = coverageData.AllTestMethods; - if (lineAnalysis.LVS !== 'gray') { - cells[0].setAttribute('class', 'red'); - cells[1].innerText = cells[1].textContent = '0'; - cells[4].setAttribute('class', 'lightred'); - } - } else { - cells[0].setAttribute('class', lineAnalysis.LVS); - cells[1].innerText = cells[1].textContent = lineAnalysis.VC; - cells[4].setAttribute('class', 'light' + lineAnalysis.LVS); - } - } -}; - -var testMethods = document.getElementsByClassName('switchtestmethod'); -for (i = 0, l = testMethods.length; i < l; i++) { - testMethods[i].addEventListener('change', switchTestMethod); -} - -/* Highlight test method by line */ -var toggleLine = function () { - if (selectedLine === this) { - selectedLine = null; - } else { - selectedLine = null; - unhighlightTestMethods(); - highlightTestMethods.call(this); - selectedLine = this; - } - -}; -var highlightTestMethods = function () { - if (selectedLine !== null) { - return; - } - - var lineAnalysis; - var coverageData = JSON.parse(this.getAttribute('data-coverage').replace(/'/g, '"')); - var testMethods = document.getElementsByClassName('testmethod'); - - for (i = 0, l = testMethods.length; i < l; i++) { - lineAnalysis = coverageData[testMethods[i].id]; - if (lineAnalysis === undefined) { - testMethods[i].className = testMethods[i].className.replace(/\s*light.+/g, ""); - } else { - testMethods[i].className += ' light' + lineAnalysis.LVS; - } - } -}; -var unhighlightTestMethods = function () { - if (selectedLine !== null) { - return; - } - - var testMethods = document.getElementsByClassName('testmethod'); - for (i = 0, l = testMethods.length; i < l; i++) { - testMethods[i].className = testMethods[i].className.replace(/\s*light.+/g, ""); - } -}; -var coverableLines = document.getElementsByClassName('coverableline'); -for (i = 0, l = coverableLines.length; i < l; i++) { - coverableLines[i].addEventListener('click', toggleLine); - coverableLines[i].addEventListener('mouseenter', highlightTestMethods); - coverableLines[i].addEventListener('mouseleave', unhighlightTestMethods); -} - -/* History charts */ -var renderChart = function (chart) { - // Remove current children (e.g. PNG placeholder) - while (chart.firstChild) { - chart.firstChild.remove(); - } - - var chartData = window[chart.getAttribute('data-data')]; - var options = { - axisY: { - type: undefined, - onlyInteger: true - }, - lineSmooth: false, - low: 0, - high: 100, - scaleMinSpace: 20, - onlyInteger: true, - fullWidth: true - }; - var lineChart = new Chartist.Line(chart, { - labels: [], - series: chartData.series - }, options); - - /* Zoom */ - var zoomButtonDiv = document.createElement("div"); - zoomButtonDiv.className = "toggleZoom"; - var zoomButtonLink = document.createElement("a"); - zoomButtonLink.setAttribute("href", ""); - var zoomButtonText = document.createElement("i"); - zoomButtonText.className = "icon-search-plus"; - - zoomButtonLink.appendChild(zoomButtonText); - zoomButtonDiv.appendChild(zoomButtonLink); - - chart.appendChild(zoomButtonDiv); - - zoomButtonDiv.addEventListener('click', function (event) { - event.preventDefault(); - - if (options.axisY.type === undefined) { - options.axisY.type = Chartist.AutoScaleAxis; - zoomButtonText.className = "icon-search-minus"; - } else { - options.axisY.type = undefined; - zoomButtonText.className = "icon-search-plus"; - } - - lineChart.update(null, options); - }); - - var tooltip = document.createElement("div"); - tooltip.className = "tooltip"; - - chart.appendChild(tooltip); - - /* Tooltips */ - var showToolTip = function () { - var point = this; - var index = [].slice.call(chart.getElementsByClassName('ct-point')).indexOf(point); - - tooltip.innerHTML = chartData.tooltips[index % chartData.tooltips.length]; - tooltip.style.display = 'block'; - }; - - var moveToolTip = function (event) { - var box = chart.getBoundingClientRect(); - var left = event.pageX - box.left - window.pageXOffset; - var top = event.pageY - box.top - window.pageYOffset; - - tooltip.style.left = left - tooltip.offsetWidth / 2 - 5 + 'px'; - tooltip.style.top = top - tooltip.offsetHeight - 40 + 'px'; - }; - - var hideToolTip = function () { - tooltip.style.display = 'none'; - }; - - chart.addEventListener('mousemove', moveToolTip); - - lineChart.on('created', function () { - var chartPoints = chart.getElementsByClassName('ct-point'); - for (i = 0, l = chartPoints.length; i < l; i++) { - chartPoints[i].addEventListener('mousemove', showToolTip); - chartPoints[i].addEventListener('mouseout', hideToolTip); - } - }); -}; - -var charts = document.getElementsByClassName('historychart'); -for (i = 0, l = charts.length; i < l; i++) { - renderChart(charts[i]); -} \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/coverage.xml b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/coverage.xml deleted file mode 100644 index dbfc7f84..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/coverage.xml +++ /dev/null @@ -1,1270 +0,0 @@ - - - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll - 2021-02-09T23:13:42Z - mscorlib - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\vstest.console.exe - 2021-03-10T01:18:39.6988681Z - vstest.console - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll - 2021-03-10T01:18:39.1758657Z - Microsoft.TestPlatform.CoreUtilities - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll - 2020-10-08T02:21:33.8194762Z - System - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.Client.dll - 2021-03-10T01:18:39.3608699Z - Microsoft.VisualStudio.TestPlatform.Client - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.Common.dll - 2021-03-10T01:18:39.3648679Z - Microsoft.VisualStudio.TestPlatform.Common - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll - 2021-02-09T23:13:40.5754059Z - System.Core - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll - 2019-12-07T09:10:37.7737543Z - System.Xml - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.Utilities.dll - 2021-03-10T01:18:39.1838668Z - Microsoft.TestPlatform.Utilities - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\NuGet.Frameworks.dll - 2021-03-10T01:18:39.455872Z - NuGet.Frameworks - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll - 2020-06-05T05:04:05.335002Z - System.Configuration - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.Extensions.FileSystemGlobbing.dll - 2021-03-10T01:18:39.1398684Z - Microsoft.Extensions.FileSystemGlobbing - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CrossPlatEngine.dll - 2021-03-10T01:18:39.1798685Z - Microsoft.TestPlatform.CrossPlatEngine - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.Cci.dll - 2021-03-10T01:18:40.1568669Z - Microsoft.Cci - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll - 2019-12-07T09:10:34.4923358Z - System.Runtime - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.InteropServices\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.InteropServices.dll - 2019-12-07T09:10:35.9300981Z - System.Runtime.InteropServices - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll - 2019-12-07T09:10:34.5705678Z - System.Collections - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.TestPlatform.Extensions.BlameDataCollector.dll - 2021-03-10T01:18:38.7708669Z - Microsoft.TestPlatform.Extensions.BlameDataCollector - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.TestPlatform.Extensions.EventLogCollector.dll - 2021-03-10T01:18:38.7728666Z - Microsoft.TestPlatform.Extensions.EventLogCollector - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.TestPlatform.TestHostRuntimeProvider.dll - 2021-03-10T01:18:38.7748692Z - Microsoft.TestPlatform.TestHostRuntimeProvider - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.ArchitectureTools.PEReader.dll - 2021-03-10T01:18:39.2018677Z - Microsoft.VisualStudio.ArchitectureTools.PEReader - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.Coverage.Interprocess.dll - 2021-03-10T01:18:38.7888677Z - Microsoft.VisualStudio.Coverage.Interprocess - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\netstandard\v4.0_2.0.0.0__cc7b13ffcd2ddd51\netstandard.dll - 2019-12-07T09:10:37.6330785Z - netstandard - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.ValueTuple\v4.0_4.0.0.0__cc7b13ffcd2ddd51\System.ValueTuple.dll - 2019-12-07T09:10:37.742541Z - System.ValueTuple - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.Fakes.DataCollector.dll - 2021-03-10T01:18:40.1588666Z - Microsoft.VisualStudio.Fakes.DataCollector - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.CodedWebTestAdapter.dll - 2021-03-10T01:18:38.7898664Z - Microsoft.VisualStudio.TestPlatform.Extensions.CodedWebTestAdapter - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.TmiAdapter.dll - 2021-03-10T01:18:38.7968662Z - Microsoft.VisualStudio.TestPlatform.Extensions.TmiAdapter - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.Common.dll - 2021-03-10T01:18:39.2388684Z - Microsoft.VisualStudio.QualityTools.Common - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.GenericTestAdapter.dll - 2021-03-10T01:18:38.7918661Z - Microsoft.VisualStudio.TestPlatform.Extensions.GenericTestAdapter - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger.dll - 2021-03-10T01:18:38.792866Z - Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.OrderedTestAdapter.dll - 2021-03-10T01:18:38.7948677Z - Microsoft.VisualStudio.TestPlatform.Extensions.OrderedTestAdapter - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.dll - 2021-03-10T01:18:38.7998656Z - Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.VSTestIntegration.dll - 2021-03-10T01:18:38.8038671Z - Microsoft.VisualStudio.TestPlatform.Extensions.VSTestIntegration - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll - 2021-03-10T01:18:39.3488657Z - Microsoft.VisualStudio.QualityTools.UnitTestFramework - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll - 2020-09-04T22:37:08Z - System.Data - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.WebTestAdapter.dll - 2021-03-10T01:18:38.8048665Z - Microsoft.VisualStudio.TestPlatform.Extensions.WebTestAdapter - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.CppUnitTestFramework.ComInterfaces.dll - 2021-03-10T01:18:38.8068659Z - Microsoft.VisualStudio.TestTools.CppUnitTestFramework.ComInterfaces - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.CppUnitTestFramework.CppUnitTestExtension.dll - 2021-03-10T01:18:38.8078663Z - Microsoft.VisualStudio.TestTools.CppUnitTestFramework.CppUnitTestExtension - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.DataCollection.MediaRecorder.Model.dll - 2021-03-10T01:18:38.8098657Z - Microsoft.VisualStudio.TestTools.DataCollection.MediaRecorder.Model - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TestTools.DataCollection.VideoRecorderCollector.dll - 2021-03-10T01:18:38.8118659Z - Microsoft.VisualStudio.TestTools.DataCollection.VideoRecorderCollector - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Microsoft.VisualStudio.TraceDataCollector.dll - 2021-03-10T01:18:38.8168685Z - Microsoft.VisualStudio.TraceDataCollector - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.IntelliTrace.Core.dll - 2021-03-10T01:18:39.1678662Z - Microsoft.IntelliTrace.Core - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\System.Reflection.Metadata.dll - 2021-03-10T01:18:39.5908676Z - System.Reflection.Metadata - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.dll - 2019-12-07T09:10:34.5552678Z - System.IO - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\System.Collections.Immutable.dll - 2021-03-10T01:18:39.5778692Z - System.Collections.Immutable - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll - 2019-12-07T09:10:36.0237545Z - System.Reflection - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO.FileSystem\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.FileSystem.dll - 2019-12-07T09:10:36.0095245Z - System.IO.FileSystem - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO.MemoryMappedFiles\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.MemoryMappedFiles.dll - 2019-12-07T09:10:34.5552678Z - System.IO.MemoryMappedFiles - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Handles\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Handles.dll - 2019-12-07T09:10:37.6643716Z - System.Runtime.Handles - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll - 2019-12-07T09:10:37.7113237Z - System.Reflection.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll - 2019-12-07T09:10:34.5860246Z - System.Threading - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Text.Encoding\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Text.Encoding.dll - 2019-12-07T09:10:36.0705812Z - System.Text.Encoding - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll - 2019-12-07T09:10:37.6174386Z - System.Runtime.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Text.Encoding.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Text.Encoding.Extensions.dll - 2019-12-07T09:10:36.1495949Z - System.Text.Encoding.Extensions - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.Fakes.dll - 2021-03-10T01:18:40.1608669Z - Microsoft.VisualStudio.TestPlatform.Fakes - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CommunicationUtilities.dll - 2021-03-10T01:18:39.1748663Z - Microsoft.TestPlatform.CommunicationUtilities - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Newtonsoft.Json.dll - 2021-03-10T01:18:39.451869Z - Newtonsoft.Json - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll - 2018-06-05T04:51:36Z - Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Resources.ResourceManager\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Resources.ResourceManager.dll - 2019-12-07T09:10:37.6960843Z - System.Resources.ResourceManager - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - 2018-06-05T04:47:02Z - Microsoft.VisualStudio.TestPlatform.TestFramework - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll - 2018-06-05T04:50:06Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.ComponentModel.Composition\v4.0_4.0.0.0__b77a5c561934e089\System.ComponentModel.Composition.dll - 2019-12-07T09:10:36.1023351Z - System.ComponentModel.Composition - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll - 2019-12-07T09:10:36.086204Z - System.Linq - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml.ReaderWriter\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Xml.ReaderWriter.dll - 2019-12-07T09:10:34.5705678Z - System.Xml.ReaderWriter - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml.XDocument\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Xml.XDocument.dll - 2019-12-07T09:10:34.5552678Z - System.Xml.XDocument - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml.Linq\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.Linq.dll - 2019-12-07T09:10:35.962301Z - System.Xml.Linq - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll - 2019-12-07T09:10:36.086204Z - System.Globalization - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\UnitTestProject1.dll - 2019-04-19T07:50:12.1584913Z - UnitTestProject1 - - - - - - - <Module> - - - - - UnitTestProject1.UnitTest1 - - - - 100663297 - System.Void UnitTestProject1.UnitTest1::TestMethod1() - - - - - - - - - - - - - - 100663298 - System.Void UnitTestProject1.UnitTest1::TestMethod2() - - - - - - - - - - - - - - 100663299 - System.Void UnitTestProject1.UnitTest1::.ctor() - - - - - - - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll - 2020-10-08T02:21:33.8194762Z - System - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll - 2021-03-10T01:18:39.1758657Z - Microsoft.TestPlatform.CoreUtilities - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll - 2020-10-08T02:21:33.8194762Z - System - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll - 2018-06-05T04:51:36Z - Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll - 2019-12-07T09:10:34.4923358Z - System.Runtime - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - 2018-06-05T04:47:02Z - Microsoft.VisualStudio.TestPlatform.TestFramework - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll - 2019-12-07T09:10:34.5705678Z - System.Collections - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll - 2018-06-05T04:50:06Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll - 2019-12-07T09:10:36.0237545Z - System.Reflection - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll - 2019-12-07T09:10:36.086204Z - System.Globalization - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll - 2019-12-07T09:10:36.086204Z - System.Linq - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll - 2021-02-09T23:13:40.5754059Z - System.Core - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll - 2019-12-07T09:10:34.5860246Z - System.Threading - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll - 2019-12-07T09:10:37.7113237Z - System.Reflection.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll - 2019-12-07T09:10:34.5705678Z - System.Threading.Tasks - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll - 2019-12-07T09:10:37.6174386Z - System.Runtime.Extensions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - 2018-06-05T04:53:44Z - Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Serialization\v4.0_4.0.0.0__b77a5c561934e089\System.Runtime.Serialization.dll - 2020-08-05T02:39:55.9298663Z - System.Runtime.Serialization - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject2\bin\debug\UnitTestProject2.dll - 2019-04-19T07:50:12.1584913Z - UnitTestProject2 - - - - - - - <Module> - - - - - UnitTestProject2.UnitTest1 - - - - 100663297 - System.Void UnitTestProject2.UnitTest1::TestMethod1() - - - - - - - - - - - - 100663298 - System.Void UnitTestProject2.UnitTest1::.ctor() - - - - - - - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll - 2021-03-10T01:18:39.1758657Z - Microsoft.TestPlatform.CoreUtilities - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll - 2020-10-08T02:21:33.8194762Z - System - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll - 2018-06-05T04:51:36Z - Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll - 2019-12-07T09:10:34.4923358Z - System.Runtime - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - 2018-06-05T04:47:02Z - Microsoft.VisualStudio.TestPlatform.TestFramework - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll - 2019-12-07T09:10:34.5705678Z - System.Collections - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll - 2018-06-05T04:50:06Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll - 2019-12-07T09:10:36.0237545Z - System.Reflection - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll - 2019-12-07T09:10:36.086204Z - System.Globalization - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll - 2019-12-07T09:10:36.086204Z - System.Linq - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll - 2021-02-09T23:13:40.5754059Z - System.Core - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll - 2019-12-07T09:10:34.5860246Z - System.Threading - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll - 2019-12-07T09:10:37.7113237Z - System.Reflection.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll - 2019-12-07T09:10:34.5705678Z - System.Threading.Tasks - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll - 2019-12-07T09:10:37.6174386Z - System.Runtime.Extensions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - 2018-06-05T04:53:44Z - Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Serialization\v4.0_4.0.0.0__b77a5c561934e089\System.Runtime.Serialization.dll - 2020-08-05T02:39:55.9298663Z - System.Runtime.Serialization - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections.Concurrent\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.Concurrent.dll - 2019-12-07T09:10:34.5552678Z - System.Collections.Concurrent - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll - 2019-12-07T09:10:34.5705678Z - System.Threading.Tasks - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll - 2021-03-10T01:18:39.1758657Z - Microsoft.TestPlatform.CoreUtilities - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll - 2020-10-08T02:21:33.8194762Z - System - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll - 2018-06-05T04:51:36Z - Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll - 2019-12-07T09:10:34.4923358Z - System.Runtime - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - 2018-06-05T04:47:02Z - Microsoft.VisualStudio.TestPlatform.TestFramework - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll - 2019-12-07T09:10:34.5705678Z - System.Collections - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll - 2019-12-07T09:10:36.0237545Z - System.Reflection - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll - 2019-12-07T09:10:37.6174386Z - System.Runtime.Extensions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll - 2018-06-05T04:50:06Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll - 2019-12-07T09:10:36.086204Z - System.Linq - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll - 2021-02-09T23:13:40.5754059Z - System.Core - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll - 2019-12-07T09:10:36.086204Z - System.Globalization - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.dll - 2019-12-07T09:10:34.5552678Z - System.IO - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - 2018-06-05T04:53:44Z - Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll - 2020-09-04T22:37:08Z - System.Data - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll - 2019-12-07T09:10:34.5860246Z - System.Threading - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll - 2019-12-07T09:10:37.7113237Z - System.Reflection.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll - 2019-12-07T09:10:34.5705678Z - System.Threading.Tasks - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll - 2020-06-05T05:04:05.335002Z - System.Configuration - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll - 2019-12-07T09:10:37.7737543Z - System.Xml - - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\ClassLibrary1.dll - 2019-04-19T06:37:47.6105603Z - ClassLibrary1 - - - - - - - <Module> - - - - - ClassLibrary1.Calculation - - - - 100663297 - System.Int32 ClassLibrary1.Calculation::Add(System.Int32,System.Int32) - - - - - - - - - - - - 100663298 - System.Int32 ClassLibrary1.Calculation::Sub(System.Int32,System.Int32) - - - - - - - - - - - - 100663299 - System.Int32 ClassLibrary1.Calculation::Sub1(System.Int32,System.Int32) - - - - - - - - - - - - 100663300 - System.Void ClassLibrary1.Calculation::.ctor() - - - - - - - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Resources.ResourceManager\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Resources.ResourceManager.dll - 2019-12-07T09:10:37.6960843Z - System.Resources.ResourceManager - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll - 2021-03-10T01:18:39.1758657Z - Microsoft.TestPlatform.CoreUtilities - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Text.RegularExpressions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Text.RegularExpressions.dll - 2019-12-07T09:10:34.4610177Z - System.Text.RegularExpressions - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.CoreUtilities.dll - 2021-03-10T01:18:39.1758657Z - Microsoft.TestPlatform.CoreUtilities - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.TestPlatform.PlatformAbstractions.dll - 2021-03-10T01:18:39.1818661Z - Microsoft.TestPlatform.PlatformAbstractions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.dll - 2018-06-05T04:54:38Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll - 2020-10-08T02:21:33.8194762Z - System - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll - 2018-06-05T04:51:36Z - Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll - 2019-12-07T09:10:34.4923358Z - System.Runtime - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - 2018-06-05T04:47:02Z - Microsoft.VisualStudio.TestPlatform.TestFramework - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll - 2019-12-07T09:10:34.5705678Z - System.Collections - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.dll - 2019-12-07T09:10:36.0237545Z - System.Reflection - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll - 2019-12-07T09:10:37.6174386Z - System.Runtime.Extensions - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.dll - 2018-06-05T04:50:06Z - Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll - 2019-12-07T09:10:36.086204Z - System.Linq - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll - 2021-02-09T23:13:40.5754059Z - System.Core - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Globalization\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Globalization.dll - 2019-12-07T09:10:36.086204Z - System.Globalization - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.IO\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.IO.dll - 2019-12-07T09:10:34.5552678Z - System.IO - - - - E:\src\sample.dotblog\CodeCoverage\OpenCover\Lab.OpenCoverDemo\UnitTestProject1\bin\debug\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - 2018-06-05T04:53:44Z - Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll - 2020-09-04T22:37:08Z - System.Data - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll - 2019-12-07T09:10:34.5860246Z - System.Threading - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Reflection.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Reflection.Extensions.dll - 2019-12-07T09:10:37.7113237Z - System.Reflection.Extensions - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll - 2019-12-07T09:10:34.5705678Z - System.Threading.Tasks - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll - 2020-06-05T05:04:05.335002Z - System.Configuration - - - - C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll - 2019-12-07T09:10:37.7737543Z - System.Xml - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.ExecutionCommon.dll - 2021-03-10T01:18:39.2588672Z - Microsoft.VisualStudio.QualityTools.ExecutionCommon - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.Resource.dll - 2021-03-10T01:18:39.3078688Z - Microsoft.VisualStudio.QualityTools.Resource - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.QualityTools.WebTestFramework.dll - 2021-03-10T01:18:39.3588667Z - Microsoft.VisualStudio.QualityTools.WebTestFramework - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - 2021-03-10T01:18:39.3688664Z - Microsoft.VisualStudio.TestPlatform.ObjectModel - - - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_cube.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_cube.svg deleted file mode 100644 index 11b5cabf..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_cube.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_down-dir_active.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_down-dir_active.svg deleted file mode 100644 index d11cf041..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_down-dir_active.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_fork.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_fork.svg deleted file mode 100644 index f0148b3a..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_fork.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_info-circled.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_info-circled.svg deleted file mode 100644 index 252166bb..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_info-circled.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_minus.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_minus.svg deleted file mode 100644 index 3c30c365..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_minus.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_plus.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_plus.svg deleted file mode 100644 index 79327232..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_plus.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-minus.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-minus.svg deleted file mode 100644 index c174eb5e..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-minus.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-plus.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-plus.svg deleted file mode 100644 index 04b24ecc..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_search-plus.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir.svg deleted file mode 100644 index 567c11f3..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir_active.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir_active.svg deleted file mode 100644 index bb225544..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_up-dir_active.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_wrench.svg b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_wrench.svg deleted file mode 100644 index 0e9a8601..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/icon_wrench.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/index.htm b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/index.htm deleted file mode 100644 index f2f23f7e..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/index.htm +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - -Summary - Coverage Report - -
-

Summary

- ---- - - - - - - - - - - - - - - - -
Generated on:2021/3/26 - 下午 04:34:49
Parser:OpenCoverParser
Assemblies:3
Classes:3
Files:3
Covered lines:15
Uncovered lines:7
Coverable lines:22
Total lines:59
Line coverage:68.1% (15 of 22)
Covered branches:0
Total branches:0
Tag:Build.2019.11.11
-

Coverage History

-
- -

Risk Hotspots

- - -

No risk hotspots found.

-

Coverage

- - ----------- - - - - - - - - - -
NameCoveredUncoveredCoverableTotalLine coverageBranch coverage
ClassLibrary13691833.3%
  
 
ClassLibrary1.Calculation3691833.3%
  
 
UnitTestProject191102690%
  
 
UnitTestProject1.UnitTest191102690%
  
 
UnitTestProject230315100%
 
 
UnitTestProject2.UnitTest130315100%
 
 
-
-
- - \ No newline at end of file diff --git a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/main.js b/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/main.js deleted file mode 100644 index 160252eb..00000000 --- a/CodeCoverage/OpenCover/Lab.OpenCoverDemo/report/main.js +++ /dev/null @@ -1,274 +0,0 @@ -/* Chartist.js 0.11.0 - * Copyright © 2017 Gion Kunz - * Free to use under either the WTFPL license or the MIT license. - * https://raw.githubusercontent.com/gionkunz/chartist-js/master/LICENSE-WTFPL - * https://raw.githubusercontent.com/gionkunz/chartist-js/master/LICENSE-MIT - */ - -!function (a, b) { "function" == typeof define && define.amd ? define("Chartist", [], function () { return a.Chartist = b() }) : "object" == typeof module && module.exports ? module.exports = b() : a.Chartist = b() }(this, function () { - var a = { version: "0.11.0" }; return function (a, b, c) { "use strict"; c.namespaces = { svg: "http://www.w3.org/2000/svg", xmlns: "http://www.w3.org/2000/xmlns/", xhtml: "http://www.w3.org/1999/xhtml", xlink: "http://www.w3.org/1999/xlink", ct: "http://gionkunz.github.com/chartist-js/ct" }, c.noop = function (a) { return a }, c.alphaNumerate = function (a) { return String.fromCharCode(97 + a % 26) }, c.extend = function (a) { var b, d, e; for (a = a || {}, b = 1; b < arguments.length; b++) { d = arguments[b]; for (var f in d) e = d[f], "object" != typeof e || null === e || e instanceof Array ? a[f] = e : a[f] = c.extend(a[f], e) } return a }, c.replaceAll = function (a, b, c) { return a.replace(new RegExp(b, "g"), c) }, c.ensureUnit = function (a, b) { return "number" == typeof a && (a += b), a }, c.quantity = function (a) { if ("string" == typeof a) { var b = /^(\d+)\s*(.*)$/g.exec(a); return { value: +b[1], unit: b[2] || void 0 } } return { value: a } }, c.querySelector = function (a) { return a instanceof Node ? a : b.querySelector(a) }, c.times = function (a) { return Array.apply(null, new Array(a)) }, c.sum = function (a, b) { return a + (b ? b : 0) }, c.mapMultiply = function (a) { return function (b) { return b * a } }, c.mapAdd = function (a) { return function (b) { return b + a } }, c.serialMap = function (a, b) { var d = [], e = Math.max.apply(null, a.map(function (a) { return a.length })); return c.times(e).forEach(function (c, e) { var f = a.map(function (a) { return a[e] }); d[e] = b.apply(null, f) }), d }, c.roundWithPrecision = function (a, b) { var d = Math.pow(10, b || c.precision); return Math.round(a * d) / d }, c.precision = 8, c.escapingMap = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }, c.serialize = function (a) { return null === a || void 0 === a ? a : ("number" == typeof a ? a = "" + a : "object" == typeof a && (a = JSON.stringify({ data: a })), Object.keys(c.escapingMap).reduce(function (a, b) { return c.replaceAll(a, b, c.escapingMap[b]) }, a)) }, c.deserialize = function (a) { if ("string" != typeof a) return a; a = Object.keys(c.escapingMap).reduce(function (a, b) { return c.replaceAll(a, c.escapingMap[b], b) }, a); try { a = JSON.parse(a), a = void 0 !== a.data ? a.data : a } catch (b) { } return a }, c.createSvg = function (a, b, d, e) { var f; return b = b || "100%", d = d || "100%", Array.prototype.slice.call(a.querySelectorAll("svg")).filter(function (a) { return a.getAttributeNS(c.namespaces.xmlns, "ct") }).forEach(function (b) { a.removeChild(b) }), f = new c.Svg("svg").attr({ width: b, height: d }).addClass(e), f._node.style.width = b, f._node.style.height = d, a.appendChild(f._node), f }, c.normalizeData = function (a, b, d) { var e, f = { raw: a, normalized: {} }; return f.normalized.series = c.getDataArray({ series: a.series || [] }, b, d), e = f.normalized.series.every(function (a) { return a instanceof Array }) ? Math.max.apply(null, f.normalized.series.map(function (a) { return a.length })) : f.normalized.series.length, f.normalized.labels = (a.labels || []).slice(), Array.prototype.push.apply(f.normalized.labels, c.times(Math.max(0, e - f.normalized.labels.length)).map(function () { return "" })), b && c.reverseData(f.normalized), f }, c.safeHasProperty = function (a, b) { return null !== a && "object" == typeof a && a.hasOwnProperty(b) }, c.isDataHoleValue = function (a) { return null === a || void 0 === a || "number" == typeof a && isNaN(a) }, c.reverseData = function (a) { a.labels.reverse(), a.series.reverse(); for (var b = 0; b < a.series.length; b++)"object" == typeof a.series[b] && void 0 !== a.series[b].data ? a.series[b].data.reverse() : a.series[b] instanceof Array && a.series[b].reverse() }, c.getDataArray = function (a, b, d) { function e(a) { if (c.safeHasProperty(a, "value")) return e(a.value); if (c.safeHasProperty(a, "data")) return e(a.data); if (a instanceof Array) return a.map(e); if (!c.isDataHoleValue(a)) { if (d) { var b = {}; return "string" == typeof d ? b[d] = c.getNumberOrUndefined(a) : b.y = c.getNumberOrUndefined(a), b.x = a.hasOwnProperty("x") ? c.getNumberOrUndefined(a.x) : b.x, b.y = a.hasOwnProperty("y") ? c.getNumberOrUndefined(a.y) : b.y, b } return c.getNumberOrUndefined(a) } } return a.series.map(e) }, c.normalizePadding = function (a, b) { return b = b || 0, "number" == typeof a ? { top: a, right: a, bottom: a, left: a } : { top: "number" == typeof a.top ? a.top : b, right: "number" == typeof a.right ? a.right : b, bottom: "number" == typeof a.bottom ? a.bottom : b, left: "number" == typeof a.left ? a.left : b } }, c.getMetaData = function (a, b) { var c = a.data ? a.data[b] : a[b]; return c ? c.meta : void 0 }, c.orderOfMagnitude = function (a) { return Math.floor(Math.log(Math.abs(a)) / Math.LN10) }, c.projectLength = function (a, b, c) { return b / c.range * a }, c.getAvailableHeight = function (a, b) { return Math.max((c.quantity(b.height).value || a.height()) - (b.chartPadding.top + b.chartPadding.bottom) - b.axisX.offset, 0) }, c.getHighLow = function (a, b, d) { function e(a) { if (void 0 !== a) if (a instanceof Array) for (var b = 0; b < a.length; b++)e(a[b]); else { var c = d ? +a[d] : +a; g && c > f.high && (f.high = c), h && c < f.low && (f.low = c) } } b = c.extend({}, b, d ? b["axis" + d.toUpperCase()] : {}); var f = { high: void 0 === b.high ? -Number.MAX_VALUE : +b.high, low: void 0 === b.low ? Number.MAX_VALUE : +b.low }, g = void 0 === b.high, h = void 0 === b.low; return (g || h) && e(a), (b.referenceValue || 0 === b.referenceValue) && (f.high = Math.max(b.referenceValue, f.high), f.low = Math.min(b.referenceValue, f.low)), f.high <= f.low && (0 === f.low ? f.high = 1 : f.low < 0 ? f.high = 0 : f.high > 0 ? f.low = 0 : (f.high = 1, f.low = 0)), f }, c.isNumeric = function (a) { return null !== a && isFinite(a) }, c.isFalseyButZero = function (a) { return !a && 0 !== a }, c.getNumberOrUndefined = function (a) { return c.isNumeric(a) ? +a : void 0 }, c.isMultiValue = function (a) { return "object" == typeof a && ("x" in a || "y" in a) }, c.getMultiValue = function (a, b) { return c.isMultiValue(a) ? c.getNumberOrUndefined(a[b || "y"]) : c.getNumberOrUndefined(a) }, c.rho = function (a) { function b(a, c) { return a % c === 0 ? c : b(c, a % c) } function c(a) { return a * a + 1 } if (1 === a) return a; var d, e = 2, f = 2; if (a % 2 === 0) return 2; do e = c(e) % a, f = c(c(f)) % a, d = b(Math.abs(e - f), a); while (1 === d); return d }, c.getBounds = function (a, b, d, e) { function f(a, b) { return a === (a += b) && (a *= 1 + (b > 0 ? o : -o)), a } var g, h, i, j = 0, k = { high: b.high, low: b.low }; k.valueRange = k.high - k.low, k.oom = c.orderOfMagnitude(k.valueRange), k.step = Math.pow(10, k.oom), k.min = Math.floor(k.low / k.step) * k.step, k.max = Math.ceil(k.high / k.step) * k.step, k.range = k.max - k.min, k.numberOfSteps = Math.round(k.range / k.step); var l = c.projectLength(a, k.step, k), m = l < d, n = e ? c.rho(k.range) : 0; if (e && c.projectLength(a, 1, k) >= d) k.step = 1; else if (e && n < k.step && c.projectLength(a, n, k) >= d) k.step = n; else for (; ;) { if (m && c.projectLength(a, k.step, k) <= d) k.step *= 2; else { if (m || !(c.projectLength(a, k.step / 2, k) >= d)) break; if (k.step /= 2, e && k.step % 1 !== 0) { k.step *= 2; break } } if (j++ > 1e3) throw new Error("Exceeded maximum number of iterations while optimizing scale step!") } var o = 2.221e-16; for (k.step = Math.max(k.step, o), h = k.min, i = k.max; h + k.step <= k.low;)h = f(h, k.step); for (; i - k.step >= k.high;)i = f(i, -k.step); k.min = h, k.max = i, k.range = k.max - k.min; var p = []; for (g = k.min; g <= k.max; g = f(g, k.step)) { var q = c.roundWithPrecision(g); q !== p[p.length - 1] && p.push(q) } return k.values = p, k }, c.polarToCartesian = function (a, b, c, d) { var e = (d - 90) * Math.PI / 180; return { x: a + c * Math.cos(e), y: b + c * Math.sin(e) } }, c.createChartRect = function (a, b, d) { var e = !(!b.axisX && !b.axisY), f = e ? b.axisY.offset : 0, g = e ? b.axisX.offset : 0, h = a.width() || c.quantity(b.width).value || 0, i = a.height() || c.quantity(b.height).value || 0, j = c.normalizePadding(b.chartPadding, d); h = Math.max(h, f + j.left + j.right), i = Math.max(i, g + j.top + j.bottom); var k = { padding: j, width: function () { return this.x2 - this.x1 }, height: function () { return this.y1 - this.y2 } }; return e ? ("start" === b.axisX.position ? (k.y2 = j.top + g, k.y1 = Math.max(i - j.bottom, k.y2 + 1)) : (k.y2 = j.top, k.y1 = Math.max(i - j.bottom - g, k.y2 + 1)), "start" === b.axisY.position ? (k.x1 = j.left + f, k.x2 = Math.max(h - j.right, k.x1 + 1)) : (k.x1 = j.left, k.x2 = Math.max(h - j.right - f, k.x1 + 1))) : (k.x1 = j.left, k.x2 = Math.max(h - j.right, k.x1 + 1), k.y2 = j.top, k.y1 = Math.max(i - j.bottom, k.y2 + 1)), k }, c.createGrid = function (a, b, d, e, f, g, h, i) { var j = {}; j[d.units.pos + "1"] = a, j[d.units.pos + "2"] = a, j[d.counterUnits.pos + "1"] = e, j[d.counterUnits.pos + "2"] = e + f; var k = g.elem("line", j, h.join(" ")); i.emit("draw", c.extend({ type: "grid", axis: d, index: b, group: g, element: k }, j)) }, c.createGridBackground = function (a, b, c, d) { var e = a.elem("rect", { x: b.x1, y: b.y2, width: b.width(), height: b.height() }, c, !0); d.emit("draw", { type: "gridBackground", group: a, element: e }) }, c.createLabel = function (a, d, e, f, g, h, i, j, k, l, m) { var n, o = {}; if (o[g.units.pos] = a + i[g.units.pos], o[g.counterUnits.pos] = i[g.counterUnits.pos], o[g.units.len] = d, o[g.counterUnits.len] = Math.max(0, h - 10), l) { var p = b.createElement("span"); p.className = k.join(" "), p.setAttribute("xmlns", c.namespaces.xhtml), p.innerText = f[e], p.style[g.units.len] = Math.round(o[g.units.len]) + "px", p.style[g.counterUnits.len] = Math.round(o[g.counterUnits.len]) + "px", n = j.foreignObject(p, c.extend({ style: "overflow: visible;" }, o)) } else n = j.elem("text", o, k.join(" ")).text(f[e]); m.emit("draw", c.extend({ type: "label", axis: g, index: e, group: j, element: n, text: f[e] }, o)) }, c.getSeriesOption = function (a, b, c) { if (a.name && b.series && b.series[a.name]) { var d = b.series[a.name]; return d.hasOwnProperty(c) ? d[c] : b[c] } return b[c] }, c.optionsProvider = function (b, d, e) { function f(b) { var f = h; if (h = c.extend({}, j), d) for (i = 0; i < d.length; i++) { var g = a.matchMedia(d[i][0]); g.matches && (h = c.extend(h, d[i][1])) } e && b && e.emit("optionsChanged", { previousOptions: f, currentOptions: h }) } function g() { k.forEach(function (a) { a.removeListener(f) }) } var h, i, j = c.extend({}, b), k = []; if (!a.matchMedia) throw "window.matchMedia not found! Make sure you're using a polyfill."; if (d) for (i = 0; i < d.length; i++) { var l = a.matchMedia(d[i][0]); l.addListener(f), k.push(l) } return f(), { removeMediaQueryListeners: g, getCurrentOptions: function () { return c.extend({}, h) } } }, c.splitIntoSegments = function (a, b, d) { var e = { increasingX: !1, fillHoles: !1 }; d = c.extend({}, e, d); for (var f = [], g = !0, h = 0; h < a.length; h += 2)void 0 === c.getMultiValue(b[h / 2].value) ? d.fillHoles || (g = !0) : (d.increasingX && h >= 2 && a[h] <= a[h - 2] && (g = !0), g && (f.push({ pathCoordinates: [], valueData: [] }), g = !1), f[f.length - 1].pathCoordinates.push(a[h], a[h + 1]), f[f.length - 1].valueData.push(b[h / 2])); return f } }(window, document, a), function (a, b, c) { "use strict"; c.Interpolation = {}, c.Interpolation.none = function (a) { var b = { fillHoles: !1 }; return a = c.extend({}, b, a), function (b, d) { for (var e = new c.Svg.Path, f = !0, g = 0; g < b.length; g += 2) { var h = b[g], i = b[g + 1], j = d[g / 2]; void 0 !== c.getMultiValue(j.value) ? (f ? e.move(h, i, !1, j) : e.line(h, i, !1, j), f = !1) : a.fillHoles || (f = !0) } return e } }, c.Interpolation.simple = function (a) { var b = { divisor: 2, fillHoles: !1 }; a = c.extend({}, b, a); var d = 1 / Math.max(1, a.divisor); return function (b, e) { for (var f, g, h, i = new c.Svg.Path, j = 0; j < b.length; j += 2) { var k = b[j], l = b[j + 1], m = (k - f) * d, n = e[j / 2]; void 0 !== n.value ? (void 0 === h ? i.move(k, l, !1, n) : i.curve(f + m, g, k - m, l, k, l, !1, n), f = k, g = l, h = n) : a.fillHoles || (f = k = h = void 0) } return i } }, c.Interpolation.cardinal = function (a) { var b = { tension: 1, fillHoles: !1 }; a = c.extend({}, b, a); var d = Math.min(1, Math.max(0, a.tension)), e = 1 - d; return function f(b, g) { var h = c.splitIntoSegments(b, g, { fillHoles: a.fillHoles }); if (h.length) { if (h.length > 1) { var i = []; return h.forEach(function (a) { i.push(f(a.pathCoordinates, a.valueData)) }), c.Svg.Path.join(i) } if (b = h[0].pathCoordinates, g = h[0].valueData, b.length <= 4) return c.Interpolation.none()(b, g); for (var j, k = (new c.Svg.Path).move(b[0], b[1], !1, g[0]), l = 0, m = b.length; m - 2 * !j > l; l += 2) { var n = [{ x: +b[l - 2], y: +b[l - 1] }, { x: +b[l], y: +b[l + 1] }, { x: +b[l + 2], y: +b[l + 3] }, { x: +b[l + 4], y: +b[l + 5] }]; j ? l ? m - 4 === l ? n[3] = { x: +b[0], y: +b[1] } : m - 2 === l && (n[2] = { x: +b[0], y: +b[1] }, n[3] = { x: +b[2], y: +b[3] }) : n[0] = { x: +b[m - 2], y: +b[m - 1] } : m - 4 === l ? n[3] = n[2] : l || (n[0] = { x: +b[l], y: +b[l + 1] }), k.curve(d * (-n[0].x + 6 * n[1].x + n[2].x) / 6 + e * n[2].x, d * (-n[0].y + 6 * n[1].y + n[2].y) / 6 + e * n[2].y, d * (n[1].x + 6 * n[2].x - n[3].x) / 6 + e * n[2].x, d * (n[1].y + 6 * n[2].y - n[3].y) / 6 + e * n[2].y, n[2].x, n[2].y, !1, g[(l + 2) / 2]) } return k } return c.Interpolation.none()([]) } }, c.Interpolation.monotoneCubic = function (a) { var b = { fillHoles: !1 }; return a = c.extend({}, b, a), function d(b, e) { var f = c.splitIntoSegments(b, e, { fillHoles: a.fillHoles, increasingX: !0 }); if (f.length) { if (f.length > 1) { var g = []; return f.forEach(function (a) { g.push(d(a.pathCoordinates, a.valueData)) }), c.Svg.Path.join(g) } if (b = f[0].pathCoordinates, e = f[0].valueData, b.length <= 4) return c.Interpolation.none()(b, e); var h, i, j = [], k = [], l = b.length / 2, m = [], n = [], o = [], p = []; for (h = 0; h < l; h++)j[h] = b[2 * h], k[h] = b[2 * h + 1]; for (h = 0; h < l - 1; h++)o[h] = k[h + 1] - k[h], p[h] = j[h + 1] - j[h], n[h] = o[h] / p[h]; for (m[0] = n[0], m[l - 1] = n[l - 2], h = 1; h < l - 1; h++)0 === n[h] || 0 === n[h - 1] || n[h - 1] > 0 != n[h] > 0 ? m[h] = 0 : (m[h] = 3 * (p[h - 1] + p[h]) / ((2 * p[h] + p[h - 1]) / n[h - 1] + (p[h] + 2 * p[h - 1]) / n[h]), isFinite(m[h]) || (m[h] = 0)); for (i = (new c.Svg.Path).move(j[0], k[0], !1, e[0]), h = 0; h < l - 1; h++)i.curve(j[h] + p[h] / 3, k[h] + m[h] * p[h] / 3, j[h + 1] - p[h] / 3, k[h + 1] - m[h + 1] * p[h] / 3, j[h + 1], k[h + 1], !1, e[h + 1]); return i } return c.Interpolation.none()([]) } }, c.Interpolation.step = function (a) { var b = { postpone: !0, fillHoles: !1 }; return a = c.extend({}, b, a), function (b, d) { for (var e, f, g, h = new c.Svg.Path, i = 0; i < b.length; i += 2) { var j = b[i], k = b[i + 1], l = d[i / 2]; void 0 !== l.value ? (void 0 === g ? h.move(j, k, !1, l) : (a.postpone ? h.line(j, f, !1, g) : h.line(e, k, !1, l), h.line(j, k, !1, l)), e = j, f = k, g = l) : a.fillHoles || (e = f = g = void 0) } return h } } }(window, document, a), function (a, b, c) { "use strict"; c.EventEmitter = function () { function a(a, b) { d[a] = d[a] || [], d[a].push(b) } function b(a, b) { d[a] && (b ? (d[a].splice(d[a].indexOf(b), 1), 0 === d[a].length && delete d[a]) : delete d[a]) } function c(a, b) { d[a] && d[a].forEach(function (a) { a(b) }), d["*"] && d["*"].forEach(function (c) { c(a, b) }) } var d = []; return { addEventHandler: a, removeEventHandler: b, emit: c } } }(window, document, a), function (a, b, c) { "use strict"; function d(a) { var b = []; if (a.length) for (var c = 0; c < a.length; c++)b.push(a[c]); return b } function e(a, b) { var d = b || this.prototype || c.Class, e = Object.create(d); c.Class.cloneDefinitions(e, a); var f = function () { var a, b = e.constructor || function () { }; return a = this === c ? Object.create(e) : this, b.apply(a, Array.prototype.slice.call(arguments, 0)), a }; return f.prototype = e, f["super"] = d, f.extend = this.extend, f } function f() { var a = d(arguments), b = a[0]; return a.splice(1, a.length - 1).forEach(function (a) { Object.getOwnPropertyNames(a).forEach(function (c) { delete b[c], Object.defineProperty(b, c, Object.getOwnPropertyDescriptor(a, c)) }) }), b } c.Class = { extend: e, cloneDefinitions: f } }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d) { return a && (this.data = a || {}, this.data.labels = this.data.labels || [], this.data.series = this.data.series || [], this.eventEmitter.emit("data", { type: "update", data: this.data })), b && (this.options = c.extend({}, d ? this.options : this.defaultOptions, b), this.initializeTimeoutId || (this.optionsProvider.removeMediaQueryListeners(), this.optionsProvider = c.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter))), this.initializeTimeoutId || this.createChart(this.optionsProvider.getCurrentOptions()), this } function e() { return this.initializeTimeoutId ? a.clearTimeout(this.initializeTimeoutId) : (a.removeEventListener("resize", this.resizeListener), this.optionsProvider.removeMediaQueryListeners()), this } function f(a, b) { return this.eventEmitter.addEventHandler(a, b), this } function g(a, b) { return this.eventEmitter.removeEventHandler(a, b), this } function h() { a.addEventListener("resize", this.resizeListener), this.optionsProvider = c.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter), this.eventEmitter.addEventHandler("optionsChanged", function () { this.update() }.bind(this)), this.options.plugins && this.options.plugins.forEach(function (a) { a instanceof Array ? a[0](this, a[1]) : a(this) }.bind(this)), this.eventEmitter.emit("data", { type: "initial", data: this.data }), this.createChart(this.optionsProvider.getCurrentOptions()), this.initializeTimeoutId = void 0 } function i(a, b, d, e, f) { this.container = c.querySelector(a), this.data = b || {}, this.data.labels = this.data.labels || [], this.data.series = this.data.series || [], this.defaultOptions = d, this.options = e, this.responsiveOptions = f, this.eventEmitter = c.EventEmitter(), this.supportsForeignObject = c.Svg.isSupported("Extensibility"), this.supportsAnimations = c.Svg.isSupported("AnimationEventsAttribute"), this.resizeListener = function () { this.update() }.bind(this), this.container && (this.container.__chartist__ && this.container.__chartist__.detach(), this.container.__chartist__ = this), this.initializeTimeoutId = setTimeout(h.bind(this), 0) } c.Base = c.Class.extend({ constructor: i, optionsProvider: void 0, container: void 0, svg: void 0, eventEmitter: void 0, createChart: function () { throw new Error("Base chart type can't be instantiated!") }, update: d, detach: e, on: f, off: g, version: c.version, supportsForeignObject: !1 }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, d, e, f, g) { a instanceof Element ? this._node = a : (this._node = b.createElementNS(c.namespaces.svg, a), "svg" === a && this.attr({ "xmlns:ct": c.namespaces.ct })), d && this.attr(d), e && this.addClass(e), f && (g && f._node.firstChild ? f._node.insertBefore(this._node, f._node.firstChild) : f._node.appendChild(this._node)) } function e(a, b) { return "string" == typeof a ? b ? this._node.getAttributeNS(b, a) : this._node.getAttribute(a) : (Object.keys(a).forEach(function (b) { if (void 0 !== a[b]) if (b.indexOf(":") !== -1) { var d = b.split(":"); this._node.setAttributeNS(c.namespaces[d[0]], b, a[b]) } else this._node.setAttribute(b, a[b]) }.bind(this)), this) } function f(a, b, d, e) { return new c.Svg(a, b, d, this, e) } function g() { return this._node.parentNode instanceof SVGElement ? new c.Svg(this._node.parentNode) : null } function h() { for (var a = this._node; "svg" !== a.nodeName;)a = a.parentNode; return new c.Svg(a) } function i(a) { var b = this._node.querySelector(a); return b ? new c.Svg(b) : null } function j(a) { var b = this._node.querySelectorAll(a); return b.length ? new c.Svg.List(b) : null } function k() { return this._node } function l(a, d, e, f) { if ("string" == typeof a) { var g = b.createElement("div"); g.innerHTML = a, a = g.firstChild } a.setAttribute("xmlns", c.namespaces.xmlns); var h = this.elem("foreignObject", d, e, f); return h._node.appendChild(a), h } function m(a) { return this._node.appendChild(b.createTextNode(a)), this } function n() { for (; this._node.firstChild;)this._node.removeChild(this._node.firstChild); return this } function o() { return this._node.parentNode.removeChild(this._node), this.parent() } function p(a) { return this._node.parentNode.replaceChild(a._node, this._node), a } function q(a, b) { return b && this._node.firstChild ? this._node.insertBefore(a._node, this._node.firstChild) : this._node.appendChild(a._node), this } function r() { return this._node.getAttribute("class") ? this._node.getAttribute("class").trim().split(/\s+/) : [] } function s(a) { return this._node.setAttribute("class", this.classes(this._node).concat(a.trim().split(/\s+/)).filter(function (a, b, c) { return c.indexOf(a) === b }).join(" ")), this } function t(a) { var b = a.trim().split(/\s+/); return this._node.setAttribute("class", this.classes(this._node).filter(function (a) { return b.indexOf(a) === -1 }).join(" ")), this } function u() { return this._node.setAttribute("class", ""), this } function v() { return this._node.getBoundingClientRect().height } function w() { return this._node.getBoundingClientRect().width } function x(a, b, d) { return void 0 === b && (b = !0), Object.keys(a).forEach(function (e) { function f(a, b) { var f, g, h, i = {}; a.easing && (h = a.easing instanceof Array ? a.easing : c.Svg.Easing[a.easing], delete a.easing), a.begin = c.ensureUnit(a.begin, "ms"), a.dur = c.ensureUnit(a.dur, "ms"), h && (a.calcMode = "spline", a.keySplines = h.join(" "), a.keyTimes = "0;1"), b && (a.fill = "freeze", i[e] = a.from, this.attr(i), g = c.quantity(a.begin || 0).value, a.begin = "indefinite"), f = this.elem("animate", c.extend({ attributeName: e }, a)), b && setTimeout(function () { try { f._node.beginElement() } catch (b) { i[e] = a.to, this.attr(i), f.remove() } }.bind(this), g), d && f._node.addEventListener("beginEvent", function () { d.emit("animationBegin", { element: this, animate: f._node, params: a }) }.bind(this)), f._node.addEventListener("endEvent", function () { d && d.emit("animationEnd", { element: this, animate: f._node, params: a }), b && (i[e] = a.to, this.attr(i), f.remove()) }.bind(this)) } a[e] instanceof Array ? a[e].forEach(function (a) { f.bind(this)(a, !1) }.bind(this)) : f.bind(this)(a[e], b) }.bind(this)), this } function y(a) { var b = this; this.svgElements = []; for (var d = 0; d < a.length; d++)this.svgElements.push(new c.Svg(a[d])); Object.keys(c.Svg.prototype).filter(function (a) { return ["constructor", "parent", "querySelector", "querySelectorAll", "replace", "append", "classes", "height", "width"].indexOf(a) === -1 }).forEach(function (a) { b[a] = function () { var d = Array.prototype.slice.call(arguments, 0); return b.svgElements.forEach(function (b) { c.Svg.prototype[a].apply(b, d) }), b } }) } c.Svg = c.Class.extend({ constructor: d, attr: e, elem: f, parent: g, root: h, querySelector: i, querySelectorAll: j, getNode: k, foreignObject: l, text: m, empty: n, remove: o, replace: p, append: q, classes: r, addClass: s, removeClass: t, removeAllClasses: u, height: v, width: w, animate: x }), c.Svg.isSupported = function (a) { return b.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#" + a, "1.1") }; var z = { easeInSine: [.47, 0, .745, .715], easeOutSine: [.39, .575, .565, 1], easeInOutSine: [.445, .05, .55, .95], easeInQuad: [.55, .085, .68, .53], easeOutQuad: [.25, .46, .45, .94], easeInOutQuad: [.455, .03, .515, .955], easeInCubic: [.55, .055, .675, .19], easeOutCubic: [.215, .61, .355, 1], easeInOutCubic: [.645, .045, .355, 1], easeInQuart: [.895, .03, .685, .22], easeOutQuart: [.165, .84, .44, 1], easeInOutQuart: [.77, 0, .175, 1], easeInQuint: [.755, .05, .855, .06], easeOutQuint: [.23, 1, .32, 1], easeInOutQuint: [.86, 0, .07, 1], easeInExpo: [.95, .05, .795, .035], easeOutExpo: [.19, 1, .22, 1], easeInOutExpo: [1, 0, 0, 1], easeInCirc: [.6, .04, .98, .335], easeOutCirc: [.075, .82, .165, 1], easeInOutCirc: [.785, .135, .15, .86], easeInBack: [.6, -.28, .735, .045], easeOutBack: [.175, .885, .32, 1.275], easeInOutBack: [.68, -.55, .265, 1.55] }; c.Svg.Easing = z, c.Svg.List = c.Class.extend({ constructor: y }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e, f, g) { var h = c.extend({ command: f ? a.toLowerCase() : a.toUpperCase() }, b, g ? { data: g } : {}); d.splice(e, 0, h) } function e(a, b) { a.forEach(function (c, d) { u[c.command.toLowerCase()].forEach(function (e, f) { b(c, e, d, f, a) }) }) } function f(a, b) { this.pathElements = [], this.pos = 0, this.close = a, this.options = c.extend({}, v, b) } function g(a) { return void 0 !== a ? (this.pos = Math.max(0, Math.min(this.pathElements.length, a)), this) : this.pos } function h(a) { return this.pathElements.splice(this.pos, a), this } function i(a, b, c, e) { return d("M", { x: +a, y: +b }, this.pathElements, this.pos++, c, e), this } function j(a, b, c, e) { return d("L", { x: +a, y: +b }, this.pathElements, this.pos++, c, e), this } function k(a, b, c, e, f, g, h, i) { return d("C", { x1: +a, y1: +b, x2: +c, y2: +e, x: +f, y: +g }, this.pathElements, this.pos++, h, i), this } function l(a, b, c, e, f, g, h, i, j) { return d("A", { rx: +a, ry: +b, xAr: +c, lAf: +e, sf: +f, x: +g, y: +h }, this.pathElements, this.pos++, i, j), this } function m(a) { var b = a.replace(/([A-Za-z])([0-9])/g, "$1 $2").replace(/([0-9])([A-Za-z])/g, "$1 $2").split(/[\s,]+/).reduce(function (a, b) { return b.match(/[A-Za-z]/) && a.push([]), a[a.length - 1].push(b), a }, []); "Z" === b[b.length - 1][0].toUpperCase() && b.pop(); var d = b.map(function (a) { var b = a.shift(), d = u[b.toLowerCase()]; return c.extend({ command: b }, d.reduce(function (b, c, d) { return b[c] = +a[d], b }, {})) }), e = [this.pos, 0]; return Array.prototype.push.apply(e, d), Array.prototype.splice.apply(this.pathElements, e), this.pos += d.length, this } function n() { var a = Math.pow(10, this.options.accuracy); return this.pathElements.reduce(function (b, c) { var d = u[c.command.toLowerCase()].map(function (b) { return this.options.accuracy ? Math.round(c[b] * a) / a : c[b] }.bind(this)); return b + c.command + d.join(",") }.bind(this), "") + (this.close ? "Z" : "") } function o(a, b) { return e(this.pathElements, function (c, d) { c[d] *= "x" === d[0] ? a : b }), this } function p(a, b) { return e(this.pathElements, function (c, d) { c[d] += "x" === d[0] ? a : b }), this } function q(a) { return e(this.pathElements, function (b, c, d, e, f) { var g = a(b, c, d, e, f); (g || 0 === g) && (b[c] = g) }), this } function r(a) { var b = new c.Svg.Path(a || this.close); return b.pos = this.pos, b.pathElements = this.pathElements.slice().map(function (a) { return c.extend({}, a) }), b.options = c.extend({}, this.options), b } function s(a) { var b = [new c.Svg.Path]; return this.pathElements.forEach(function (d) { d.command === a.toUpperCase() && 0 !== b[b.length - 1].pathElements.length && b.push(new c.Svg.Path), b[b.length - 1].pathElements.push(d) }), b } function t(a, b, d) { for (var e = new c.Svg.Path(b, d), f = 0; f < a.length; f++)for (var g = a[f], h = 0; h < g.pathElements.length; h++)e.pathElements.push(g.pathElements[h]); return e } var u = { m: ["x", "y"], l: ["x", "y"], c: ["x1", "y1", "x2", "y2", "x", "y"], a: ["rx", "ry", "xAr", "lAf", "sf", "x", "y"] }, v = { accuracy: 3 }; c.Svg.Path = c.Class.extend({ constructor: f, position: g, remove: h, move: i, line: j, curve: k, arc: l, scale: o, translate: p, transform: q, parse: m, stringify: n, clone: r, splitByCommand: s }), c.Svg.Path.elementDescriptions = u, c.Svg.Path.join = t }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, c, d) { this.units = a, this.counterUnits = a === f.x ? f.y : f.x, this.chartRect = b, this.axisLength = b[a.rectEnd] - b[a.rectStart], this.gridOffset = b[a.rectOffset], this.ticks = c, this.options = d } function e(a, b, d, e, f) { var g = e["axis" + this.units.pos.toUpperCase()], h = this.ticks.map(this.projectValue.bind(this)), i = this.ticks.map(g.labelInterpolationFnc); h.forEach(function (j, k) { var l, m = { x: 0, y: 0 }; l = h[k + 1] ? h[k + 1] - j : Math.max(this.axisLength - j, 30), c.isFalseyButZero(i[k]) && "" !== i[k] || ("x" === this.units.pos ? (j = this.chartRect.x1 + j, m.x = e.axisX.labelOffset.x, "start" === e.axisX.position ? m.y = this.chartRect.padding.top + e.axisX.labelOffset.y + (d ? 5 : 20) : m.y = this.chartRect.y1 + e.axisX.labelOffset.y + (d ? 5 : 20)) : (j = this.chartRect.y1 - j, m.y = e.axisY.labelOffset.y - (d ? l : 0), "start" === e.axisY.position ? m.x = d ? this.chartRect.padding.left + e.axisY.labelOffset.x : this.chartRect.x1 - 10 : m.x = this.chartRect.x2 + e.axisY.labelOffset.x + 10), g.showGrid && c.createGrid(j, k, this, this.gridOffset, this.chartRect[this.counterUnits.len](), a, [e.classNames.grid, e.classNames[this.units.dir]], f), g.showLabel && c.createLabel(j, l, k, i, this, g.offset, m, b, [e.classNames.label, e.classNames[this.units.dir], "start" === g.position ? e.classNames[g.position] : e.classNames.end], d, f)) }.bind(this)) } var f = { x: { pos: "x", len: "width", dir: "horizontal", rectStart: "x1", rectEnd: "x2", rectOffset: "y2" }, y: { pos: "y", len: "height", dir: "vertical", rectStart: "y2", rectEnd: "y1", rectOffset: "x1" } }; c.Axis = c.Class.extend({ constructor: d, createGridAndLabels: e, projectValue: function (a, b, c) { throw new Error("Base axis can't be instantiated!") } }), c.Axis.units = f }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { var f = e.highLow || c.getHighLow(b, e, a.pos); this.bounds = c.getBounds(d[a.rectEnd] - d[a.rectStart], f, e.scaleMinSpace || 20, e.onlyInteger), this.range = { min: this.bounds.min, max: this.bounds.max }, c.AutoScaleAxis["super"].constructor.call(this, a, d, this.bounds.values, e) } function e(a) { return this.axisLength * (+c.getMultiValue(a, this.units.pos) - this.bounds.min) / this.bounds.range } c.AutoScaleAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { var f = e.highLow || c.getHighLow(b, e, a.pos); this.divisor = e.divisor || 1, this.ticks = e.ticks || c.times(this.divisor).map(function (a, b) { return f.low + (f.high - f.low) / this.divisor * b }.bind(this)), this.ticks.sort(function (a, b) { return a - b }), this.range = { min: f.low, max: f.high }, c.FixedScaleAxis["super"].constructor.call(this, a, d, this.ticks, e), this.stepLength = this.axisLength / this.divisor } function e(a) { return this.axisLength * (+c.getMultiValue(a, this.units.pos) - this.range.min) / (this.range.max - this.range.min) } c.FixedScaleAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, d, e) { c.StepAxis["super"].constructor.call(this, a, d, e.ticks, e); var f = Math.max(1, e.ticks.length - (e.stretch ? 1 : 0)); this.stepLength = this.axisLength / f } function e(a, b) { return this.stepLength * b } c.StepAxis = c.Axis.extend({ constructor: d, projectValue: e }) }(window, document, a), function (a, b, c) { "use strict"; function d(a) { var b = c.normalizeData(this.data, a.reverseData, !0); this.svg = c.createSvg(this.container, a.width, a.height, a.classNames.chart); var d, e, g = this.svg.elem("g").addClass(a.classNames.gridGroup), h = this.svg.elem("g"), i = this.svg.elem("g").addClass(a.classNames.labelGroup), j = c.createChartRect(this.svg, a, f.padding); d = void 0 === a.axisX.type ? new c.StepAxis(c.Axis.units.x, b.normalized.series, j, c.extend({}, a.axisX, { ticks: b.normalized.labels, stretch: a.fullWidth })) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, j, a.axisX), e = void 0 === a.axisY.type ? new c.AutoScaleAxis(c.Axis.units.y, b.normalized.series, j, c.extend({}, a.axisY, { high: c.isNumeric(a.high) ? a.high : a.axisY.high, low: c.isNumeric(a.low) ? a.low : a.axisY.low })) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, j, a.axisY), d.createGridAndLabels(g, i, this.supportsForeignObject, a, this.eventEmitter), e.createGridAndLabels(g, i, this.supportsForeignObject, a, this.eventEmitter), a.showGridBackground && c.createGridBackground(g, j, a.classNames.gridBackground, this.eventEmitter), b.raw.series.forEach(function (f, g) { var i = h.elem("g"); i.attr({ "ct:series-name": f.name, "ct:meta": c.serialize(f.meta) }), i.addClass([a.classNames.series, f.className || a.classNames.series + "-" + c.alphaNumerate(g)].join(" ")); var k = [], l = []; b.normalized.series[g].forEach(function (a, h) { var i = { x: j.x1 + d.projectValue(a, h, b.normalized.series[g]), y: j.y1 - e.projectValue(a, h, b.normalized.series[g]) }; k.push(i.x, i.y), l.push({ value: a, valueIndex: h, meta: c.getMetaData(f, h) }) }.bind(this)); var m = { lineSmooth: c.getSeriesOption(f, a, "lineSmooth"), showPoint: c.getSeriesOption(f, a, "showPoint"), showLine: c.getSeriesOption(f, a, "showLine"), showArea: c.getSeriesOption(f, a, "showArea"), areaBase: c.getSeriesOption(f, a, "areaBase") }, n = "function" == typeof m.lineSmooth ? m.lineSmooth : m.lineSmooth ? c.Interpolation.monotoneCubic() : c.Interpolation.none(), o = n(k, l); if (m.showPoint && o.pathElements.forEach(function (b) { var h = i.elem("line", { x1: b.x, y1: b.y, x2: b.x + .01, y2: b.y }, a.classNames.point).attr({ "ct:value": [b.data.value.x, b.data.value.y].filter(c.isNumeric).join(","), "ct:meta": c.serialize(b.data.meta) }); this.eventEmitter.emit("draw", { type: "point", value: b.data.value, index: b.data.valueIndex, meta: b.data.meta, series: f, seriesIndex: g, axisX: d, axisY: e, group: i, element: h, x: b.x, y: b.y }) }.bind(this)), m.showLine) { var p = i.elem("path", { d: o.stringify() }, a.classNames.line, !0); this.eventEmitter.emit("draw", { type: "line", values: b.normalized.series[g], path: o.clone(), chartRect: j, index: g, series: f, seriesIndex: g, seriesMeta: f.meta, axisX: d, axisY: e, group: i, element: p }) } if (m.showArea && e.range) { var q = Math.max(Math.min(m.areaBase, e.range.max), e.range.min), r = j.y1 - e.projectValue(q); o.splitByCommand("M").filter(function (a) { return a.pathElements.length > 1 }).map(function (a) { var b = a.pathElements[0], c = a.pathElements[a.pathElements.length - 1]; return a.clone(!0).position(0).remove(1).move(b.x, r).line(b.x, b.y).position(a.pathElements.length + 1).line(c.x, r) }).forEach(function (c) { var h = i.elem("path", { d: c.stringify() }, a.classNames.area, !0); this.eventEmitter.emit("draw", { type: "area", values: b.normalized.series[g], path: c.clone(), series: f, seriesIndex: g, axisX: d, axisY: e, chartRect: j, index: g, group: i, element: h }) }.bind(this)) } }.bind(this)), this.eventEmitter.emit("created", { bounds: e.bounds, chartRect: j, axisX: d, axisY: e, svg: this.svg, options: a }) } function e(a, b, d, e) { c.Line["super"].constructor.call(this, a, b, f, c.extend({}, f, d), e) } var f = { axisX: { offset: 30, position: "end", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, type: void 0 }, axisY: { offset: 40, position: "start", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, type: void 0, scaleMinSpace: 20, onlyInteger: !1 }, width: void 0, height: void 0, showLine: !0, showPoint: !0, showArea: !1, areaBase: 0, lineSmooth: !0, showGridBackground: !1, low: void 0, high: void 0, chartPadding: { top: 15, right: 15, bottom: 5, left: 10 }, fullWidth: !1, reverseData: !1, classNames: { chart: "ct-chart-line", label: "ct-label", labelGroup: "ct-labels", series: "ct-series", line: "ct-line", point: "ct-point", area: "ct-area", grid: "ct-grid", gridGroup: "ct-grids", gridBackground: "ct-grid-background", vertical: "ct-vertical", horizontal: "ct-horizontal", start: "ct-start", end: "ct-end" } }; c.Line = c.Base.extend({ constructor: e, createChart: d }) }(window, document, a), function (a, b, c) { - "use strict"; function d(a) { - var b, d; a.distributeSeries ? (b = c.normalizeData(this.data, a.reverseData, a.horizontalBars ? "x" : "y"), b.normalized.series = b.normalized.series.map(function (a) { return [a] })) : b = c.normalizeData(this.data, a.reverseData, a.horizontalBars ? "x" : "y"), this.svg = c.createSvg(this.container, a.width, a.height, a.classNames.chart + (a.horizontalBars ? " " + a.classNames.horizontalBars : "")); var e = this.svg.elem("g").addClass(a.classNames.gridGroup), g = this.svg.elem("g"), h = this.svg.elem("g").addClass(a.classNames.labelGroup); if (a.stackBars && 0 !== b.normalized.series.length) { - var i = c.serialMap(b.normalized.series, function () { - return Array.prototype.slice.call(arguments).map(function (a) { return a }).reduce(function (a, b) { return { x: a.x + (b && b.x) || 0, y: a.y + (b && b.y) || 0 } }, { x: 0, y: 0 }) - }); d = c.getHighLow([i], a, a.horizontalBars ? "x" : "y") - } else d = c.getHighLow(b.normalized.series, a, a.horizontalBars ? "x" : "y"); d.high = +a.high || (0 === a.high ? 0 : d.high), d.low = +a.low || (0 === a.low ? 0 : d.low); var j, k, l, m, n, o = c.createChartRect(this.svg, a, f.padding); k = a.distributeSeries && a.stackBars ? b.normalized.labels.slice(0, 1) : b.normalized.labels, a.horizontalBars ? (j = m = void 0 === a.axisX.type ? new c.AutoScaleAxis(c.Axis.units.x, b.normalized.series, o, c.extend({}, a.axisX, { highLow: d, referenceValue: 0 })) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, o, c.extend({}, a.axisX, { highLow: d, referenceValue: 0 })), l = n = void 0 === a.axisY.type ? new c.StepAxis(c.Axis.units.y, b.normalized.series, o, { ticks: k }) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, o, a.axisY)) : (l = m = void 0 === a.axisX.type ? new c.StepAxis(c.Axis.units.x, b.normalized.series, o, { ticks: k }) : a.axisX.type.call(c, c.Axis.units.x, b.normalized.series, o, a.axisX), j = n = void 0 === a.axisY.type ? new c.AutoScaleAxis(c.Axis.units.y, b.normalized.series, o, c.extend({}, a.axisY, { highLow: d, referenceValue: 0 })) : a.axisY.type.call(c, c.Axis.units.y, b.normalized.series, o, c.extend({}, a.axisY, { highLow: d, referenceValue: 0 }))); var p = a.horizontalBars ? o.x1 + j.projectValue(0) : o.y1 - j.projectValue(0), q = []; l.createGridAndLabels(e, h, this.supportsForeignObject, a, this.eventEmitter), j.createGridAndLabels(e, h, this.supportsForeignObject, a, this.eventEmitter), a.showGridBackground && c.createGridBackground(e, o, a.classNames.gridBackground, this.eventEmitter), b.raw.series.forEach(function (d, e) { var f, h, i = e - (b.raw.series.length - 1) / 2; f = a.distributeSeries && !a.stackBars ? l.axisLength / b.normalized.series.length / 2 : a.distributeSeries && a.stackBars ? l.axisLength / 2 : l.axisLength / b.normalized.series[e].length / 2, h = g.elem("g"), h.attr({ "ct:series-name": d.name, "ct:meta": c.serialize(d.meta) }), h.addClass([a.classNames.series, d.className || a.classNames.series + "-" + c.alphaNumerate(e)].join(" ")), b.normalized.series[e].forEach(function (g, k) { var r, s, t, u; if (u = a.distributeSeries && !a.stackBars ? e : a.distributeSeries && a.stackBars ? 0 : k, r = a.horizontalBars ? { x: o.x1 + j.projectValue(g && g.x ? g.x : 0, k, b.normalized.series[e]), y: o.y1 - l.projectValue(g && g.y ? g.y : 0, u, b.normalized.series[e]) } : { x: o.x1 + l.projectValue(g && g.x ? g.x : 0, u, b.normalized.series[e]), y: o.y1 - j.projectValue(g && g.y ? g.y : 0, k, b.normalized.series[e]) }, l instanceof c.StepAxis && (l.options.stretch || (r[l.units.pos] += f * (a.horizontalBars ? -1 : 1)), r[l.units.pos] += a.stackBars || a.distributeSeries ? 0 : i * a.seriesBarDistance * (a.horizontalBars ? -1 : 1)), t = q[k] || p, q[k] = t - (p - r[l.counterUnits.pos]), void 0 !== g) { var v = {}; v[l.units.pos + "1"] = r[l.units.pos], v[l.units.pos + "2"] = r[l.units.pos], !a.stackBars || "accumulate" !== a.stackMode && a.stackMode ? (v[l.counterUnits.pos + "1"] = p, v[l.counterUnits.pos + "2"] = r[l.counterUnits.pos]) : (v[l.counterUnits.pos + "1"] = t, v[l.counterUnits.pos + "2"] = q[k]), v.x1 = Math.min(Math.max(v.x1, o.x1), o.x2), v.x2 = Math.min(Math.max(v.x2, o.x1), o.x2), v.y1 = Math.min(Math.max(v.y1, o.y2), o.y1), v.y2 = Math.min(Math.max(v.y2, o.y2), o.y1); var w = c.getMetaData(d, k); s = h.elem("line", v, a.classNames.bar).attr({ "ct:value": [g.x, g.y].filter(c.isNumeric).join(","), "ct:meta": c.serialize(w) }), this.eventEmitter.emit("draw", c.extend({ type: "bar", value: g, index: k, meta: w, series: d, seriesIndex: e, axisX: m, axisY: n, chartRect: o, group: h, element: s }, v)) } }.bind(this)) }.bind(this)), this.eventEmitter.emit("created", { bounds: j.bounds, chartRect: o, axisX: m, axisY: n, svg: this.svg, options: a }) - } function e(a, b, d, e) { c.Bar["super"].constructor.call(this, a, b, f, c.extend({}, f, d), e) } var f = { axisX: { offset: 30, position: "end", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, scaleMinSpace: 30, onlyInteger: !1 }, axisY: { offset: 40, position: "start", labelOffset: { x: 0, y: 0 }, showLabel: !0, showGrid: !0, labelInterpolationFnc: c.noop, scaleMinSpace: 20, onlyInteger: !1 }, width: void 0, height: void 0, high: void 0, low: void 0, referenceValue: 0, chartPadding: { top: 15, right: 15, bottom: 5, left: 10 }, seriesBarDistance: 15, stackBars: !1, stackMode: "accumulate", horizontalBars: !1, distributeSeries: !1, reverseData: !1, showGridBackground: !1, classNames: { chart: "ct-chart-bar", horizontalBars: "ct-horizontal-bars", label: "ct-label", labelGroup: "ct-labels", series: "ct-series", bar: "ct-bar", grid: "ct-grid", gridGroup: "ct-grids", gridBackground: "ct-grid-background", vertical: "ct-vertical", horizontal: "ct-horizontal", start: "ct-start", end: "ct-end" } }; c.Bar = c.Base.extend({ constructor: e, createChart: d }) - }(window, document, a), function (a, b, c) { "use strict"; function d(a, b, c) { var d = b.x > a.x; return d && "explode" === c || !d && "implode" === c ? "start" : d && "implode" === c || !d && "explode" === c ? "end" : "middle" } function e(a) { var b, e, f, h, i, j = c.normalizeData(this.data), k = [], l = a.startAngle; this.svg = c.createSvg(this.container, a.width, a.height, a.donut ? a.classNames.chartDonut : a.classNames.chartPie), e = c.createChartRect(this.svg, a, g.padding), f = Math.min(e.width() / 2, e.height() / 2), i = a.total || j.normalized.series.reduce(function (a, b) { return a + b }, 0); var m = c.quantity(a.donutWidth); "%" === m.unit && (m.value *= f / 100), f -= a.donut && !a.donutSolid ? m.value / 2 : 0, h = "outside" === a.labelPosition || a.donut && !a.donutSolid ? f : "center" === a.labelPosition ? 0 : a.donutSolid ? f - m.value / 2 : f / 2, h += a.labelOffset; var n = { x: e.x1 + e.width() / 2, y: e.y2 + e.height() / 2 }, o = 1 === j.raw.series.filter(function (a) { return a.hasOwnProperty("value") ? 0 !== a.value : 0 !== a }).length; j.raw.series.forEach(function (a, b) { k[b] = this.svg.elem("g", null, null) }.bind(this)), a.showLabel && (b = this.svg.elem("g", null, null)), j.raw.series.forEach(function (e, g) { if (0 !== j.normalized.series[g] || !a.ignoreEmptyValues) { k[g].attr({ "ct:series-name": e.name }), k[g].addClass([a.classNames.series, e.className || a.classNames.series + "-" + c.alphaNumerate(g)].join(" ")); var p = i > 0 ? l + j.normalized.series[g] / i * 360 : 0, q = Math.max(0, l - (0 === g || o ? 0 : .2)); p - q >= 359.99 && (p = q + 359.99); var r, s, t, u = c.polarToCartesian(n.x, n.y, f, q), v = c.polarToCartesian(n.x, n.y, f, p), w = new c.Svg.Path(!a.donut || a.donutSolid).move(v.x, v.y).arc(f, f, 0, p - l > 180, 0, u.x, u.y); a.donut ? a.donutSolid && (t = f - m.value, r = c.polarToCartesian(n.x, n.y, t, l - (0 === g || o ? 0 : .2)), s = c.polarToCartesian(n.x, n.y, t, p), w.line(r.x, r.y), w.arc(t, t, 0, p - l > 180, 1, s.x, s.y)) : w.line(n.x, n.y); var x = a.classNames.slicePie; a.donut && (x = a.classNames.sliceDonut, a.donutSolid && (x = a.classNames.sliceDonutSolid)); var y = k[g].elem("path", { d: w.stringify() }, x); if (y.attr({ "ct:value": j.normalized.series[g], "ct:meta": c.serialize(e.meta) }), a.donut && !a.donutSolid && (y._node.style.strokeWidth = m.value + "px"), this.eventEmitter.emit("draw", { type: "slice", value: j.normalized.series[g], totalDataSum: i, index: g, meta: e.meta, series: e, group: k[g], element: y, path: w.clone(), center: n, radius: f, startAngle: l, endAngle: p }), a.showLabel) { var z; z = 1 === j.raw.series.length ? { x: n.x, y: n.y } : c.polarToCartesian(n.x, n.y, h, l + (p - l) / 2); var A; A = j.normalized.labels && !c.isFalseyButZero(j.normalized.labels[g]) ? j.normalized.labels[g] : j.normalized.series[g]; var B = a.labelInterpolationFnc(A, g); if (B || 0 === B) { var C = b.elem("text", { dx: z.x, dy: z.y, "text-anchor": d(n, z, a.labelDirection) }, a.classNames.label).text("" + B); this.eventEmitter.emit("draw", { type: "label", index: g, group: b, element: C, text: "" + B, x: z.x, y: z.y }) } } l = p } }.bind(this)), this.eventEmitter.emit("created", { chartRect: e, svg: this.svg, options: a }) } function f(a, b, d, e) { c.Pie["super"].constructor.call(this, a, b, g, c.extend({}, g, d), e) } var g = { width: void 0, height: void 0, chartPadding: 5, classNames: { chartPie: "ct-chart-pie", chartDonut: "ct-chart-donut", series: "ct-series", slicePie: "ct-slice-pie", sliceDonut: "ct-slice-donut", sliceDonutSolid: "ct-slice-donut-solid", label: "ct-label" }, startAngle: 0, total: void 0, donut: !1, donutSolid: !1, donutWidth: 60, showLabel: !0, labelOffset: 0, labelPosition: "inside", labelInterpolationFnc: c.noop, labelDirection: "neutral", reverseData: !1, ignoreEmptyValues: !1 }; c.Pie = c.Base.extend({ constructor: f, createChart: e, determineAnchorPosition: d }) }(window, document, a), a -}); - -var i, l, selectedLine = null; - -/* Navigate to hash without browser history entry */ -var navigateToHash = function () { - if (window.history !== undefined && window.history.replaceState !== undefined) { - window.history.replaceState(undefined, undefined, this.getAttribute("href")); - } -}; - -var hashLinks = document.getElementsByClassName('navigatetohash'); -for (i = 0, l = hashLinks.length; i < l; i++) { - hashLinks[i].addEventListener('click', navigateToHash); -} - -/* Switch test method */ -var switchTestMethod = function () { - var method = this.getAttribute("value"); - console.log("Selected test method: " + method); - - var lines, i, l, coverageData, lineAnalysis, cells; - - lines = document.querySelectorAll('.lineAnalysis tr'); - - for (i = 1, l = lines.length; i < l; i++) { - coverageData = JSON.parse(lines[i].getAttribute('data-coverage').replace(/'/g, '"')); - lineAnalysis = coverageData[method]; - cells = lines[i].querySelectorAll('td'); - if (lineAnalysis === undefined) { - lineAnalysis = coverageData.AllTestMethods; - if (lineAnalysis.LVS !== 'gray') { - cells[0].setAttribute('class', 'red'); - cells[1].innerText = cells[1].textContent = '0'; - cells[4].setAttribute('class', 'lightred'); - } - } else { - cells[0].setAttribute('class', lineAnalysis.LVS); - cells[1].innerText = cells[1].textContent = lineAnalysis.VC; - cells[4].setAttribute('class', 'light' + lineAnalysis.LVS); - } - } -}; - -var testMethods = document.getElementsByClassName('switchtestmethod'); -for (i = 0, l = testMethods.length; i < l; i++) { - testMethods[i].addEventListener('change', switchTestMethod); -} - -/* Highlight test method by line */ -var toggleLine = function () { - if (selectedLine === this) { - selectedLine = null; - } else { - selectedLine = null; - unhighlightTestMethods(); - highlightTestMethods.call(this); - selectedLine = this; - } - -}; -var highlightTestMethods = function () { - if (selectedLine !== null) { - return; - } - - var lineAnalysis; - var coverageData = JSON.parse(this.getAttribute('data-coverage').replace(/'/g, '"')); - var testMethods = document.getElementsByClassName('testmethod'); - - for (i = 0, l = testMethods.length; i < l; i++) { - lineAnalysis = coverageData[testMethods[i].id]; - if (lineAnalysis === undefined) { - testMethods[i].className = testMethods[i].className.replace(/\s*light.+/g, ""); - } else { - testMethods[i].className += ' light' + lineAnalysis.LVS; - } - } -}; -var unhighlightTestMethods = function () { - if (selectedLine !== null) { - return; - } - - var testMethods = document.getElementsByClassName('testmethod'); - for (i = 0, l = testMethods.length; i < l; i++) { - testMethods[i].className = testMethods[i].className.replace(/\s*light.+/g, ""); - } -}; -var coverableLines = document.getElementsByClassName('coverableline'); -for (i = 0, l = coverableLines.length; i < l; i++) { - coverableLines[i].addEventListener('click', toggleLine); - coverableLines[i].addEventListener('mouseenter', highlightTestMethods); - coverableLines[i].addEventListener('mouseleave', unhighlightTestMethods); -} - -/* History charts */ -var renderChart = function (chart) { - // Remove current children (e.g. PNG placeholder) - while (chart.firstChild) { - chart.firstChild.remove(); - } - - var chartData = window[chart.getAttribute('data-data')]; - var options = { - axisY: { - type: undefined, - onlyInteger: true - }, - lineSmooth: false, - low: 0, - high: 100, - scaleMinSpace: 20, - onlyInteger: true, - fullWidth: true - }; - var lineChart = new Chartist.Line(chart, { - labels: [], - series: chartData.series - }, options); - - /* Zoom */ - var zoomButtonDiv = document.createElement("div"); - zoomButtonDiv.className = "toggleZoom"; - var zoomButtonLink = document.createElement("a"); - zoomButtonLink.setAttribute("href", ""); - var zoomButtonText = document.createElement("i"); - zoomButtonText.className = "icon-search-plus"; - - zoomButtonLink.appendChild(zoomButtonText); - zoomButtonDiv.appendChild(zoomButtonLink); - - chart.appendChild(zoomButtonDiv); - - zoomButtonDiv.addEventListener('click', function (event) { - event.preventDefault(); - - if (options.axisY.type === undefined) { - options.axisY.type = Chartist.AutoScaleAxis; - zoomButtonText.className = "icon-search-minus"; - } else { - options.axisY.type = undefined; - zoomButtonText.className = "icon-search-plus"; - } - - lineChart.update(null, options); - }); - - var tooltip = document.createElement("div"); - tooltip.className = "tooltip"; - - chart.appendChild(tooltip); - - /* Tooltips */ - var showToolTip = function () { - var point = this; - var index = [].slice.call(chart.getElementsByClassName('ct-point')).indexOf(point); - - tooltip.innerHTML = chartData.tooltips[index % chartData.tooltips.length]; - tooltip.style.display = 'block'; - }; - - var moveToolTip = function (event) { - var box = chart.getBoundingClientRect(); - var left = event.pageX - box.left - window.pageXOffset; - var top = event.pageY - box.top - window.pageYOffset; - - tooltip.style.left = left - tooltip.offsetWidth / 2 - 5 + 'px'; - tooltip.style.top = top - tooltip.offsetHeight - 40 + 'px'; - }; - - var hideToolTip = function () { - tooltip.style.display = 'none'; - }; - - chart.addEventListener('mousemove', moveToolTip); - - lineChart.on('created', function () { - var chartPoints = chart.getElementsByClassName('ct-point'); - for (i = 0, l = chartPoints.length; i < l; i++) { - chartPoints[i].addEventListener('mousemove', showToolTip); - chartPoints[i].addEventListener('mouseout', hideToolTip); - } - }); -}; - -var charts = document.getElementsByClassName('historychart'); -for (i = 0, l = charts.length; i < l; i++) { - renderChart(charts[i]); -} - -var assemblies = [ - { - "name": "ClassLibrary1", - "classes": [ - { "name": "ClassLibrary1.Calculation", "rp": "ClassLibrary1_Calculation.htm", "cl": 3, "ucl": 6, "cal": 9, "tl": 18, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [33.3], "bch": [], "hc": [{ "et": "2021/3/26 - 下午 04:34:47", "cl": 3, "ucl": 6, "cal": 9, "tl": 18, "lcq": 33.3, "cb": 0, "tb": 0, "bcq": 0 }] }, - ]}, - { - "name": "UnitTestProject1", - "classes": [ - { "name": "UnitTestProject1.UnitTest1", "rp": "UnitTestProject1_UnitTest1.htm", "cl": 9, "ucl": 1, "cal": 10, "tl": 26, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [90], "bch": [], "hc": [{ "et": "2021/3/26 - 下午 04:34:47", "cl": 9, "ucl": 1, "cal": 10, "tl": 26, "lcq": 90, "cb": 0, "tb": 0, "bcq": 0 }] }, - ]}, - { - "name": "UnitTestProject2", - "classes": [ - { "name": "UnitTestProject2.UnitTest1", "rp": "UnitTestProject2_UnitTest1.htm", "cl": 3, "ucl": 0, "cal": 3, "tl": 15, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [100], "bch": [], "hc": [{ "et": "2021/3/26 - 下午 04:34:47", "cl": 3, "ucl": 0, "cal": 3, "tl": 15, "lcq": 100, "cb": 0, "tb": 0, "bcq": 0 }] }, - ]}, -]; - -var historicCoverageExecutionTimes = []; - -var riskHotspotMetrics = [ -]; - -var riskHotspots = [ -]; - -var branchCoverageAvailable = true; - - -var translations = { -'top': 'Top:', -'all': 'All', -'assembly': 'Assembly', -'class': 'Class', -'method': 'Method', -'lineCoverage': 'LineCoverage', -'noGrouping': 'No grouping', -'byAssembly': 'By assembly', -'byNamespace': 'By namespace, Level:', -'all': 'All', -'collapseAll': 'Collapse all', -'expandAll': 'Expand all', -'grouping': 'Grouping:', -'filter': 'Filter:', -'name': 'Name', -'covered': 'Covered', -'uncovered': 'Uncovered', -'coverable': 'Coverable', -'total': 'Total', -'coverage': 'Line coverage', -'branchCoverage': 'Branch coverage', -'history': 'Coverage History', -'compareHistory': 'Compare with:', -'date': 'Date', -'allChanges': 'All changes', -'lineCoverageIncreaseOnly': 'Line coverage: Increase only', -'lineCoverageDecreaseOnly': 'Line coverage: Decrease only', -'branchCoverageIncreaseOnly': 'Branch coverage: Increase only', -'branchCoverageDecreaseOnly': 'Branch coverage: Decrease only' -}; - - -!function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c",this._properties=n&&n.properties||{},this._zoneDelegate=new c(this,this._parent&&this._parent._zoneDelegate,n)}return n.assertZonePatched=function(){if(t.Promise!==T.ZoneAwarePromise)throw new Error("Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten.\nMost likely cause is that a Promise polyfill has been loaded after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. If you must load one, do so before loading zone.js.)")},Object.defineProperty(n,"root",{get:function(){for(var t=n.current;t.parent;)t=t.parent;return t},enumerable:!0,configurable:!0}),Object.defineProperty(n,"current",{get:function(){return j.zone},enumerable:!0,configurable:!0}),Object.defineProperty(n,"currentTask",{get:function(){return M},enumerable:!0,configurable:!0}),n.__load_patch=function(o,i){if(T.hasOwnProperty(o))throw Error("Already loaded patch: "+o);if(!t["__Zone_disable_"+o]){var u="Zone:"+o;r(u),T[o]=i(t,n,O),e(u,u)}},Object.defineProperty(n.prototype,"parent",{get:function(){return this._parent},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"name",{get:function(){return this._name},enumerable:!0,configurable:!0}),n.prototype.get=function(t){var n=this.getZoneWith(t);if(n)return n._properties[t]},n.prototype.getZoneWith=function(t){for(var n=this;n;){if(n._properties.hasOwnProperty(t))return n;n=n._parent}return null},n.prototype.fork=function(t){if(!t)throw new Error("ZoneSpec required!");return this._zoneDelegate.fork(this,t)},n.prototype.wrap=function(t,n){if("function"!=typeof t)throw new Error("Expecting function got: "+t);var r=this._zoneDelegate.intercept(this,t,n),e=this;return function(){return e.runGuarded(r,this,arguments,n)}},n.prototype.run=function(t,n,r,e){void 0===n&&(n=void 0),void 0===r&&(r=null),void 0===e&&(e=null),j={parent:j,zone:this};try{return this._zoneDelegate.invoke(this,t,n,r,e)}finally{j=j.parent}},n.prototype.runGuarded=function(t,n,r,e){void 0===n&&(n=null),void 0===r&&(r=null),void 0===e&&(e=null),j={parent:j,zone:this};try{try{return this._zoneDelegate.invoke(this,t,n,r,e)}catch(o){if(this._zoneDelegate.handleError(this,o))throw o}}finally{j=j.parent}},n.prototype.runTask=function(t,n,r){if(t.zone!=this)throw new Error("A task can only be run in the zone of creation! (Creation: "+(t.zone||d).name+"; Execution: "+this.name+")");if(t.state!==g||t.type!==F){var e=t.state!=_;e&&t._transitionTo(_,m),t.runCount++;var o=M;M=t,j={parent:j,zone:this};try{t.type==S&&t.data&&!t.data.isPeriodic&&(t.cancelFn=null);try{return this._zoneDelegate.invokeTask(this,t,n,r)}catch(i){if(this._zoneDelegate.handleError(this,i))throw i}}finally{t.state!==g&&t.state!==w&&(t.type==F||t.data&&t.data.isPeriodic?e&&t._transitionTo(m,_):(t.runCount=0,this._updateTaskCount(t,-1),e&&t._transitionTo(g,_,g))),j=j.parent,M=o}}},n.prototype.scheduleTask=function(t){if(t.zone&&t.zone!==this)for(var n=this;n;){if(n===t.zone)throw Error("can not reschedule task to "+this.name+" which is descendants of the original zone "+t.zone.name);n=n.parent}t._transitionTo(b,g);var r=[];t._zoneDelegates=r,t._zone=this;try{t=this._zoneDelegate.scheduleTask(this,t)}catch(e){throw t._transitionTo(w,b,g),this._zoneDelegate.handleError(this,e),e}return t._zoneDelegates===r&&this._updateTaskCount(t,1),t.state==b&&t._transitionTo(m,b),t},n.prototype.scheduleMicroTask=function(t,n,r,e){return this.scheduleTask(new a(x,t,n,r,e,null))},n.prototype.scheduleMacroTask=function(t,n,r,e,o){return this.scheduleTask(new a(S,t,n,r,e,o))},n.prototype.scheduleEventTask=function(t,n,r,e,o){return this.scheduleTask(new a(F,t,n,r,e,o))},n.prototype.cancelTask=function(t){if(t.zone!=this)throw new Error("A task can only be cancelled in the zone of creation! (Creation: "+(t.zone||d).name+"; Execution: "+this.name+")");t._transitionTo(k,m,_);try{this._zoneDelegate.cancelTask(this,t)}catch(n){throw t._transitionTo(w,k),this._zoneDelegate.handleError(this,n),n}return this._updateTaskCount(t,-1),t._transitionTo(g,k),t.runCount=0,t},n.prototype._updateTaskCount=function(t,n){var r=t._zoneDelegates;-1==n&&(t._zoneDelegates=null);for(var e=0;e0,macroTask:r.macroTask>0,eventTask:r.eventTask>0,change:t})},t}(),a=function(){function n(r,e,o,i,u,c){this._zone=null,this.runCount=0,this._zoneDelegates=null,this._state="notScheduled",this.type=r,this.source=e,this.data=i,this.scheduleFn=u,this.cancelFn=c,this.callback=o;var a=this;this.invoke=r===F&&i&&i.useG?n.invokeTask:function(){return n.invokeTask.call(t,a,this,arguments)}}return n.invokeTask=function(t,n,r){t||(t=this),K++;try{return t.runCount++,t.zone.runTask(t,n,r)}finally{1==K&&y(),K--}},Object.defineProperty(n.prototype,"zone",{get:function(){return this._zone},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"state",{get:function(){return this._state},enumerable:!0,configurable:!0}),n.prototype.cancelScheduleRequest=function(){this._transitionTo(g,b)},n.prototype._transitionTo=function(t,n,r){if(this._state!==n&&this._state!==r)throw new Error(this.type+" '"+this.source+"': can not transition to '"+t+"', expecting state '"+n+"'"+(r?" or '"+r+"'":"")+", was '"+this._state+"'.");this._state=t,t==g&&(this._zoneDelegates=null)},n.prototype.toString=function(){return this.data&&void 0!==this.data.handleId?this.data.handleId:Object.prototype.toString.call(this)},n.prototype.toJSON=function(){return{type:this.type,state:this.state,source:this.source,zone:this.zone.name,runCount:this.runCount}},n}(),f=X("setTimeout"),s=X("Promise"),l=X("then"),h=[],p=!1;function v(n){0===K&&0===h.length&&(o||t[s]&&(o=t[s].resolve(0)),o?o[l](y):t[f](y,0)),n&&h.push(n)}function y(){if(!p){for(p=!0;h.length;){var t=h;h=[];for(var n=0;n=0;r--)"function"==typeof t[r]&&(t[r]=h(t[r],n+"_"+r));return t}function k(t){return!t||!1!==t.writable&&!("function"==typeof t.get&&void 0===t.set)}var w="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope,x=!("nw"in g)&&void 0!==g.process&&"[object process]"==={}.toString.call(g.process),S=!x&&!w&&!(!y||!d.HTMLElement),F=void 0!==g.process&&"[object process]"==={}.toString.call(g.process)&&!w&&!(!y||!d.HTMLElement),T={},O=function(t){if(t=t||g.event){var n=T[t.type];n||(n=T[t.type]=v("ON_PROPERTY"+t.type));var r=(this||t.target||g)[n],e=r&&r.apply(this,arguments);return null==e||e||t.preventDefault(),e}};function j(r,e,o){var i=t(r,e);if(!i&&o&&t(o,e)&&(i={enumerable:!0,configurable:!0}),i&&i.configurable){delete i.writable,delete i.value;var u=i.get,c=i.set,a=e.substr(2),f=T[a];f||(f=T[a]=v("ON_PROPERTY"+a)),i.set=function(t){var n=this;n||r!==g||(n=g),n&&(n[f]&&n.removeEventListener(a,O),c&&c.apply(n,m),"function"==typeof t?(n[f]=t,n.addEventListener(a,O,!1)):n[f]=null)},i.get=function(){var t=this;if(t||r!==g||(t=g),!t)return null;var n=t[f];if(n)return n;if(u){var o=u&&u.call(this);if(o)return i.set.call(this,o),"function"==typeof t[b]&&t.removeAttribute(e),o}return null},n(r,e,i)}}function M(t,n,r){if(n)for(var e=0;e1?new c(n,r):new c(n),l=t(s,"onmessage");return l&&!1===l.configurable?(a=e(s),f=s,[i,u,"send","close"].forEach(function(t){a[t]=function(){var n=o.call(arguments);if(t===i||t===u){var r=n.length>0?n[0]:void 0;if(r){var e=Zone.__symbol__("ON_PROPERTY"+r);s[e]=a[e]}}return s[t].apply(s,n)}})):a=s,M(a,["close","error","message","open"],f),a};var a=r.WebSocket;for(var f in c)a[f]=c[f]}(0,a)}}var lt=v("unbound");Zone.__load_patch("util",function(t,n,r){r.patchOnProperties=M,r.patchMethod=X,r.bindArguments=_}),Zone.__load_patch("timers",function(t){G(t,"set","clear","Timeout"),G(t,"set","clear","Interval"),G(t,"set","clear","Immediate")}),Zone.__load_patch("requestAnimationFrame",function(t){G(t,"request","cancel","AnimationFrame"),G(t,"mozRequest","mozCancel","AnimationFrame"),G(t,"webkitRequest","webkitCancel","AnimationFrame")}),Zone.__load_patch("blocking",function(t,n){for(var r=["alert","prompt","confirm"],e=0;e=0&&"function"==typeof r[e.cbIdx]?p(e.name,r[e.cbIdx],e,i,null):t.apply(n,r)}})}()}),Zone.__load_patch("XHR",function(t,n){!function(n){var f=XMLHttpRequest.prototype,s=f[c],l=f[a];if(!s){var h=t.XMLHttpRequestEventTarget;if(h){var v=h.prototype;s=v[c],l=v[a]}}var y="readystatechange",d="scheduled";function g(t){XMLHttpRequest[i]=!1;var n=t.data,e=n.target,u=e[o];s||(s=e[c],l=e[a]),u&&l.call(e,y,u);var f=e[o]=function(){e.readyState===e.DONE&&!n.aborted&&XMLHttpRequest[i]&&t.state===d&&t.invoke()};return s.call(e,y,f),e[r]||(e[r]=t),k.apply(e,n.args),XMLHttpRequest[i]=!0,t}function b(){}function m(t){var n=t.data;return n.aborted=!0,w.apply(n.target,n.args)}var _=X(f,"open",function(){return function(t,n){return t[e]=0==n[2],t[u]=n[1],_.apply(t,n)}}),k=X(f,"send",function(){return function(t,n){return t[e]?k.apply(t,n):p("XMLHttpRequest.send",b,{target:t,url:t[u],isPeriodic:!1,delay:null,args:n,aborted:!1},g,m)}}),w=X(f,"abort",function(){return function(t){var n=t[r];if(n&&"string"==typeof n.type){if(null==n.cancelFn||n.data&&n.data.aborted)return;n.zone.cancelTask(n)}}})}();var r=v("xhrTask"),e=v("xhrSync"),o=v("xhrListener"),i=v("xhrScheduled"),u=v("xhrURL")}),Zone.__load_patch("geolocation",function(n){n.navigator&&n.navigator.geolocation&&function(n,r){for(var e=n.constructor.name,o=function(o){var i=r[o],u=n[i];if(u){if(!k(t(n,i)))return"continue";n[i]=function(t){var n=function(){return t.apply(this,_(arguments,e+"."+i))};return Z(n,t),n}(u)}},i=0;if;)a.call(t,u=c[f++])&&n.push(u);return n}},"1TsA":function(t,n){t.exports=function(t,n){return{value:n,done:!!t}}},"1sa7":function(t,n){t.exports=Math.log1p||function(t){return(t=+t)>-1e-8&&t<1e-8?t-t*t/2:Math.log(1+t)}},"25dN":function(t,n,r){var e=r("XKFU");e(e.S,"Object",{is:r("g6HL")})},"2OiF":function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},"2Spj":function(t,n,r){var e=r("XKFU");e(e.P,"Function",{bind:r("8MEG")})},"2atp":function(t,n,r){var e=r("XKFU"),o=Math.atanh;e(e.S+e.F*!(o&&1/o(-0)<0),"Math",{atanh:function(t){return 0==(t=+t)?t:Math.log((1+t)/(1-t))/2}})},"3Lyj":function(t,n,r){var e=r("KroJ");t.exports=function(t,n,r){for(var o in n)e(t,o,n[o],r);return t}},"4A4+":function(t,n,r){r("2Spj"),r("f3/d"),r("IXt9"),t.exports=r("g3g5").Function},"4LiD":function(t,n,r){"use strict";var e=r("dyZX"),o=r("XKFU"),i=r("KroJ"),u=r("3Lyj"),c=r("Z6vF"),a=r("SlkY"),f=r("9gX7"),s=r("0/R4"),l=r("eeVq"),h=r("XMVh"),p=r("fyDq"),v=r("Xbzi");t.exports=function(t,n,r,y,d,g){var b=e[t],m=b,_=d?"set":"add",k=m&&m.prototype,w={},x=function(t){var n=k[t];i(k,t,"delete"==t?function(t){return!(g&&!s(t))&&n.call(this,0===t?0:t)}:"has"==t?function(t){return!(g&&!s(t))&&n.call(this,0===t?0:t)}:"get"==t?function(t){return g&&!s(t)?void 0:n.call(this,0===t?0:t)}:"add"==t?function(t){return n.call(this,0===t?0:t),this}:function(t,r){return n.call(this,0===t?0:t,r),this})};if("function"==typeof m&&(g||k.forEach&&!l(function(){(new m).entries().next()}))){var S=new m,F=S[_](g?{}:-0,1)!=S,T=l(function(){S.has(1)}),O=h(function(t){new m(t)}),j=!g&&l(function(){for(var t=new m,n=5;n--;)t[_](n,n);return!t.has(-0)});O||((m=n(function(n,r){f(n,m,t);var e=v(new b,n,m);return null!=r&&a(r,d,e[_],e),e})).prototype=k,k.constructor=m),(T||j)&&(x("delete"),x("has"),d&&x("get")),(j||F)&&x(_),g&&k.clear&&delete k.clear}else m=y.getConstructor(n,t,d,_),u(m.prototype,r),c.NEED=!0;return p(m,t),w[t]=m,o(o.G+o.W+o.F*(m!=b),w),g||y.setStrong(m,t,d),m}},"4R4u":function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},"5Pf0":function(t,n,r){var e=r("S/j/"),o=r("OP3Y");r("Xtr8")("getPrototypeOf",function(){return function(t){return o(e(t))}})},"69bn":function(t,n,r){var e=r("y3w9"),o=r("2OiF"),i=r("K0xU")("species");t.exports=function(t,n){var r,u=e(t).constructor;return void 0===u||null==(r=e(u)[i])?n:o(r)}},"6AQ9":function(t,n,r){"use strict";var e=r("XKFU"),o=r("8a7r");e(e.S+e.F*r("eeVq")(function(){function t(){}return!(Array.of.call(t)instanceof t)}),"Array",{of:function(){for(var t=0,n=arguments.length,r=new("function"==typeof this?this:Array)(n);n>t;)o(r,t,arguments[t++]);return r.length=n,r}})},"6FMO":function(t,n,r){var e=r("0/R4"),o=r("EWmC"),i=r("K0xU")("species");t.exports=function(t){var n;return o(t)&&("function"!=typeof(n=t.constructor)||n!==Array&&!o(n.prototype)||(n=void 0),e(n)&&null===(n=n[i])&&(n=void 0)),void 0===n?Array:n}},"7h0T":function(t,n,r){var e=r("XKFU");e(e.S,"Number",{isNaN:function(t){return t!=t}})},"8+KV":function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(0),i=r("LyE8")([].forEach,!0);e(e.P+e.F*!i,"Array",{forEach:function(t){return o(this,t,arguments[1])}})},"84bF":function(t,n,r){"use strict";r("OGtf")("small",function(t){return function(){return t(this,"small","","")}})},"8MEG":function(t,n,r){"use strict";var e=r("2OiF"),o=r("0/R4"),i=r("MfQN"),u=[].slice,c={};t.exports=Function.bind||function(t){var n=e(this),r=u.call(arguments,1),a=function(){var e=r.concat(u.call(arguments));return this instanceof a?function(t,n,r){if(!(n in c)){for(var e=[],o=0;o0?arguments[0]:void 0)}},{get:function(t){var n=e.getEntry(o(this,"Map"),t);return n&&n.v},set:function(t,n){return e.def(o(this,"Map"),0===t?0:t,n)}},e,!0)},"9P93":function(t,n,r){var e=r("XKFU"),o=Math.imul;e(e.S+e.F*r("eeVq")(function(){return-5!=o(4294967295,5)||2!=o.length}),"Math",{imul:function(t,n){var r=+t,e=+n,o=65535&r,i=65535&e;return 0|o*i+((65535&r>>>16)*i+o*(65535&e>>>16)<<16>>>0)}})},"9VmF":function(t,n,r){"use strict";var e=r("XKFU"),o=r("ne8i"),i=r("0sh+"),u="".startsWith;e(e.P+e.F*r("UUeW")("startsWith"),"String",{startsWith:function(t){var n=i(this,t,"startsWith"),r=o(Math.min(arguments.length>1?arguments[1]:void 0,n.length)),e=String(t);return u?u.call(n,e,r):n.slice(r,r+e.length)===e}})},"9gX7":function(t,n){t.exports=function(t,n,r,e){if(!(t instanceof n)||void 0!==e&&e in t)throw TypeError(r+": incorrect invocation!");return t}},A2zW:function(t,n,r){"use strict";var e=r("XKFU"),o=r("RYi7"),i=r("vvmO"),u=r("l0Rn"),c=1..toFixed,a=Math.floor,f=[0,0,0,0,0,0],s="Number.toFixed: incorrect invocation!",l=function(t,n){for(var r=-1,e=n;++r<6;)f[r]=(e+=t*f[r])%1e7,e=a(e/1e7)},h=function(t){for(var n=6,r=0;--n>=0;)f[n]=a((r+=f[n])/t),r=r%t*1e7},p=function(){for(var t=6,n="";--t>=0;)if(""!==n||0===t||0!==f[t]){var r=String(f[t]);n=""===n?r:n+u.call("0",7-r.length)+r}return n},v=function(t,n,r){return 0===n?r:n%2==1?v(t,n-1,r*t):v(t*t,n/2,r)};e(e.P+e.F*(!!c&&("0.000"!==8e-5.toFixed(3)||"1"!==.9.toFixed(0)||"1.25"!==1.255.toFixed(2)||"1000000000000000128"!==(0xde0b6b3a7640080).toFixed(0))||!r("eeVq")(function(){c.call({})})),"Number",{toFixed:function(t){var n,r,e,c,a=i(this,s),f=o(t),y="",d="0";if(f<0||f>20)throw RangeError(s);if(a!=a)return"NaN";if(a<=-1e21||a>=1e21)return String(a);if(a<0&&(y="-",a=-a),a>1e-21)if(r=(n=function(t){for(var n=0,r=t;r>=4096;)n+=12,r/=4096;for(;r>=2;)n+=1,r/=2;return n}(a*v(2,69,1))-69)<0?a*v(2,-n,1):a/v(2,n,1),r*=4503599627370496,(n=52-n)>0){for(l(0,r),e=f;e>=7;)l(1e7,0),e-=7;for(l(v(10,e,1),0),e=n-1;e>=23;)h(1<<23),e-=23;h(1<0?y+((c=d.length)<=f?"0."+u.call("0",f-c)+d:d.slice(0,c-f)+"."+d.slice(c-f)):y+d}})},A5AN:function(t,n,r){"use strict";var e=r("AvRE")(!0);t.exports=function(t,n,r){return n+(r?e(t,n).length:1)}},Afnz:function(t,n,r){"use strict";var e=r("LQAc"),o=r("XKFU"),i=r("KroJ"),u=r("Mukb"),c=r("hPIQ"),a=r("QaDb"),f=r("fyDq"),s=r("OP3Y"),l=r("K0xU")("iterator"),h=!([].keys&&"next"in[].keys()),p=function(){return this};t.exports=function(t,n,r,v,y,d,g){a(r,n,v);var b,m,_,k=function(t){if(!h&&t in F)return F[t];switch(t){case"keys":case"values":return function(){return new r(this,t)}}return function(){return new r(this,t)}},w=n+" Iterator",x="values"==y,S=!1,F=t.prototype,T=F[l]||F["@@iterator"]||y&&F[y],O=T||k(y),j=y?x?k("entries"):O:void 0,M="Array"==n&&F.entries||T;if(M&&(_=s(M.call(new t)))!==Object.prototype&&_.next&&(f(_,w,!0),e||"function"==typeof _[l]||u(_,l,p)),x&&T&&"values"!==T.name&&(S=!0,O=function(){return T.call(this)}),e&&!g||!h&&!S&&F[l]||u(F,l,O),c[n]=O,c[w]=p,y)if(b={values:x?O:k("values"),keys:d?O:k("keys"),entries:j},g)for(m in b)m in F||i(F,m,b[m]);else o(o.P+o.F*(h||S),n,b);return b}},AphP:function(t,n,r){"use strict";var e=r("XKFU"),o=r("S/j/"),i=r("apmT");e(e.P+e.F*r("eeVq")(function(){return null!==new Date(NaN).toJSON()||1!==Date.prototype.toJSON.call({toISOString:function(){return 1}})}),"Date",{toJSON:function(t){var n=o(this),r=i(n);return"number"!=typeof r||isFinite(r)?n.toISOString():null}})},AvRE:function(t,n,r){var e=r("RYi7"),o=r("vhPU");t.exports=function(t){return function(n,r){var i,u,c=String(o(n)),a=e(r),f=c.length;return a<0||a>=f?t?"":void 0:(i=c.charCodeAt(a))<55296||i>56319||a+1===f||(u=c.charCodeAt(a+1))<56320||u>57343?t?c.charAt(a):i:t?c.slice(a,a+2):u-56320+(i-55296<<10)+65536}}},BC7C:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{fround:r("kcoS")})},"BJ/l":function(t,n,r){var e=r("XKFU");e(e.S,"Math",{log1p:r("1sa7")})},BP8U:function(t,n,r){var e=r("XKFU"),o=r("PKUr");e(e.S+e.F*(Number.parseInt!=o),"Number",{parseInt:o})},Btvt:function(t,n,r){"use strict";var e=r("I8a+"),o={};o[r("K0xU")("toStringTag")]="z",o+""!="[object z]"&&r("KroJ")(Object.prototype,"toString",function(){return"[object "+e(this)+"]"},!0)},"C/va":function(t,n,r){"use strict";var e=r("y3w9");t.exports=function(){var t=e(this),n="";return t.global&&(n+="g"),t.ignoreCase&&(n+="i"),t.multiline&&(n+="m"),t.unicode&&(n+="u"),t.sticky&&(n+="y"),n}},CkkT:function(t,n,r){var e=r("m0Pp"),o=r("Ymqv"),i=r("S/j/"),u=r("ne8i"),c=r("zRwo");t.exports=function(t,n){var r=1==t,a=2==t,f=3==t,s=4==t,l=6==t,h=5==t||l,p=n||c;return function(n,c,v){for(var y,d,g=i(n),b=o(g),m=e(c,v,3),_=u(b.length),k=0,w=r?p(n,_):a?p(n,0):void 0;_>k;k++)if((h||k in b)&&(d=m(y=b[k],k,g),t))if(r)w[k]=d;else if(d)switch(t){case 3:return!0;case 5:return y;case 6:return k;case 2:w.push(y)}else if(s)return!1;return l?-1:f||s?s:w}}},CuTL:function(t,n,r){r("fyVe"),r("U2t9"),r("2atp"),r("+auO"),r("MtdB"),r("Jcmo"),r("nzyx"),r("BC7C"),r("x8ZO"),r("9P93"),r("eHKK"),r("BJ/l"),r("pp/T"),r("CyHz"),r("bBoP"),r("x8Yj"),r("hLT2"),t.exports=r("g3g5").Math},CyHz:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{sign:r("lvtm")})},DNiP:function(t,n,r){"use strict";var e=r("XKFU"),o=r("eyMr");e(e.P+e.F*!r("LyE8")([].reduce,!0),"Array",{reduce:function(t){return o(this,t,arguments.length,arguments[1],!1)}})},DVgA:function(t,n,r){var e=r("zhAb"),o=r("4R4u");t.exports=Object.keys||function(t){return e(t,o)}},DW2E:function(t,n,r){var e=r("0/R4"),o=r("Z6vF").onFreeze;r("Xtr8")("freeze",function(t){return function(n){return t&&e(n)?t(o(n)):n}})},EK0E:function(t,n,r){"use strict";var e,o=r("CkkT")(0),i=r("KroJ"),u=r("Z6vF"),c=r("czNK"),a=r("ZD67"),f=r("0/R4"),s=r("eeVq"),l=r("s5qY"),h=u.getWeak,p=Object.isExtensible,v=a.ufstore,y={},d=function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},g={get:function(t){if(f(t)){var n=h(t);return!0===n?v(l(this,"WeakMap")).get(t):n?n[this._i]:void 0}},set:function(t,n){return a.def(l(this,"WeakMap"),t,n)}},b=t.exports=r("4LiD")("WeakMap",d,g,a,!0,!0);s(function(){return 7!=(new b).set((Object.freeze||Object)(y),7).get(y)})&&(c((e=a.getConstructor(d,"WeakMap")).prototype,g),u.NEED=!0,o(["delete","has","get","set"],function(t){var n=b.prototype,r=n[t];i(n,t,function(n,o){if(f(n)&&!p(n)){this._f||(this._f=new e);var i=this._f[t](n,o);return"set"==t?this:i}return r.call(this,n,o)})}))},EWmC:function(t,n,r){var e=r("LZWt");t.exports=Array.isArray||function(t){return"Array"==e(t)}},EemH:function(t,n,r){var e=r("UqcF"),o=r("RjD/"),i=r("aCFj"),u=r("apmT"),c=r("aagx"),a=r("xpql"),f=Object.getOwnPropertyDescriptor;n.f=r("nh4g")?f:function(t,n){if(t=i(t),n=u(n,!0),a)try{return f(t,n)}catch(r){}if(c(t,n))return o(!e.f.call(t,n),t[n])}},FEjr:function(t,n,r){"use strict";r("OGtf")("strike",function(t){return function(){return t(this,"strike","","")}})},FJW5:function(t,n,r){var e=r("hswa"),o=r("y3w9"),i=r("DVgA");t.exports=r("nh4g")?Object.defineProperties:function(t,n){o(t);for(var r,u=i(n),c=u.length,a=0;c>a;)e.f(t,r=u[a++],n[r]);return t}},FLlr:function(t,n,r){var e=r("XKFU");e(e.P,"String",{repeat:r("l0Rn")})},FlsD:function(t,n,r){var e=r("0/R4");r("Xtr8")("isExtensible",function(t){return function(n){return!!e(n)&&(!t||t(n))}})},GNAe:function(t,n,r){var e=r("XKFU"),o=r("PKUr");e(e.G+e.F*(parseInt!=o),{parseInt:o})},H6hf:function(t,n,r){var e=r("y3w9");t.exports=function(t,n,r,o){try{return o?n(e(r)[0],r[1]):n(r)}catch(u){var i=t.return;throw void 0!==i&&e(i.call(t)),u}}},"HAE/":function(t,n,r){var e=r("XKFU");e(e.S+e.F*!r("nh4g"),"Object",{defineProperty:r("hswa").f})},HEwt:function(t,n,r){"use strict";var e=r("m0Pp"),o=r("XKFU"),i=r("S/j/"),u=r("H6hf"),c=r("M6Qj"),a=r("ne8i"),f=r("8a7r"),s=r("J+6e");o(o.S+o.F*!r("XMVh")(function(t){Array.from(t)}),"Array",{from:function(t){var n,r,o,l,h=i(t),p="function"==typeof this?this:Array,v=arguments.length,y=v>1?arguments[1]:void 0,d=void 0!==y,g=0,b=s(h);if(d&&(y=e(y,v>2?arguments[2]:void 0,2)),null==b||p==Array&&c(b))for(r=new p(n=a(h.length));n>g;g++)f(r,g,d?y(h[g],g):h[g]);else for(l=b.call(h),r=new p;!(o=l.next()).done;g++)f(r,g,d?u(l,y,[o.value,g],!0):o.value);return r.length=g,r}})},I78e:function(t,n,r){"use strict";var e=r("XKFU"),o=r("+rLv"),i=r("LZWt"),u=r("d/Gc"),c=r("ne8i"),a=[].slice;e(e.P+e.F*r("eeVq")(function(){o&&a.call(o)}),"Array",{slice:function(t,n){var r=c(this.length),e=i(this);if(n=void 0===n?r:n,"Array"==e)return a.call(this,t,n);for(var o=u(t,r),f=u(n,r),s=c(f-o),l=new Array(s),h=0;h1?arguments[1]:void 0)}}),r("nGyu")(i)},"IU+Z":function(t,n,r){"use strict";r("sMXx");var e=r("KroJ"),o=r("Mukb"),i=r("eeVq"),u=r("vhPU"),c=r("K0xU"),a=r("Ugos"),f=c("species"),s=!i(function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")}),l=function(){var t=/(?:)/,n=t.exec;t.exec=function(){return n.apply(this,arguments)};var r="ab".split(t);return 2===r.length&&"a"===r[0]&&"b"===r[1]}();t.exports=function(t,n,r){var h=c(t),p=!i(function(){var n={};return n[h]=function(){return 7},7!=""[t](n)}),v=p?!i(function(){var n=!1,r=/a/;return r.exec=function(){return n=!0,null},"split"===t&&(r.constructor={},r.constructor[f]=function(){return r}),r[h](""),!n}):void 0;if(!p||!v||"replace"===t&&!s||"split"===t&&!l){var y=/./[h],d=r(u,h,""[t],function(t,n,r,e,o){return n.exec===a?p&&!o?{done:!0,value:y.call(n,r,e)}:{done:!0,value:t.call(r,n,e)}:{done:!1}}),g=d[1];e(String.prototype,t,d[0]),o(RegExp.prototype,h,2==n?function(t,n){return g.call(t,this,n)}:function(t){return g.call(t,this)})}}},IXt9:function(t,n,r){"use strict";var e=r("0/R4"),o=r("OP3Y"),i=r("K0xU")("hasInstance"),u=Function.prototype;i in u||r("hswa").f(u,i,{value:function(t){if("function"!=typeof this||!e(t))return!1;if(!e(this.prototype))return t instanceof this;for(;t=o(t);)if(this.prototype===t)return!0;return!1}})},Iw71:function(t,n,r){var e=r("0/R4"),o=r("dyZX").document,i=e(o)&&e(o.createElement);t.exports=function(t){return i?o.createElement(t):{}}},"J+6e":function(t,n,r){var e=r("I8a+"),o=r("K0xU")("iterator"),i=r("hPIQ");t.exports=r("g3g5").getIteratorMethod=function(t){if(null!=t)return t[o]||t["@@iterator"]||i[e(t)]}},JCqj:function(t,n,r){"use strict";r("OGtf")("sup",function(t){return function(){return t(this,"sup","","")}})},Jcmo:function(t,n,r){var e=r("XKFU"),o=Math.exp;e(e.S,"Math",{cosh:function(t){return(o(t=+t)+o(-t))/2}})},JduL:function(t,n,r){r("Xtr8")("getOwnPropertyNames",function(){return r("e7yV").f})},JiEa:function(t,n){n.f=Object.getOwnPropertySymbols},K0xU:function(t,n,r){var e=r("VTer")("wks"),o=r("ylqs"),i=r("dyZX").Symbol,u="function"==typeof i;(t.exports=function(t){return e[t]||(e[t]=u&&i[t]||(u?i:o)("Symbol."+t))}).store=e},KKXr:function(t,n,r){"use strict";var e=r("quPj"),o=r("y3w9"),i=r("69bn"),u=r("A5AN"),c=r("ne8i"),a=r("Xxuz"),f=r("Ugos"),s=Math.min,l=[].push,h=!!function(){try{return new RegExp("x","y")}catch(t){}}();r("IU+Z")("split",2,function(t,n,r,p){var v;return v="c"=="abbc".split(/(b)*/)[1]||4!="test".split(/(?:)/,-1).length||2!="ab".split(/(?:ab)*/).length||4!=".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length?function(t,n){var o=String(this);if(void 0===t&&0===n)return[];if(!e(t))return r.call(o,t,n);for(var i,u,c,a=[],s=0,h=void 0===n?4294967295:n>>>0,p=new RegExp(t.source,(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":"")+"g");(i=f.call(p,o))&&!((u=p.lastIndex)>s&&(a.push(o.slice(s,i.index)),i.length>1&&i.index=h));)p.lastIndex===i.index&&p.lastIndex++;return s===o.length?!c&&p.test("")||a.push(""):a.push(o.slice(s)),a.length>h?a.slice(0,h):a}:"0".split(void 0,0).length?function(t,n){return void 0===t&&0===n?[]:r.call(this,t,n)}:r,[function(r,e){var o=t(this),i=null==r?void 0:r[n];return void 0!==i?i.call(r,o,e):v.call(String(o),r,e)},function(t,n){var e=p(v,t,this,n,v!==r);if(e.done)return e.value;var f=o(t),l=String(this),y=i(f,RegExp),d=f.unicode,g=new y(h?f:"^(?:"+f.source+")",(f.ignoreCase?"i":"")+(f.multiline?"m":"")+(f.unicode?"u":"")+(h?"y":"g")),b=void 0===n?4294967295:n>>>0;if(0===b)return[];if(0===l.length)return null===a(g,l)?[l]:[];for(var m=0,_=0,k=[];_document.F=Object<\/script>"),t.close(),a=t.F;e--;)delete a.prototype[i[e]];return a()};t.exports=Object.create||function(t,n){var r;return null!==t?(c.prototype=e(t),r=new c,c.prototype=null,r[u]=t):r=a(),void 0===n?r:o(r,n)}},L9s1:function(t,n,r){"use strict";var e=r("XKFU"),o=r("0sh+");e(e.P+e.F*r("UUeW")("includes"),"String",{includes:function(t){return!!~o(this,t,"includes").indexOf(t,arguments.length>1?arguments[1]:void 0)}})},LK8F:function(t,n,r){var e=r("XKFU");e(e.S,"Array",{isArray:r("EWmC")})},LQAc:function(t,n){t.exports=!1},LVwc:function(t,n){var r=Math.expm1;t.exports=!r||r(10)>22025.465794806718||r(10)<22025.465794806718||-2e-17!=r(-2e-17)?function(t){return 0==(t=+t)?t:t>-1e-6&&t<1e-6?t+t*t/2:Math.exp(t)-1}:r},LZWt:function(t,n){var r={}.toString;t.exports=function(t){return r.call(t).slice(8,-1)}},Ljet:function(t,n,r){var e=r("XKFU");e(e.S,"Number",{EPSILON:Math.pow(2,-52)})},Lmuc:function(t,n,r){r("xfY5"),r("A2zW"),r("VKir"),r("Ljet"),r("/KAi"),r("fN96"),r("7h0T"),r("sbF8"),r("h/M4"),r("knhD"),r("XfKG"),r("BP8U"),t.exports=r("g3g5").Number},LyE8:function(t,n,r){"use strict";var e=r("eeVq");t.exports=function(t,n){return!!t&&e(function(){n?t.call(null,function(){},1):t.call(null)})}},M6Qj:function(t,n,r){var e=r("hPIQ"),o=r("K0xU")("iterator"),i=Array.prototype;t.exports=function(t){return void 0!==t&&(e.Array===t||i[o]===t)}},MfQN:function(t,n){t.exports=function(t,n,r){var e=void 0===r;switch(n.length){case 0:return e?t():t.call(r);case 1:return e?t(n[0]):t.call(r,n[0]);case 2:return e?t(n[0],n[1]):t.call(r,n[0],n[1]);case 3:return e?t(n[0],n[1],n[2]):t.call(r,n[0],n[1],n[2]);case 4:return e?t(n[0],n[1],n[2],n[3]):t.call(r,n[0],n[1],n[2],n[3])}return t.apply(r,n)}},MtdB:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{clz32:function(t){return(t>>>=0)?31-Math.floor(Math.log(t+.5)*Math.LOG2E):32}})},Mukb:function(t,n,r){var e=r("hswa"),o=r("RjD/");t.exports=r("nh4g")?function(t,n,r){return e.f(t,n,o(1,r))}:function(t,n,r){return t[n]=r,t}},N8g3:function(t,n,r){n.f=r("K0xU")},Nr18:function(t,n,r){"use strict";var e=r("S/j/"),o=r("d/Gc"),i=r("ne8i");t.exports=function(t){for(var n=e(this),r=i(n.length),u=arguments.length,c=o(u>1?arguments[1]:void 0,r),a=u>2?arguments[2]:void 0,f=void 0===a?r:o(a,r);f>c;)n[c++]=t;return n}},Nz9U:function(t,n,r){"use strict";var e=r("XKFU"),o=r("aCFj"),i=[].join;e(e.P+e.F*(r("Ymqv")!=Object||!r("LyE8")(i)),"Array",{join:function(t){return i.call(o(this),void 0===t?",":t)}})},OEbY:function(t,n,r){r("nh4g")&&"g"!=/./g.flags&&r("hswa").f(RegExp.prototype,"flags",{configurable:!0,get:r("C/va")})},OG14:function(t,n,r){"use strict";var e=r("y3w9"),o=r("g6HL"),i=r("Xxuz");r("IU+Z")("search",1,function(t,n,r,u){return[function(r){var e=t(this),o=null==r?void 0:r[n];return void 0!==o?o.call(r,e):new RegExp(r)[n](String(e))},function(t){var n=u(r,t,this);if(n.done)return n.value;var c=e(t),a=String(this),f=c.lastIndex;o(f,0)||(c.lastIndex=0);var s=i(c,a);return o(c.lastIndex,f)||(c.lastIndex=f),null===s?-1:s.index}]})},OGtf:function(t,n,r){var e=r("XKFU"),o=r("eeVq"),i=r("vhPU"),u=/"/g,c=function(t,n,r,e){var o=String(i(t)),c="<"+n;return""!==r&&(c+=" "+r+'="'+String(e).replace(u,""")+'"'),c+">"+o+""};t.exports=function(t,n){var r={};r[t]=n(c),e(e.P+e.F*o(function(){var n=""[t]('"');return n!==n.toLowerCase()||n.split('"').length>3}),"String",r)}},OP3Y:function(t,n,r){var e=r("aagx"),o=r("S/j/"),i=r("YTvA")("IE_PROTO"),u=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=o(t),e(t,i)?t[i]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?u:null}},OnI7:function(t,n,r){var e=r("dyZX"),o=r("g3g5"),i=r("LQAc"),u=r("N8g3"),c=r("hswa").f;t.exports=function(t){var n=o.Symbol||(o.Symbol=i?{}:e.Symbol||{});"_"==t.charAt(0)||t in n||c(n,t,{value:u.f(t)})}},Oyvg:function(t,n,r){var e=r("dyZX"),o=r("Xbzi"),i=r("hswa").f,u=r("kJMx").f,c=r("quPj"),a=r("C/va"),f=e.RegExp,s=f,l=f.prototype,h=/a/g,p=/a/g,v=new f(h)!==h;if(r("nh4g")&&(!v||r("eeVq")(function(){return p[r("K0xU")("match")]=!1,f(h)!=h||f(p)==p||"/a/i"!=f(h,"i")}))){f=function(t,n){var r=this instanceof f,e=c(t),i=void 0===n;return!r&&e&&t.constructor===f&&i?t:o(v?new s(e&&!i?t.source:t,n):s((e=t instanceof f)?t.source:t,e&&i?a.call(t):n),r?this:l,f)};for(var y=function(t){t in f||i(f,t,{configurable:!0,get:function(){return s[t]},set:function(n){s[t]=n}})},d=u(s),g=0;d.length>g;)y(d[g++]);l.constructor=f,f.prototype=l,r("KroJ")(e,"RegExp",f)}r("elZq")("RegExp")},PKUr:function(t,n,r){var e=r("dyZX").parseInt,o=r("qncB").trim,i=r("/e88"),u=/^[-+]?0[xX]/;t.exports=8!==e(i+"08")||22!==e(i+"0x16")?function(t,n){var r=o(String(t),3);return e(r,n>>>0||(u.test(r)?16:10))}:e},QaDb:function(t,n,r){"use strict";var e=r("Kuth"),o=r("RjD/"),i=r("fyDq"),u={};r("Mukb")(u,r("K0xU")("iterator"),function(){return this}),t.exports=function(t,n,r){t.prototype=e(u,{next:o(1,r)}),i(t,n+" Iterator")}},RW0V:function(t,n,r){var e=r("S/j/"),o=r("DVgA");r("Xtr8")("keys",function(){return function(t){return o(e(t))}})},RYi7:function(t,n){var r=Math.ceil,e=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?e:r)(t)}},"RjD/":function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},"S/j/":function(t,n,r){var e=r("vhPU");t.exports=function(t){return Object(e(t))}},SMB2:function(t,n,r){"use strict";r("OGtf")("bold",function(t){return function(){return t(this,"b","","")}})},SPin:function(t,n,r){"use strict";var e=r("XKFU"),o=r("eyMr");e(e.P+e.F*!r("LyE8")([].reduceRight,!0),"Array",{reduceRight:function(t){return o(this,t,arguments.length,arguments[1],!0)}})},SRfc:function(t,n,r){"use strict";var e=r("y3w9"),o=r("ne8i"),i=r("A5AN"),u=r("Xxuz");r("IU+Z")("match",1,function(t,n,r,c){return[function(r){var e=t(this),o=null==r?void 0:r[n];return void 0!==o?o.call(r,e):new RegExp(r)[n](String(e))},function(t){var n=c(r,t,this);if(n.done)return n.value;var a=e(t),f=String(this);if(!a.global)return u(a,f);var s=a.unicode;a.lastIndex=0;for(var l,h=[],p=0;null!==(l=u(a,f));){var v=String(l[0]);h[p]=v,""===v&&(a.lastIndex=i(f,o(a.lastIndex),s)),p++}return 0===p?null:h}]})},SlkY:function(t,n,r){var e=r("m0Pp"),o=r("H6hf"),i=r("M6Qj"),u=r("y3w9"),c=r("ne8i"),a=r("J+6e"),f={},s={};(n=t.exports=function(t,n,r,l,h){var p,v,y,d,g=h?function(){return t}:a(t),b=e(r,l,n?2:1),m=0;if("function"!=typeof g)throw TypeError(t+" is not iterable!");if(i(g)){for(p=c(t.length);p>m;m++)if((d=n?b(u(v=t[m])[0],v[1]):b(t[m]))===f||d===s)return d}else for(y=g.call(t);!(v=y.next()).done;)if((d=o(y,b,v.value,n))===f||d===s)return d}).BREAK=f,n.RETURN=s},T39b:function(t,n,r){"use strict";var e=r("wmvG"),o=r("s5qY");t.exports=r("4LiD")("Set",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{add:function(t){return e.def(o(this,"Set"),t=0===t?0:t,t)}},e)},Tze0:function(t,n,r){"use strict";r("qncB")("trim",function(t){return function(){return t(this,3)}})},U2t9:function(t,n,r){var e=r("XKFU"),o=Math.asinh;e(e.S+e.F*!(o&&1/o(0)>0),"Math",{asinh:function t(n){return isFinite(n=+n)&&0!=n?n<0?-t(-n):Math.log(n+Math.sqrt(n*n+1)):n}})},UUeW:function(t,n,r){var e=r("K0xU")("match");t.exports=function(t){var n=/./;try{"/./"[t](n)}catch(r){try{return n[e]=!1,!"/./"[t](n)}catch(o){}}return!0}},Ugos:function(t,n,r){"use strict";var e,o,i=r("C/va"),u=RegExp.prototype.exec,c=String.prototype.replace,a=u,f=(o=/b*/g,u.call(e=/a/,"a"),u.call(o,"a"),0!==e.lastIndex||0!==o.lastIndex),s=void 0!==/()??/.exec("")[1];(f||s)&&(a=function(t){var n,r,e,o,a=this;return s&&(r=new RegExp("^"+a.source+"$(?!\\s)",i.call(a))),f&&(n=a.lastIndex),e=u.call(a,t),f&&e&&(a.lastIndex=a.global?e.index+e[0].length:n),s&&e&&e.length>1&&c.call(e[0],r,function(){for(o=1;ou;){if(n=+arguments[u++],o(n,1114111)!==n)throw RangeError(n+" is not a valid code point");r.push(n<65536?i(n):i(55296+((n-=65536)>>10),n%1024+56320))}return r.join("")}})},WLL4:function(t,n,r){var e=r("XKFU");e(e.S+e.F*!r("nh4g"),"Object",{defineProperties:r("FJW5")})},XKFU:function(t,n,r){var e=r("dyZX"),o=r("g3g5"),i=r("Mukb"),u=r("KroJ"),c=r("m0Pp"),a=function(t,n,r){var f,s,l,h,p=t&a.F,v=t&a.G,y=t&a.P,d=t&a.B,g=v?e:t&a.S?e[n]||(e[n]={}):(e[n]||{}).prototype,b=v?o:o[n]||(o[n]={}),m=b.prototype||(b.prototype={});for(f in v&&(r=n),r)l=((s=!p&&g&&void 0!==g[f])?g:r)[f],h=d&&s?c(l,e):y&&"function"==typeof l?c(Function.call,l):l,g&&u(g,f,l,t&a.U),b[f]!=l&&i(b,f,h),y&&m[f]!=l&&(m[f]=l)};e.core=o,a.F=1,a.G=2,a.S=4,a.P=8,a.B=16,a.W=32,a.U=64,a.R=128,t.exports=a},XMVh:function(t,n,r){var e=r("K0xU")("iterator"),o=!1;try{var i=[7][e]();i.return=function(){o=!0},Array.from(i,function(){throw 2})}catch(u){}t.exports=function(t,n){if(!n&&!o)return!1;var r=!1;try{var i=[7],c=i[e]();c.next=function(){return{done:r=!0}},i[e]=function(){return c},t(i)}catch(u){}return r}},Xbzi:function(t,n,r){var e=r("0/R4"),o=r("i5dc").set;t.exports=function(t,n,r){var i,u=n.constructor;return u!==r&&"function"==typeof u&&(i=u.prototype)!==r.prototype&&e(i)&&o&&o(t,i),t}},XfKG:function(t,n,r){var e=r("XKFU"),o=r("11IZ");e(e.S+e.F*(Number.parseFloat!=o),"Number",{parseFloat:o})},XfO3:function(t,n,r){"use strict";var e=r("AvRE")(!0);r("Afnz")(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,n=this._t,r=this._i;return r>=n.length?{value:void 0,done:!0}:(t=e(n,r),this._i+=t.length,{value:t,done:!1})})},Xtr8:function(t,n,r){var e=r("XKFU"),o=r("g3g5"),i=r("eeVq");t.exports=function(t,n){var r=(o.Object||{})[t]||Object[t],u={};u[t]=n(r),e(e.S+e.F*i(function(){r(1)}),"Object",u)}},Xxuz:function(t,n,r){"use strict";var e=r("I8a+"),o=RegExp.prototype.exec;t.exports=function(t,n){var r=t.exec;if("function"==typeof r){var i=r.call(t,n);if("object"!=typeof i)throw new TypeError("RegExp exec method returned something other than an Object or null");return i}if("RegExp"!==e(t))throw new TypeError("RegExp#exec called on incompatible receiver");return o.call(t,n)}},YJVH:function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(4);e(e.P+e.F*!r("LyE8")([].every,!0),"Array",{every:function(t){return o(this,t,arguments[1])}})},YTvA:function(t,n,r){var e=r("VTer")("keys"),o=r("ylqs");t.exports=function(t){return e[t]||(e[t]=o(t))}},Ymqv:function(t,n,r){var e=r("LZWt");t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==e(t)?t.split(""):Object(t)}},Z6vF:function(t,n,r){var e=r("ylqs")("meta"),o=r("0/R4"),i=r("aagx"),u=r("hswa").f,c=0,a=Object.isExtensible||function(){return!0},f=!r("eeVq")(function(){return a(Object.preventExtensions({}))}),s=function(t){u(t,e,{value:{i:"O"+ ++c,w:{}}})},l=t.exports={KEY:e,NEED:!1,fastKey:function(t,n){if(!o(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!i(t,e)){if(!a(t))return"F";if(!n)return"E";s(t)}return t[e].i},getWeak:function(t,n){if(!i(t,e)){if(!a(t))return!0;if(!n)return!1;s(t)}return t[e].w},onFreeze:function(t){return f&&l.NEED&&a(t)&&!i(t,e)&&s(t),t}}},ZD67:function(t,n,r){"use strict";var e=r("3Lyj"),o=r("Z6vF").getWeak,i=r("y3w9"),u=r("0/R4"),c=r("9gX7"),a=r("SlkY"),f=r("CkkT"),s=r("aagx"),l=r("s5qY"),h=f(5),p=f(6),v=0,y=function(t){return t._l||(t._l=new d)},d=function(){this.a=[]},g=function(t,n){return h(t.a,function(t){return t[0]===n})};d.prototype={get:function(t){var n=g(this,t);if(n)return n[1]},has:function(t){return!!g(this,t)},set:function(t,n){var r=g(this,t);r?r[1]=n:this.a.push([t,n])},delete:function(t){var n=p(this.a,function(n){return n[0]===t});return~n&&this.a.splice(n,1),!!~n}},t.exports={getConstructor:function(t,n,r,i){var f=t(function(t,e){c(t,f,n,"_i"),t._t=n,t._i=v++,t._l=void 0,null!=e&&a(e,r,t[i],t)});return e(f.prototype,{delete:function(t){if(!u(t))return!1;var r=o(t);return!0===r?y(l(this,n)).delete(t):r&&s(r,this._i)&&delete r[this._i]},has:function(t){if(!u(t))return!1;var r=o(t);return!0===r?y(l(this,n)).has(t):r&&s(r,this._i)}}),f},def:function(t,n,r){var e=o(i(n),!0);return!0===e?y(t).set(n,r):e[t._i]=r,t},ufstore:y}},Zshi:function(t,n,r){var e=r("0/R4");r("Xtr8")("isFrozen",function(t){return function(n){return!e(n)||!!t&&t(n)}})},Zz4T:function(t,n,r){"use strict";r("OGtf")("sub",function(t){return function(){return t(this,"sub","","")}})},a1Th:function(t,n,r){"use strict";r("OEbY");var e=r("y3w9"),o=r("C/va"),i=r("nh4g"),u=/./.toString,c=function(t){r("KroJ")(RegExp.prototype,"toString",t,!0)};r("eeVq")(function(){return"/a/b"!=u.call({source:"a",flags:"b"})})?c(function(){var t=e(this);return"/".concat(t.source,"/","flags"in t?t.flags:!i&&t instanceof RegExp?o.call(t):void 0)}):"toString"!=u.name&&c(function(){return u.call(this)})},aCFj:function(t,n,r){var e=r("Ymqv"),o=r("vhPU");t.exports=function(t){return e(o(t))}},aagx:function(t,n){var r={}.hasOwnProperty;t.exports=function(t,n){return r.call(t,n)}},apmT:function(t,n,r){var e=r("0/R4");t.exports=function(t,n){if(!e(t))return t;var r,o;if(n&&"function"==typeof(r=t.toString)&&!e(o=r.call(t)))return o;if("function"==typeof(r=t.valueOf)&&!e(o=r.call(t)))return o;if(!n&&"function"==typeof(r=t.toString)&&!e(o=r.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},bBoP:function(t,n,r){var e=r("XKFU"),o=r("LVwc"),i=Math.exp;e(e.S+e.F*r("eeVq")(function(){return-2e-17!=!Math.sinh(-2e-17)}),"Math",{sinh:function(t){return Math.abs(t=+t)<1?(o(t)-o(-t))/2:(i(t-1)-i(-t-1))*(Math.E/2)}})},bDcW:function(t,n,r){"use strict";r("OGtf")("fontcolor",function(t){return function(n){return t(this,"font","color",n)}})},bHtr:function(t,n,r){var e=r("XKFU");e(e.P,"Array",{fill:r("Nr18")}),r("nGyu")("fill")},bWfx:function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(1);e(e.P+e.F*!r("LyE8")([].map,!0),"Array",{map:function(t){return o(this,t,arguments[1])}})},czNK:function(t,n,r){"use strict";var e=r("DVgA"),o=r("JiEa"),i=r("UqcF"),u=r("S/j/"),c=r("Ymqv"),a=Object.assign;t.exports=!a||r("eeVq")(function(){var t={},n={},r=Symbol(),e="abcdefghijklmnopqrst";return t[r]=7,e.split("").forEach(function(t){n[t]=t}),7!=a({},t)[r]||Object.keys(a({},n)).join("")!=e})?function(t,n){for(var r=u(t),a=arguments.length,f=1,s=o.f,l=i.f;a>f;)for(var h,p=c(arguments[f++]),v=s?e(p).concat(s(p)):e(p),y=v.length,d=0;y>d;)l.call(p,h=v[d++])&&(r[h]=p[h]);return r}:a},"d/Gc":function(t,n,r){var e=r("RYi7"),o=Math.max,i=Math.min;t.exports=function(t,n){return(t=e(t))<0?o(t+n,0):i(t,n)}},"dE+T":function(t,n,r){var e=r("XKFU");e(e.P,"Array",{copyWithin:r("upKx")}),r("nGyu")("copyWithin")},dQfE:function(t,n,r){r("XfO3"),r("LK8F"),r("HEwt"),r("6AQ9"),r("Nz9U"),r("I78e"),r("Vd3H"),r("8+KV"),r("bWfx"),r("0l/t"),r("dZ+Y"),r("YJVH"),r("DNiP"),r("SPin"),r("V+eJ"),r("mGWK"),r("dE+T"),r("bHtr"),r("dRSK"),r("INYr"),r("0E+W"),r("yt8O"),t.exports=r("g3g5").Array},dRSK:function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(5),i=!0;"find"in[]&&Array(1).find(function(){i=!1}),e(e.P+e.F*i,"Array",{find:function(t){return o(this,t,arguments.length>1?arguments[1]:void 0)}}),r("nGyu")("find")},"dZ+Y":function(t,n,r){"use strict";var e=r("XKFU"),o=r("CkkT")(3);e(e.P+e.F*!r("LyE8")([].some,!0),"Array",{some:function(t){return o(this,t,arguments[1])}})},dyZX:function(t,n){var r=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},e7yV:function(t,n,r){var e=r("aCFj"),o=r("kJMx").f,i={}.toString,u="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];t.exports.f=function(t){return u&&"[object Window]"==i.call(t)?function(t){try{return o(t)}catch(n){return u.slice()}}(t):o(e(t))}},eHKK:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{log10:function(t){return Math.log(t)*Math.LOG10E}})},eI33:function(t,n,r){var e=r("XKFU"),o=r("aCFj"),i=r("ne8i");e(e.S,"String",{raw:function(t){for(var n=o(t.raw),r=i(n.length),e=arguments.length,u=[],c=0;r>c;)u.push(String(n[c++])),c=0:l>h;h+=p)h in s&&(c=n(c,s[h],h,f));return c}},"f3/d":function(t,n,r){var e=r("hswa").f,o=Function.prototype,i=/^\s*function ([^ (]*)/;"name"in o||r("nh4g")&&e(o,"name",{configurable:!0,get:function(){try{return(""+this).match(i)[1]}catch(t){return""}}})},fN96:function(t,n,r){var e=r("XKFU");e(e.S,"Number",{isInteger:r("nBIS")})},fyDq:function(t,n,r){var e=r("hswa").f,o=r("aagx"),i=r("K0xU")("toStringTag");t.exports=function(t,n,r){t&&!o(t=r?t:t.prototype,i)&&e(t,i,{configurable:!0,value:n})}},fyVe:function(t,n,r){var e=r("XKFU"),o=r("1sa7"),i=Math.sqrt,u=Math.acosh;e(e.S+e.F*!(u&&710==Math.floor(u(Number.MAX_VALUE))&&u(1/0)==1/0),"Math",{acosh:function(t){return(t=+t)<1?NaN:t>94906265.62425156?Math.log(t)+Math.LN2:o(t-1+i(t-1)*i(t+1))}})},g3g5:function(t,n){var r=t.exports={version:"2.6.1"};"number"==typeof __e&&(__e=r)},g4EE:function(t,n,r){"use strict";var e=r("y3w9"),o=r("apmT");t.exports=function(t){if("string"!==t&&"number"!==t&&"default"!==t)throw TypeError("Incorrect hint");return o(e(this),"number"!=t)}},g6HL:function(t,n){t.exports=Object.is||function(t,n){return t===n?0!==t||1/t==1/n:t!=t&&n!=n}},"h/M4":function(t,n,r){var e=r("XKFU");e(e.S,"Number",{MAX_SAFE_INTEGER:9007199254740991})},h7Nl:function(t,n,r){var e=Date.prototype,o=e.toString,i=e.getTime;new Date(NaN)+""!="Invalid Date"&&r("KroJ")(e,"toString",function(){var t=i.call(this);return t==t?o.call(this):"Invalid Date"})},hEkN:function(t,n,r){"use strict";r("OGtf")("anchor",function(t){return function(n){return t(this,"a","name",n)}})},hHhE:function(t,n,r){var e=r("XKFU");e(e.S,"Object",{create:r("Kuth")})},hLT2:function(t,n,r){var e=r("XKFU");e(e.S,"Math",{trunc:function(t){return(t>0?Math.floor:Math.ceil)(t)}})},"hN/g":function(t,n,r){"use strict";r.r(n),r("dQfE"),r("nx1v"),r("4A4+"),r("qKs0"),r("CuTL"),r("Lmuc"),r("99sg"),r("ifmr"),r("oka+"),r("rfyP"),r("VXxg"),r("V5/Y"),r("vqGA"),r("hYbK"),r("0TWp")},hPIQ:function(t,n){t.exports={}},hYbK:function(t,n,r){r("Btvt"),r("yt8O"),r("EK0E"),t.exports=r("g3g5").WeakMap},hswa:function(t,n,r){var e=r("y3w9"),o=r("xpql"),i=r("apmT"),u=Object.defineProperty;n.f=r("nh4g")?Object.defineProperty:function(t,n,r){if(e(t),n=i(n,!0),e(r),o)try{return u(t,n,r)}catch(c){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(t[n]=r.value),t}},i5dc:function(t,n,r){var e=r("0/R4"),o=r("y3w9"),i=function(t,n){if(o(t),!e(n)&&null!==n)throw TypeError(n+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,n,e){try{(e=r("m0Pp")(Function.call,r("EemH").f(Object.prototype,"__proto__").set,2))(t,[]),n=!(t instanceof Array)}catch(o){n=!0}return function(t,r){return i(t,r),n?t.__proto__=r:e(t,r),t}}({},!1):void 0),check:i}},ifmr:function(t,n,r){r("tyy+"),t.exports=r("g3g5").parseFloat},ioFf:function(t,n,r){"use strict";var e=r("dyZX"),o=r("aagx"),i=r("nh4g"),u=r("XKFU"),c=r("KroJ"),a=r("Z6vF").KEY,f=r("eeVq"),s=r("VTer"),l=r("fyDq"),h=r("ylqs"),p=r("K0xU"),v=r("N8g3"),y=r("OnI7"),d=r("1MBn"),g=r("EWmC"),b=r("y3w9"),m=r("0/R4"),_=r("aCFj"),k=r("apmT"),w=r("RjD/"),x=r("Kuth"),S=r("e7yV"),F=r("EemH"),T=r("hswa"),O=r("DVgA"),j=F.f,M=T.f,K=S.f,U=e.Symbol,X=e.JSON,Z=X&&X.stringify,z=p("_hidden"),E=p("toPrimitive"),D={}.propertyIsEnumerable,A=s("symbol-registry"),L=s("symbols"),I=s("op-symbols"),P=Object.prototype,C="function"==typeof U,q=e.QObject,V=!q||!q.prototype||!q.prototype.findChild,R=i&&f(function(){return 7!=x(M({},"a",{get:function(){return M(this,"a",{value:7}).a}})).a})?function(t,n,r){var e=j(P,n);e&&delete P[n],M(t,n,r),e&&t!==P&&M(P,n,e)}:M,G=function(t){var n=L[t]=x(U.prototype);return n._k=t,n},J=C&&"symbol"==typeof U.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof U},Y=function(t,n,r){return t===P&&Y(I,n,r),b(t),n=k(n,!0),b(r),o(L,n)?(r.enumerable?(o(t,z)&&t[z][n]&&(t[z][n]=!1),r=x(r,{enumerable:w(0,!1)})):(o(t,z)||M(t,z,w(1,{})),t[z][n]=!0),R(t,n,r)):M(t,n,r)},H=function(t,n){b(t);for(var r,e=d(n=_(n)),o=0,i=e.length;i>o;)Y(t,r=e[o++],n[r]);return t},N=function(t){var n=D.call(this,t=k(t,!0));return!(this===P&&o(L,t)&&!o(I,t))&&(!(n||!o(this,t)||!o(L,t)||o(this,z)&&this[z][t])||n)},W=function(t,n){if(t=_(t),n=k(n,!0),t!==P||!o(L,n)||o(I,n)){var r=j(t,n);return!r||!o(L,n)||o(t,z)&&t[z][n]||(r.enumerable=!0),r}},B=function(t){for(var n,r=K(_(t)),e=[],i=0;r.length>i;)o(L,n=r[i++])||n==z||n==a||e.push(n);return e},Q=function(t){for(var n,r=t===P,e=K(r?I:_(t)),i=[],u=0;e.length>u;)!o(L,n=e[u++])||r&&!o(P,n)||i.push(L[n]);return i};C||(c((U=function(){if(this instanceof U)throw TypeError("Symbol is not a constructor!");var t=h(arguments.length>0?arguments[0]:void 0),n=function(r){this===P&&n.call(I,r),o(this,z)&&o(this[z],t)&&(this[z][t]=!1),R(this,t,w(1,r))};return i&&V&&R(P,t,{configurable:!0,set:n}),G(t)}).prototype,"toString",function(){return this._k}),F.f=W,T.f=Y,r("kJMx").f=S.f=B,r("UqcF").f=N,r("JiEa").f=Q,i&&!r("LQAc")&&c(P,"propertyIsEnumerable",N,!0),v.f=function(t){return G(p(t))}),u(u.G+u.W+u.F*!C,{Symbol:U});for(var $="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),tt=0;$.length>tt;)p($[tt++]);for(var nt=O(p.store),rt=0;nt.length>rt;)y(nt[rt++]);u(u.S+u.F*!C,"Symbol",{for:function(t){return o(A,t+="")?A[t]:A[t]=U(t)},keyFor:function(t){if(!J(t))throw TypeError(t+" is not a symbol!");for(var n in A)if(A[n]===t)return n},useSetter:function(){V=!0},useSimple:function(){V=!1}}),u(u.S+u.F*!C,"Object",{create:function(t,n){return void 0===n?x(t):H(x(t),n)},defineProperty:Y,defineProperties:H,getOwnPropertyDescriptor:W,getOwnPropertyNames:B,getOwnPropertySymbols:Q}),X&&u(u.S+u.F*(!C||f(function(){var t=U();return"[null]"!=Z([t])||"{}"!=Z({a:t})||"{}"!=Z(Object(t))})),"JSON",{stringify:function(t){for(var n,r,e=[t],o=1;arguments.length>o;)e.push(arguments[o++]);if(r=n=e[1],(m(n)||void 0!==t)&&!J(t))return g(n)||(n=function(t,n){if("function"==typeof r&&(n=r.call(this,t,n)),!J(n))return n}),e[1]=n,Z.apply(X,e)}}),U.prototype[E]||r("Mukb")(U.prototype,E,U.prototype.valueOf),l(U,"Symbol"),l(Math,"Math",!0),l(e.JSON,"JSON",!0)},jqX0:function(t,n,r){var e=r("XKFU"),o=r("jtBr");e(e.P+e.F*(Date.prototype.toISOString!==o),"Date",{toISOString:o})},jtBr:function(t,n,r){"use strict";var e=r("eeVq"),o=Date.prototype.getTime,i=Date.prototype.toISOString,u=function(t){return t>9?t:"0"+t};t.exports=e(function(){return"0385-07-25T07:06:39.999Z"!=i.call(new Date(-5e13-1))})||!e(function(){i.call(new Date(NaN))})?function(){if(!isFinite(o.call(this)))throw RangeError("Invalid time value");var t=this,n=t.getUTCFullYear(),r=t.getUTCMilliseconds(),e=n<0?"-":n>9999?"+":"";return e+("00000"+Math.abs(n)).slice(e?-6:-4)+"-"+u(t.getUTCMonth()+1)+"-"+u(t.getUTCDate())+"T"+u(t.getUTCHours())+":"+u(t.getUTCMinutes())+":"+u(t.getUTCSeconds())+"."+(r>99?r:"0"+u(r))+"Z"}:i},kJMx:function(t,n,r){var e=r("zhAb"),o=r("4R4u").concat("length","prototype");n.f=Object.getOwnPropertyNames||function(t){return e(t,o)}},kcoS:function(t,n,r){var e=r("lvtm"),o=Math.pow,i=o(2,-52),u=o(2,-23),c=o(2,127)*(2-u),a=o(2,-126);t.exports=Math.fround||function(t){var n,r,o=Math.abs(t),f=e(t);return oc||r!=r?f*(1/0):f*r}},knhD:function(t,n,r){var e=r("XKFU");e(e.S,"Number",{MIN_SAFE_INTEGER:-9007199254740991})},l0Rn:function(t,n,r){"use strict";var e=r("RYi7"),o=r("vhPU");t.exports=function(t){var n=String(o(this)),r="",i=e(t);if(i<0||i==1/0)throw RangeError("Count can't be negative");for(;i>0;(i>>>=1)&&(n+=n))1&i&&(r+=n);return r}},lvtm:function(t,n){t.exports=Math.sign||function(t){return 0==(t=+t)||t!=t?t:t<0?-1:1}},m0Pp:function(t,n,r){var e=r("2OiF");t.exports=function(t,n,r){if(e(t),void 0===n)return t;switch(r){case 1:return function(r){return t.call(n,r)};case 2:return function(r,e){return t.call(n,r,e)};case 3:return function(r,e,o){return t.call(n,r,e,o)}}return function(){return t.apply(n,arguments)}}},mGWK:function(t,n,r){"use strict";var e=r("XKFU"),o=r("aCFj"),i=r("RYi7"),u=r("ne8i"),c=[].lastIndexOf,a=!!c&&1/[1].lastIndexOf(1,-0)<0;e(e.P+e.F*(a||!r("LyE8")(c)),"Array",{lastIndexOf:function(t){if(a)return c.apply(this,arguments)||0;var n=o(this),r=u(n.length),e=r-1;for(arguments.length>1&&(e=Math.min(e,i(arguments[1]))),e<0&&(e=r+e);e>=0;e--)if(e in n&&n[e]===t)return e||0;return-1}})},mYba:function(t,n,r){var e=r("aCFj"),o=r("EemH").f;r("Xtr8")("getOwnPropertyDescriptor",function(){return function(t,n){return o(e(t),n)}})},mura:function(t,n,r){var e=r("0/R4"),o=r("Z6vF").onFreeze;r("Xtr8")("preventExtensions",function(t){return function(n){return t&&e(n)?t(o(n)):n}})},nBIS:function(t,n,r){var e=r("0/R4"),o=Math.floor;t.exports=function(t){return!e(t)&&isFinite(t)&&o(t)===t}},nGyu:function(t,n,r){var e=r("K0xU")("unscopables"),o=Array.prototype;null==o[e]&&r("Mukb")(o,e,{}),t.exports=function(t){o[e][t]=!0}},nIY7:function(t,n,r){"use strict";r("OGtf")("big",function(t){return function(){return t(this,"big","","")}})},ne8i:function(t,n,r){var e=r("RYi7"),o=Math.min;t.exports=function(t){return t>0?o(e(t),9007199254740991):0}},nh4g:function(t,n,r){t.exports=!r("eeVq")(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},nsiH:function(t,n,r){"use strict";r("OGtf")("fontsize",function(t){return function(n){return t(this,"font","size",n)}})},nx1v:function(t,n,r){r("eM6i"),r("AphP"),r("jqX0"),r("h7Nl"),r("yM4b"),t.exports=Date},nzyx:function(t,n,r){var e=r("XKFU"),o=r("LVwc");e(e.S+e.F*(o!=Math.expm1),"Math",{expm1:o})},oDIu:function(t,n,r){"use strict";var e=r("XKFU"),o=r("AvRE")(!1);e(e.P,"String",{codePointAt:function(t){return o(this,t)}})},"oka+":function(t,n,r){r("GNAe"),t.exports=r("g3g5").parseInt},pIFo:function(t,n,r){"use strict";var e=r("y3w9"),o=r("S/j/"),i=r("ne8i"),u=r("RYi7"),c=r("A5AN"),a=r("Xxuz"),f=Math.max,s=Math.min,l=Math.floor,h=/\$([$&`']|\d\d?|<[^>]*>)/g,p=/\$([$&`']|\d\d?)/g;r("IU+Z")("replace",2,function(t,n,r,v){return[function(e,o){var i=t(this),u=null==e?void 0:e[n];return void 0!==u?u.call(e,i,o):r.call(String(i),e,o)},function(t,n){var o=v(r,t,this,n);if(o.done)return o.value;var l=e(t),h=String(this),p="function"==typeof n;p||(n=String(n));var d=l.global;if(d){var g=l.unicode;l.lastIndex=0}for(var b=[];;){var m=a(l,h);if(null===m)break;if(b.push(m),!d)break;""===String(m[0])&&(l.lastIndex=c(h,i(l.lastIndex),g))}for(var _,k="",w=0,x=0;x=w&&(k+=h.slice(w,F)+K,w=F+S.length)}return k+h.slice(w)}];function y(t,n,e,i,u,c){var a=e+t.length,f=i.length,s=p;return void 0!==u&&(u=o(u),s=h),r.call(c,s,function(r,o){var c;switch(o.charAt(0)){case"$":return"$";case"&":return t;case"`":return n.slice(0,e);case"'":return n.slice(a);case"<":c=u[o.slice(1,-1)];break;default:var s=+o;if(0===s)return o;if(s>f){var h=l(s/10);return 0===h?o:h<=f?void 0===i[h-1]?o.charAt(1):i[h-1]+o.charAt(1):o}c=i[s-1]}return void 0===c?"":c})}})},"pp/T":function(t,n,r){var e=r("XKFU");e(e.S,"Math",{log2:function(t){return Math.log(t)/Math.LN2}})},qKs0:function(t,n,r){r("Btvt"),r("XfO3"),r("rGqo"),r("9AAn"),t.exports=r("g3g5").Map},qncB:function(t,n,r){var e=r("XKFU"),o=r("vhPU"),i=r("eeVq"),u=r("/e88"),c="["+u+"]",a=RegExp("^"+c+c+"*"),f=RegExp(c+c+"*$"),s=function(t,n,r){var o={},c=i(function(){return!!u[t]()||"\u200b\x85"!="\u200b\x85"[t]()}),a=o[t]=c?n(l):u[t];r&&(o[r]=a),e(e.P+e.F*c,"String",o)},l=s.trim=function(t,n){return t=String(o(t)),1&n&&(t=t.replace(a,"")),2&n&&(t=t.replace(f,"")),t};t.exports=s},quPj:function(t,n,r){var e=r("0/R4"),o=r("LZWt"),i=r("K0xU")("match");t.exports=function(t){var n;return e(t)&&(void 0!==(n=t[i])?!!n:"RegExp"==o(t))}},rGqo:function(t,n,r){for(var e=r("yt8O"),o=r("DVgA"),i=r("KroJ"),u=r("dyZX"),c=r("Mukb"),a=r("hPIQ"),f=r("K0xU"),s=f("iterator"),l=f("toStringTag"),h=a.Array,p={CSSRuleList:!0,CSSStyleDeclaration:!1,CSSValueList:!1,ClientRectList:!1,DOMRectList:!1,DOMStringList:!1,DOMTokenList:!0,DataTransferItemList:!1,FileList:!1,HTMLAllCollection:!1,HTMLCollection:!1,HTMLFormElement:!1,HTMLSelectElement:!1,MediaList:!0,MimeTypeArray:!1,NamedNodeMap:!1,NodeList:!0,PaintRequestList:!1,Plugin:!1,PluginArray:!1,SVGLengthList:!1,SVGNumberList:!1,SVGPathSegList:!1,SVGPointList:!1,SVGStringList:!1,SVGTransformList:!1,SourceBufferList:!1,StyleSheetList:!0,TextTrackCueList:!1,TextTrackList:!1,TouchList:!1},v=o(p),y=0;y1?arguments[1]:void 0,e=o(n.length),c=void 0===r?e:Math.min(o(r),e),a=String(t);return u?u.call(n,a,c):n.slice(c-a.length,c)===a}})},s5qY:function(t,n,r){var e=r("0/R4");t.exports=function(t,n){if(!e(t)||t._t!==n)throw TypeError("Incompatible receiver, "+n+" required!");return t}},sMXx:function(t,n,r){"use strict";var e=r("Ugos");r("XKFU")({target:"RegExp",proto:!0,forced:e!==/./.exec},{exec:e})},sbF8:function(t,n,r){var e=r("XKFU"),o=r("nBIS"),i=Math.abs;e(e.S,"Number",{isSafeInteger:function(t){return o(t)&&i(t)<=9007199254740991}})},tUrg:function(t,n,r){"use strict";r("OGtf")("link",function(t){return function(n){return t(this,"a","href",n)}})},"tyy+":function(t,n,r){var e=r("XKFU"),o=r("11IZ");e(e.G+e.F*(parseFloat!=o),{parseFloat:o})},upKx:function(t,n,r){"use strict";var e=r("S/j/"),o=r("d/Gc"),i=r("ne8i");t.exports=[].copyWithin||function(t,n){var r=e(this),u=i(r.length),c=o(t,u),a=o(n,u),f=arguments.length>2?arguments[2]:void 0,s=Math.min((void 0===f?u:o(f,u))-a,u-c),l=1;for(a0;)a in r?r[c]=r[a]:delete r[c],c+=l,a+=l;return r}},vhPU:function(t,n){t.exports=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t}},vqGA:function(t,n,r){r("ioFf"),r("Btvt"),t.exports=r("g3g5").Symbol},vvmO:function(t,n,r){var e=r("LZWt");t.exports=function(t,n){if("number"!=typeof t&&"Number"!=e(t))throw TypeError(n);return+t}},w2a5:function(t,n,r){var e=r("aCFj"),o=r("ne8i"),i=r("d/Gc");t.exports=function(t){return function(n,r,u){var c,a=e(n),f=o(a.length),s=i(u,f);if(t&&r!=r){for(;f>s;)if((c=a[s++])!=c)return!0}else for(;f>s;s++)if((t||s in a)&&a[s]===r)return t||s||0;return!t&&-1}}},wmvG:function(t,n,r){"use strict";var e=r("hswa").f,o=r("Kuth"),i=r("3Lyj"),u=r("m0Pp"),c=r("9gX7"),a=r("SlkY"),f=r("Afnz"),s=r("1TsA"),l=r("elZq"),h=r("nh4g"),p=r("Z6vF").fastKey,v=r("s5qY"),y=h?"_s":"size",d=function(t,n){var r,e=p(n);if("F"!==e)return t._i[e];for(r=t._f;r;r=r.n)if(r.k==n)return r};t.exports={getConstructor:function(t,n,r,f){var s=t(function(t,e){c(t,s,n,"_i"),t._t=n,t._i=o(null),t._f=void 0,t._l=void 0,t[y]=0,null!=e&&a(e,r,t[f],t)});return i(s.prototype,{clear:function(){for(var t=v(this,n),r=t._i,e=t._f;e;e=e.n)e.r=!0,e.p&&(e.p=e.p.n=void 0),delete r[e.i];t._f=t._l=void 0,t[y]=0},delete:function(t){var r=v(this,n),e=d(r,t);if(e){var o=e.n,i=e.p;delete r._i[e.i],e.r=!0,i&&(i.n=o),o&&(o.p=i),r._f==e&&(r._f=o),r._l==e&&(r._l=i),r[y]--}return!!e},forEach:function(t){v(this,n);for(var r,e=u(t,arguments.length>1?arguments[1]:void 0,3);r=r?r.n:this._f;)for(e(r.v,r.k,this);r&&r.r;)r=r.p},has:function(t){return!!d(v(this,n),t)}}),h&&e(s.prototype,"size",{get:function(){return v(this,n)[y]}}),s},def:function(t,n,r){var e,o,i=d(t,n);return i?i.v=r:(t._l=i={i:o=p(n,!0),k:n,v:r,p:e=t._l,n:void 0,r:!1},t._f||(t._f=i),e&&(e.n=i),t[y]++,"F"!==o&&(t._i[o]=i)),t},getEntry:d,setStrong:function(t,n,r){f(t,n,function(t,r){this._t=v(t,n),this._k=r,this._l=void 0},function(){for(var t=this._k,n=this._l;n&&n.r;)n=n.p;return this._t&&(this._l=n=n?n.n:this._t._f)?s(0,"keys"==t?n.k:"values"==t?n.v:[n.k,n.v]):(this._t=void 0,s(1))},r?"entries":"values",!r,!0),l(n)}}},x8Yj:function(t,n,r){var e=r("XKFU"),o=r("LVwc"),i=Math.exp;e(e.S,"Math",{tanh:function(t){var n=o(t=+t),r=o(-t);return n==1/0?1:r==1/0?-1:(n-r)/(i(t)+i(-t))}})},x8ZO:function(t,n,r){var e=r("XKFU"),o=Math.abs;e(e.S,"Math",{hypot:function(t,n){for(var r,e,i=0,u=0,c=arguments.length,a=0;u0?(e=r/a)*e:r;return a===1/0?1/0:a*Math.sqrt(i)}})},xfY5:function(t,n,r){"use strict";var e=r("dyZX"),o=r("aagx"),i=r("LZWt"),u=r("Xbzi"),c=r("apmT"),a=r("eeVq"),f=r("kJMx").f,s=r("EemH").f,l=r("hswa").f,h=r("qncB").trim,p=e.Number,v=p,y=p.prototype,d="Number"==i(r("Kuth")(y)),g="trim"in String.prototype,b=function(t){var n=c(t,!1);if("string"==typeof n&&n.length>2){var r,e,o,i=(n=g?n.trim():h(n,3)).charCodeAt(0);if(43===i||45===i){if(88===(r=n.charCodeAt(2))||120===r)return NaN}else if(48===i){switch(n.charCodeAt(1)){case 66:case 98:e=2,o=49;break;case 79:case 111:e=8,o=55;break;default:return+n}for(var u,a=n.slice(2),f=0,s=a.length;fo)return NaN;return parseInt(a,e)}}return+n};if(!p(" 0o1")||!p("0b1")||p("+0x1")){p=function(t){var n=arguments.length<1?0:t,r=this;return r instanceof p&&(d?a(function(){y.valueOf.call(r)}):"Number"!=i(r))?u(new v(b(n)),r,p):b(n)};for(var m,_=r("nh4g")?f(v):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger".split(","),k=0;_.length>k;k++)o(v,m=_[k])&&!o(p,m)&&l(p,m,s(v,m));p.prototype=y,y.constructor=p,r("KroJ")(e,"Number",p)}},xpql:function(t,n,r){t.exports=!r("nh4g")&&!r("eeVq")(function(){return 7!=Object.defineProperty(r("Iw71")("div"),"a",{get:function(){return 7}}).a})},y3w9:function(t,n,r){var e=r("0/R4");t.exports=function(t){if(!e(t))throw TypeError(t+" is not an object!");return t}},yM4b:function(t,n,r){var e=r("K0xU")("toPrimitive"),o=Date.prototype;e in o||r("Mukb")(o,e,r("g4EE"))},ylqs:function(t,n){var r=0,e=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++r+e).toString(36))}},yt8O:function(t,n,r){"use strict";var e=r("nGyu"),o=r("1TsA"),i=r("hPIQ"),u=r("aCFj");t.exports=r("Afnz")(Array,"Array",function(t,n){this._t=u(t),this._i=0,this._k=n},function(){var t=this._t,n=this._k,r=this._i++;return!t||r>=t.length?(this._t=void 0,o(1)):o(0,"keys"==n?r:"values"==n?t[r]:[r,t[r]])},"values"),i.Arguments=i.Array,e("keys"),e("values"),e("entries")},z2o2:function(t,n,r){var e=r("0/R4"),o=r("Z6vF").onFreeze;r("Xtr8")("seal",function(t){return function(n){return t&&e(n)?t(o(n)):n}})},zRwo:function(t,n,r){var e=r("6FMO");t.exports=function(t,n){return new(e(t))(n)}},zhAb:function(t,n,r){var e=r("aagx"),o=r("aCFj"),i=r("w2a5")(!1),u=r("YTvA")("IE_PROTO");t.exports=function(t,n){var r,c=o(t),a=0,f=[];for(r in c)r!=u&&e(c,r)&&f.push(r);for(;n.length>a;)e(c,r=n[a++])&&(~i(f,r)||f.push(r));return f}}},[[1,0]]]); - -(window.webpackJsonp=window.webpackJsonp||[]).push([[1],{0:function(n,t,e){n.exports=e("zUnb")},crnd:function(n,t){function e(n){return Promise.resolve().then(function(){var t=new Error("Cannot find module '"+n+"'");throw t.code="MODULE_NOT_FOUND",t})}e.keys=function(){return[]},e.resolve=e,n.exports=e,e.id="crnd"},zUnb:function(n,t,e){"use strict";e.r(t);var r=function(n,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,t){n.__proto__=t}||function(n,t){for(var e in t)t.hasOwnProperty(e)&&(n[e]=t[e])})(n,t)};function o(n,t){function e(){this.constructor=n}r(n,t),n.prototype=null===t?Object.create(t):(e.prototype=t.prototype,new e)}var i=function(){return(i=Object.assign||function(n){for(var t,e=1,r=arguments.length;e=0;u--)(o=n[u])&&(l=(i<3?o(l):i>3?o(t,e,l):o(t,e))||l);return i>3&&l&&Object.defineProperty(t,e,l),l}function u(n,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(n,t)}function s(n){var t="function"==typeof Symbol&&n[Symbol.iterator],e=0;return t?t.call(n):{next:function(){return n&&e>=n.length&&(n=void 0),{value:n&&n[e++],done:!n}}}}function a(n,t){var e="function"==typeof Symbol&&n[Symbol.iterator];if(!e)return n;var r,o,i=e.call(n),l=[];try{for(;(void 0===t||t-- >0)&&!(r=i.next()).done;)l.push(r.value)}catch(u){o={error:u}}finally{try{r&&!r.done&&(e=i.return)&&e.call(i)}finally{if(o)throw o.error}}return l}function c(){for(var n=[],t=0;t0?this._next(t.shift()):0===this.active&&this.hasCompleted&&this.destination.complete()},t}(W);function rn(n){return n}function on(){return function(n){return n.lift(new ln(n))}}var ln=function(){function n(n){this.connectable=n}return n.prototype.call=function(n,t){var e=this.connectable;e._refCount++;var r=new un(n,e),o=t.subscribe(r);return r.closed||(r.connection=e.connect()),o},n}(),un=function(n){function t(t,e){var r=n.call(this,t)||this;return r.connectable=e,r}return o(t,n),t.prototype._unsubscribe=function(){var n=this.connectable;if(n){this.connectable=null;var t=n._refCount;if(t<=0)this.connection=null;else if(n._refCount=t-1,t>1)this.connection=null;else{var e=this.connection,r=n._connection;this.connection=null,!r||e&&r!==e||r.unsubscribe()}}else this.connection=null},t}(S),sn=function(n){function t(t,e){var r=n.call(this)||this;return r.source=t,r.subjectFactory=e,r._refCount=0,r._isComplete=!1,r}return o(t,n),t.prototype._subscribe=function(n){return this.getSubject().subscribe(n)},t.prototype.getSubject=function(){var n=this._subject;return n&&!n.isStopped||(this._subject=this.subjectFactory()),this._subject},t.prototype.connect=function(){var n=this._connection;return n||(this._isComplete=!1,(n=this._connection=new _).add(this.source.subscribe(new cn(this.getSubject(),this))),n.closed?(this._connection=null,n=_.EMPTY):this._connection=n),n},t.prototype.refCount=function(){return on()(this)},t}(I).prototype,an={operator:{value:null},_refCount:{value:0,writable:!0},_subject:{value:null,writable:!0},_connection:{value:null,writable:!0},_subscribe:{value:sn._subscribe},_isComplete:{value:sn._isComplete,writable:!0},getSubject:{value:sn.getSubject},connect:{value:sn.connect},refCount:{value:sn.refCount}},cn=function(n){function t(t,e){var r=n.call(this,t)||this;return r.connectable=e,r}return o(t,n),t.prototype._error=function(t){this._unsubscribe(),n.prototype._error.call(this,t)},t.prototype._complete=function(){this.connectable._isComplete=!0,this._unsubscribe(),n.prototype._complete.call(this)},t.prototype._unsubscribe=function(){var n=this.connectable;if(n){this.connectable=null;var t=n._connection;n._refCount=0,n._subject=null,n._connection=null,t&&t.unsubscribe()}},t}(V);function fn(){return new H}function hn(n){for(var t in n)if(n[t]===hn)return t;throw Error("Could not find renamed property on target object.")}var pn=hn({ngComponentDef:hn}),dn=hn({ngInjectableDef:hn}),gn=hn({ngInjectorDef:hn}),vn=hn({ngModuleDef:hn}),yn=hn({__NG_ELEMENT_ID__:hn});function mn(n){return{providedIn:n.providedIn||null,factory:n.factory,value:void 0}}function bn(n){return n.hasOwnProperty(dn)?n[dn]:null}function _n(n){return n.hasOwnProperty(gn)?n[gn]:null}var wn=function(){function n(n,t){this._desc=n,this.ngMetadataName="InjectionToken",this.ngInjectableDef=void 0!==t?mn({providedIn:t.providedIn||"root",factory:t.factory}):void 0}return n.prototype.toString=function(){return"InjectionToken "+this._desc},n}(),Cn="__parameters__";function En(n,t,e){var r=function(n){return function(){for(var t=[],e=0;e=Yn?e:e[ot]}function Nt(n){return n[St]}function Dt(n){var t=Nt(n);return t?Array.isArray(t)?t:t.lViewData:null}function Mt(n){return 32767&n}function Vt(n,t){for(var e=n>>16,r=t;e>0;)r=r[pt],e--;return r}var Ht,Rt,jt,Lt,zt,Ft,Bt,Ut,Gt=("undefined"!=typeof requestAnimationFrame&&requestAnimationFrame||setTimeout).bind(Sn);function Zt(){return Ht}function qt(){return Rt}function $t(){return jt}function Qt(n){jt=n}function Wt(n,t){jt=n,Ut=t}function Kt(){return Lt}function Jt(n){Lt=n}function Yt(){return zt}function Xt(){return Bt}function ne(){return Ut}var te=!1;function ee(){return te}function re(n){te=n}var oe=!0;function ie(n){oe=n}function le(n,t){var e=Ut;return zt=n&&n[Xn],Bt=n&&1==(1&n[nt]),oe=n&&zt.firstTemplatePass,Ht=n&&n[ct],jt=t,Lt=!0,Ut=n,e&&(e[rt]=Ft),Ft=n&&n[rt],e}function ue(n,t){t||(te||yt(Ut,zt.viewHooks,zt.viewCheckHooks,Bt),Ut[nt]&=-6),Ut[nt]|=16,Ut[lt]=zt.bindingStartIndex,le(n,null)}var se=!1;function ae(n){var t=se;return se=n,t}var ce=255,fe=0;function he(n,t){var e=de(n,t);if(-1!==e)return e;var r=t[Xn];r.firstTemplatePass&&(n.injectorIndex=t.length,pe(r.data,n),pe(t,null),pe(r.blueprint,null));var o=ge(n,t),i=Mt(o),l=Vt(o,t),u=n.injectorIndex;if(o!==Wn)for(var s=l[Xn].data,a=0;a<8;a++)t[u+a]=l[i+a]|s[i+a];return t[u+$n]=o,u}function pe(n,t){n.push(0,0,0,0,0,0,0,0,t)}function de(n,t){return-1===n.injectorIndex||n.parent&&n.parent.injectorIndex===n.injectorIndex||null==t[n.injectorIndex+$n]?-1:n.injectorIndex}function ge(n,t){if(n.parent&&-1!==n.parent.injectorIndex)return n.parent.injectorIndex;for(var e=t[it],r=1;e&&-1===e.injectorIndex;)e=(t=t[pt])[it],r++;return e?e.injectorIndex|r<<16|(e&&3===e.type?32768:0):-1}var ve={};function ye(n,t,e,r){var o=t[Xn],i=o.data[n+qn],l=i.flags,u=i.providerIndexes,s=o.data,a=!1;(null==r&&function(n){return 4096==(4096&n.flags)}(i)&&se||null!=r&&r!=o&&(null==o.node||3===o.node.type))&&(a=!0);for(var c=65535&u,f=l>>16,h=4095&l,p=a?c:c+(u>>16);p=f&&d.type===e)return me(s,t,p,i)}return ve}function me(n,t,e,r){var o,i=t[e];if(null!=(o=i)&&"object"==typeof o&&Object.getPrototypeOf(o)==Jn){var l=i;if(l.resolving)throw new Error("Circular dep for "+At(n[e]));var u=ae(l.canSeeViewProviders);l.resolving=!0;var s=void 0;l.injectImpl&&(s=Bn(l.injectImpl));var a=$t(),c=ne();Wt(r,t);try{i=t[e]=l.factory(null,n,t,r)}finally{l.injectImpl&&Bn(s),ae(u),l.resolving=!1,Wt(a,c)}}return i}function be(n,t,e){var r=64&n,o=32&n;return!!((128&n?r?o?e[t+7]:e[t+6]:o?e[t+5]:e[t+4]:r?o?e[t+3]:e[t+2]:o?e[t+1]:e[t])&1< ");else if("object"==typeof t){var o=[];for(var i in t)if(t.hasOwnProperty(i)){var l=t[i];o.push(i+":"+("string"==typeof l?JSON.stringify(l):Dn(l)))}r="{"+o.join(", ")+"}"}return"StaticInjectorError"+(e?"("+e+")":"")+"["+r+"]: "+n.replace(ze,"\n ")}function Ze(n,t){return new Error(Ge(n,t))}var qe=function(){return function(){}}(),$e=function(){return function(){}}(),Qe="ngProjectAs";function We(n){return!!n.listen}var Ke={createRenderer:function(n,t){return document}},Je=[];function Ye(n){for(var t=n[it];t&&2===t.type;)t=(n=n[tt])[it];return n}function Xe(n,t,e,r,o){0===n?We(t)?t.insertBefore(e,r,o):e.insertBefore(r,o,!0):1===n?We(t)?t.removeChild(e,r):e.removeChild(r):2===n&&t.destroyNode(r)}function nr(n){var t=n[Xn].childIndex;return-1===t?null:n[t]}function tr(n,t){var e;return n.length>=Yn&&(e=n[it])&&2===e.type?function(t,e){if(-1===t.index){var r=n[ht];return r>-1?n[tt][r]:null}return n[tt][t.parent.index]}(e):n[tt]===t?null:n[tt]}function er(n){if(n.length>=Yn){var t=n;!function(n){var t=n[Xn].cleanup;if(null!=t){for(var e=0;e=Yn?t[Xn].childIndex>-1&&(e=nr(t)):t[Ot].length&&(e=t[Ot][0]),null==e){for(;t&&!t[et]&&t!==n;)er(t),t=tr(t,n);er(t||n),e=t&&t[et]}t=e}}(n),n[nt]|=32},n.prototype.onDestroy=function(n){var t,e;e=n,function(n){return n[ut]||(n[ut]=[])}(t=this._view).push(e),t[Xn].firstTemplatePass&&function(n){return n[Xn].cleanup||(n[Xn].cleanup=[])}(t).push(t[ut].length-1,null)},n.prototype.markForCheck=function(){!function(n){for(var t=n;t&&!(64&t[nt]);)t[nt]|=4,t=t[tt];var e,r,o;t[nt]|=4,o=0===(e=t[st]).flags,e.flags|=1,o&&e.clean==or&&(e.clean=new Promise(function(n){return r=n}),e.scheduler(function(){if(1&e.flags&&(e.flags&=-2,mr(e)),2&e.flags){e.flags&=-3;var n=e.playerHandler;n&&n.flushPlayers()}e.clean=or,r(null)}))}(this._view)},n.prototype.detach=function(){this._view[nt]&=-9},n.prototype.reattach=function(){this._view[nt]|=8},n.prototype.detectChanges=function(){var n=qt();n.begin&&n.begin(),br(this.context),n.end&&n.end()},n.prototype.checkNoChanges=function(){!function(n){re(!0);try{br(n)}finally{re(!1)}}(this.context)},n.prototype.attachToViewContainerRef=function(n){this._viewContainerRef=n},n.prototype.detachFromAppRef=function(){this._appRef=null},n.prototype.attachToAppRef=function(n){this._appRef=n},n.prototype._lookUpContext=function(){return this._context=this._view[tt][this._componentIndex]},n}());function Or(n,t,e,r,o){var i=e[Xn],l=function(n,t,e){var r=$t();n.firstTemplatePass&&(e.providersResolver&&e.providersResolver(e),function(n,t,e){var o=-(r.index-Yn),i=n.data.length-(65535&r.providerIndexes);(n.expandoInstructions||(n.expandoInstructions=[])).push(o,i,1)}(n),function(n,t,e,r){n.data.push(e);var o=new Kn(r,function(n){return null!==n.template}(e),null);n.blueprint.push(o),t.push(o),function(n,t){n.expandoInstructions.push(t.hostBindings||Ee),t.hostVars&&n.expandoInstructions.push(t.hostVars)}(n,e)}(n,t,e,e.factory));var o=me(n.data,t,t.length-1,r);return function(n,t,e,r){var o=It(t,n);Ce(e,n),o&&Ce(o,n),null!=r.attributes&&3==t.type&&function(n,t){for(var e=Zt(),r=We(e),o=0;o>16,r=e+(4095&n),o=e;o',!this.inertBodyElement.querySelector||this.inertBodyElement.querySelector("svg")?(this.inertBodyElement.innerHTML='

',this.getInertBodyElement=this.inertBodyElement.querySelector&&this.inertBodyElement.querySelector("svg img")&&function(){try{return!!window.DOMParser}catch(n){return!1}}()?this.getInertBodyElement_DOMParser:this.getInertBodyElement_InertDocument):this.getInertBodyElement=this.getInertBodyElement_XHR}return n.prototype.getInertBodyElement_XHR=function(n){n=""+n+"";try{n=encodeURI(n)}catch(r){return null}var t=new XMLHttpRequest;t.responseType="document",t.open("GET","data:text/html;charset=utf-8,"+n,!1),t.send(void 0);var e=t.response.body;return e.removeChild(e.firstChild),e},n.prototype.getInertBodyElement_DOMParser=function(n){n=""+n+"";try{var t=(new window.DOMParser).parseFromString(n,"text/html").body;return t.removeChild(t.firstChild),t}catch(e){return null}},n.prototype.getInertBodyElement_InertDocument=function(n){var t=this.inertDocument.createElement("template");return"content"in t?(t.innerHTML=n,t):(this.inertBodyElement.innerHTML=n,this.defaultDoc.documentMode&&this.stripCustomNsAttrs(this.inertBodyElement),this.inertBodyElement)},n.prototype.stripCustomNsAttrs=function(n){for(var t=n.attributes,e=t.length-1;0"),!0},n.prototype.endElement=function(n){var t=n.nodeName.toLowerCase();Oo.hasOwnProperty(t)&&!wo.hasOwnProperty(t)&&(this.buf.push(""))},n.prototype.chars=function(n){this.buf.push(No(n))},n.prototype.checkClobberedElement=function(n,t){if(t&&(n.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_CONTAINED_BY)===Node.DOCUMENT_POSITION_CONTAINED_BY)throw new Error("Failed to sanitize html because the element is clobbered: "+n.outerHTML);return t},n}(),Io=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,Po=/([^\#-~ |!])/g;function No(n){return n.replace(/&/g,"&").replace(Io,function(n){return"&#"+(1024*(n.charCodeAt(0)-55296)+(n.charCodeAt(1)-56320)+65536)+";"}).replace(Po,function(n){return"&#"+n.charCodeAt(0)+";"}).replace(//g,">")}function Do(n){return"content"in n&&function(n){return n.nodeType===Node.ELEMENT_NODE&&"TEMPLATE"===n.nodeName}(n)?n.content:null}var Mo={provide:Zr,useFactory:function(){return new eo},deps:[]},Vo=function(n){function t(t,e){var r=n.call(this)||this;return r._bootstrapComponents=[],r.destroyCbs=[],r._bootstrapComponents=(t[vn]||null).bootstrap,r.injector=function(n,t,e){return void 0===t&&(t=null),void 0===e&&(e=null),t=t||Dr(),new Mr(n,e,t)}(t,e,[Mo,{provide:qe,useValue:r}]),r.instance=r.injector.get(t),r.componentFactoryResolver=new eo,r}return o(t,n),t.prototype.destroy=function(){this.destroyCbs.forEach(function(n){return n()}),this.destroyCbs=null},t.prototype.onDestroy=function(n){this.destroyCbs.push(n)},t}(qe);!function(n){function t(t){var e=n.call(this)||this;return e.moduleType=t,e}o(t,n),t.prototype.create=function(n){return new Vo(this.moduleType,n)}}($e);var Ho=function(n){function t(t){void 0===t&&(t=!1);var e=n.call(this)||this;return e.__isAsync=t,e}return o(t,n),t.prototype.emit=function(t){n.prototype.next.call(this,t)},t.prototype.subscribe=function(t,e,r){var o,i=function(n){return null},l=function(){return null};t&&"object"==typeof t?(o=this.__isAsync?function(n){setTimeout(function(){return t.next(n)})}:function(n){t.next(n)},t.error&&(i=this.__isAsync?function(n){setTimeout(function(){return t.error(n)})}:function(n){t.error(n)}),t.complete&&(l=this.__isAsync?function(){setTimeout(function(){return t.complete()})}:function(){t.complete()})):(o=this.__isAsync?function(n){setTimeout(function(){return t(n)})}:function(n){t(n)},e&&(i=this.__isAsync?function(n){setTimeout(function(){return e(n)})}:function(n){e(n)}),r&&(l=this.__isAsync?function(){setTimeout(function(){return r()})}:function(){r()}));var u=n.prototype.subscribe.call(this,o,i,l);return t instanceof _&&t.add(u),u},t}(H),Ro=function(){function n(){}return n.__NG_ELEMENT_ID__=function(){return jo(n,Qr)},n}(),jo=Ee,Lo=function(n){return n[n.NONE=0]="NONE",n[n.HTML=1]="HTML",n[n.STYLE=2]="STYLE",n[n.SCRIPT=3]="SCRIPT",n[n.URL=4]="URL",n[n.RESOURCE_URL=5]="RESOURCE_URL",n}({}),zo=function(){return function(){}}(),Fo=new RegExp("^([-,.\"'%_!# a-zA-Z0-9]+|(?:(?:matrix|translate|scale|rotate|skew|perspective)(?:X|Y|3d)?|(?:rgb|hsl)a?|(?:repeating-)?(?:linear|radial)-gradient|(?:calc|attr))\\([-0-9.%, #a-zA-Z]+\\))$","g"),Bo=/^url\(([^)]+)\)$/;Function,String,String;var Uo="ngDebugContext",Go="ngOriginalError",Zo="ngErrorLogger";function qo(n){return n[Uo]}function $o(n){return n[Go]}function Qo(n){for(var t=[],e=1;e0&&(o=setTimeout(function(){r._callbacks=r._callbacks.filter(function(n){return n.timeoutId!==o}),n(r._didWork,r.getPendingTasks())},t)),this._callbacks.push({doneCb:n,timeoutId:o,updateCb:e})},n.prototype.whenStable=function(n,t,e){if(e&&!this.taskTrackingZone)throw new Error('Task tracking zone is required when passing an update callback to whenStable(). Is "zone.js/dist/task-tracking.js" loaded?');this.addCallback(n,t,e),this._runCallbacksIfReady()},n.prototype.getPendingRequestCount=function(){return this._pendingCount},n.prototype.findProviders=function(n,t,e){return[]},n}(),ki=function(){function n(){this._applications=new Map,Si.addToWindow(this)}return n.prototype.registerApplication=function(n,t){this._applications.set(n,t)},n.prototype.unregisterApplication=function(n){this._applications.delete(n)},n.prototype.unregisterAllApplications=function(){this._applications.clear()},n.prototype.getTestability=function(n){return this._applications.get(n)||null},n.prototype.getAllTestabilities=function(){return Array.from(this._applications.values())},n.prototype.getAllRootElements=function(){return Array.from(this._applications.keys())},n.prototype.findTestabilityInTree=function(n,t){return void 0===t&&(t=!0),Si.findTestabilityInTree(this,n,t)},l([u("design:paramtypes",[])],n)}(),Si=new(function(){function n(){}return n.prototype.addToWindow=function(n){},n.prototype.findTestabilityInTree=function(n,t,e){return null},n}()),Ai=new wn("AllowMultipleToken"),Ti=function(){return function(n,t){this.name=n,this.token=t}}();function Ii(n,t,e){void 0===e&&(e=[]);var r="Platform: "+t,o=new wn(r);return function(t){void 0===t&&(t=[]);var i=Pi();if(!i||i.injector.get(Ai,!1))if(n)n(e.concat(t).concat({provide:o,useValue:!0}));else{var l=e.concat(t).concat({provide:o,useValue:!0});!function(n){if(Ei&&!Ei.destroyed&&!Ei.injector.get(Ai,!1))throw new Error("There can be only one platform. Destroy the previous one to create a new one.");Ei=n.get(Ni);var t=n.get(ri,null);t&&t.forEach(function(n){return n()})}(Ne.create({providers:l,name:r}))}return function(n){var t=Pi();if(!t)throw new Error("No platform exists!");if(!t.injector.get(n,null))throw new Error("A platform with a different configuration has been created. Please destroy it first.");return t}(o)}}function Pi(){return Ei&&!Ei.destroyed?Ei:null}var Ni=function(){function n(n){this._injector=n,this._modules=[],this._destroyListeners=[],this._destroyed=!1}return n.prototype.bootstrapModuleFactory=function(n,t){var e,r=this,o="noop"===(e=t?t.ngZone:void 0)?new xi:("zone.js"===e?void 0:e)||new yi({enableLongStackTrace:ho()}),i=[{provide:yi,useValue:o}];return o.run(function(){var t=Ne.create({providers:i,parent:r.injector,name:n.moduleType.name}),e=n.create(t),l=e.injector.get(Wo,null);if(!l)throw new Error("No ErrorHandler. Is platform module (BrowserModule) included?");return e.onDestroy(function(){return Vi(r._modules,e)}),o.runOutsideAngular(function(){return o.onError.subscribe({next:function(n){l.handleError(n)}})}),function(n,t,o){try{var i=((l=e.injector.get(Xo)).runInitializers(),l.donePromise.then(function(){return r._moduleDoBootstrap(e),e}));return Ko(i)?i.catch(function(e){throw t.runOutsideAngular(function(){return n.handleError(e)}),e}):i}catch(u){throw t.runOutsideAngular(function(){return n.handleError(u)}),u}var l}(l,o)})},n.prototype.bootstrapModule=function(n,t){var e=this;void 0===t&&(t=[]);var r=Di({},t);return function(n,t,e){return n.get(fi).createCompiler([t]).compileModuleAsync(e)}(this.injector,r,n).then(function(n){return e.bootstrapModuleFactory(n,r)})},n.prototype._moduleDoBootstrap=function(n){var t=n.injector.get(Mi);if(n._bootstrapComponents.length>0)n._bootstrapComponents.forEach(function(n){return t.bootstrap(n)});else{if(!n.instance.ngDoBootstrap)throw new Error("The module "+Dn(n.instance.constructor)+' was bootstrapped, but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. Please define one of these.');n.instance.ngDoBootstrap(t)}this._modules.push(n)},n.prototype.onDestroy=function(n){this._destroyListeners.push(n)},Object.defineProperty(n.prototype,"injector",{get:function(){return this._injector},enumerable:!0,configurable:!0}),n.prototype.destroy=function(){if(this._destroyed)throw new Error("The platform has already been destroyed!");this._modules.slice().forEach(function(n){return n.destroy()}),this._destroyListeners.forEach(function(n){return n()}),this._destroyed=!0},Object.defineProperty(n.prototype,"destroyed",{get:function(){return this._destroyed},enumerable:!0,configurable:!0}),n}();function Di(n,t){return Array.isArray(t)?t.reduce(Di,n):i({},n,t)}var Mi=function(){function n(n,t,e,r,o,i){var l=this;this._zone=n,this._console=t,this._injector=e,this._exceptionHandler=r,this._componentFactoryResolver=o,this._initStatus=i,this._bootstrapListeners=[],this._views=[],this._runningTick=!1,this._enforceNoNewChanges=!1,this._stable=!0,this.componentTypes=[],this.components=[],this._enforceNoNewChanges=ho(),this._zone.onMicrotaskEmpty.subscribe({next:function(){l._zone.run(function(){l.tick()})}});var u=new I(function(n){l._stable=l._zone.isStable&&!l._zone.hasPendingMacrotasks&&!l._zone.hasPendingMicrotasks,l._zone.runOutsideAngular(function(){n.next(l._stable),n.complete()})}),s=new I(function(n){var t;l._zone.runOutsideAngular(function(){t=l._zone.onStable.subscribe(function(){yi.assertNotInAngularZone(),Pn(function(){l._stable||l._zone.hasPendingMacrotasks||l._zone.hasPendingMicrotasks||(l._stable=!0,n.next(!0))})})});var e=l._zone.onUnstable.subscribe(function(){yi.assertInAngularZone(),l._stable&&(l._stable=!1,l._zone.runOutsideAngular(function(){n.next(!1)}))});return function(){t.unsubscribe(),e.unsubscribe()}});this.isStable=function(){for(var n=[],t=0;t1&&"number"==typeof n[n.length-1]&&(r=n.pop())):"number"==typeof i&&(r=n.pop()),null===o&&1===n.length&&n[0]instanceof I?n[0]:function(n){return void 0===n&&(n=Number.POSITIVE_INFINITY),function n(t,e,r){return void 0===r&&(r=Number.POSITIVE_INFINITY),"function"==typeof e?function(o){return o.pipe(n(function(n,r){return nn(t(n,r)).pipe(K(function(t,o){return e(n,t,r,o)}))},r))}:("number"==typeof e&&(r=e),function(n){return n.lift(new tn(t,r))})}(rn,n)}(r)(X(n,o))}(u,s.pipe(function(n){return on()((t=fn,function(n){var e;e="function"==typeof t?t:function(){return t};var r=Object.create(n,an);return r.source=n,r.subjectFactory=e,r})(n));var t}))}var t;return t=n,n.prototype.bootstrap=function(n,t){var e,r=this;if(!this._initStatus.done)throw new Error("Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.");e=n instanceof Fr?n:this._componentFactoryResolver.resolveComponentFactory(n),this.componentTypes.push(e.componentType);var o=e instanceof $r?null:this._injector.get(qe),i=e.create(Ne.NULL,[],t||e.selector,o);i.onDestroy(function(){r._unloadComponent(i)});var l=i.injector.get(Oi,null);return l&&i.injector.get(ki).registerApplication(i.location.nativeElement,l),this._loadComponent(i),ho()&&this._console.log("Angular is running in the development mode. Call enableProdMode() to enable the production mode."),i},n.prototype.tick=function(){var n=this;if(this._runningTick)throw new Error("ApplicationRef.tick is called recursively");var e=t._tickScope();try{this._runningTick=!0,this._views.forEach(function(n){return n.detectChanges()}),this._enforceNoNewChanges&&this._views.forEach(function(n){return n.checkNoChanges()})}catch(r){this._zone.runOutsideAngular(function(){return n._exceptionHandler.handleError(r)})}finally{this._runningTick=!1,vi(e)}},n.prototype.attachView=function(n){var t=n;this._views.push(t),t.attachToAppRef(this)},n.prototype.detachView=function(n){var t=n;Vi(this._views,t),t.detachFromAppRef()},n.prototype._loadComponent=function(n){this.attachView(n.hostView),this.tick(),this.components.push(n),this._injector.get(ii,[]).concat(this._bootstrapListeners).forEach(function(t){return t(n)})},n.prototype._unloadComponent=function(n){this.detachView(n.hostView),Vi(this.components,n)},n.prototype.ngOnDestroy=function(){this._views.slice().forEach(function(n){return n.destroy()})},Object.defineProperty(n.prototype,"viewCount",{get:function(){return this._views.length},enumerable:!0,configurable:!0}),n._tickScope=gi("ApplicationRef#tick()"),n}();function Vi(n,t){var e=n.indexOf(t);e>-1&&n.splice(e,1)}var Hi,Ri=function(){function n(){this.dirty=!0,this._results=[],this.changes=new Ho,this.length=0}return n.prototype.map=function(n){return this._results.map(n)},n.prototype.filter=function(n){return this._results.filter(n)},n.prototype.find=function(n){return this._results.find(n)},n.prototype.reduce=function(n,t){return this._results.reduce(n,t)},n.prototype.forEach=function(n){this._results.forEach(n)},n.prototype.some=function(n){return this._results.some(n)},n.prototype.toArray=function(){return this._results.slice()},n.prototype[In()]=function(){return this._results[In()]()},n.prototype.toString=function(){return this._results.toString()},n.prototype.reset=function(n){this._results=function n(t){return t.reduce(function(t,e){var r=Array.isArray(e)?n(e):e;return t.concat(r)},[])}(n),this.dirty=!1,this.length=this._results.length,this.last=this._results[this.length-1],this.first=this._results[0]},n.prototype.notifyOnChanges=function(){this.changes.emit(this)},n.prototype.setDirty=function(){this.dirty=!0},n.prototype.destroy=function(){this.changes.complete(),this.changes.unsubscribe()},n}(),ji=function(){function n(){}return n.__NG_ELEMENT_ID__=function(){return Li(n,Qr)},n}(),Li=Ee,zi=function(){function n(){}return n.__NG_ELEMENT_ID__=function(){return Fi()},n}(),Fi=function(){for(var n=[],t=0;t-1}(r)||"root"===o.providedIn&&r._def.isRoot))){var c=n._providers.length;return n._def.providersByKey[t.tokenKey]={flags:5120,value:u.factory,deps:[],index:c,token:t.token},n._providers[c]=fu,n._providers[c]=yu(n,n._def.providersByKey[t.tokenKey])}return 4&t.flags?e:n._parent.get(t.token,e)}finally{Fn(i)}}function yu(n,t){var e;switch(201347067&t.flags){case 512:e=function(n,t,e){var r=e.length;switch(r){case 0:return new t;case 1:return new t(vu(n,e[0]));case 2:return new t(vu(n,e[0]),vu(n,e[1]));case 3:return new t(vu(n,e[0]),vu(n,e[1]),vu(n,e[2]));default:for(var o=new Array(r),i=0;i=e.length)&&(t=e.length-1),t<0)return null;var r=e[t];return r.viewContainerParent=null,Cu(e,t),Cl.dirtyParentQueries(r),_u(r),r}function bu(n,t,e){var r=t?Fl(t,t.def.lastRenderRootNode):n.renderElement,o=e.renderer.parentNode(r),i=e.renderer.nextSibling(r);Wl(e,2,o,i,void 0)}function _u(n){Wl(n,3,null,null,void 0)}function wu(n,t,e){t>=n.length?n.push(e):n.splice(t,0,e)}function Cu(n,t){t>=n.length-1?n.pop():n.splice(t,1)}var Eu=new Object;function xu(n,t,e,r,o,i){return new Ou(n,t,e,r,o,i)}var Ou=function(n){function t(t,e,r,o,i,l){var u=n.call(this)||this;return u.selector=t,u.componentType=e,u._inputs=o,u._outputs=i,u.ngContentSelectors=l,u.viewDefFactory=r,u}return o(t,n),Object.defineProperty(t.prototype,"inputs",{get:function(){var n=[],t=this._inputs;for(var e in t)n.push({propName:e,templateName:t[e]});return n},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"outputs",{get:function(){var n=[];for(var t in this._outputs)n.push({propName:t,templateName:this._outputs[t]});return n},enumerable:!0,configurable:!0}),t.prototype.create=function(n,t,e,r){if(!r)throw new Error("ngModule should be provided");var o=Ql(this.viewDefFactory),i=o.nodes[0].element.componentProvider.nodeIndex,l=Cl.createRootView(n,t||[],e,o,r,Eu),u=bl(l,i).instance;return e&&l.renderer.setAttribute(ml(l,0).renderElement,"ng-version",to.full),new ku(l,new Iu(l),u)},t}(Fr),ku=function(n){function t(t,e,r){var o=n.call(this)||this;return o._view=t,o._viewRef=e,o._component=r,o._elDef=o._view.def.nodes[0],o.hostView=e,o.changeDetectorRef=e,o.instance=r,o}return o(t,n),Object.defineProperty(t.prototype,"location",{get:function(){return new Qr(ml(this._view,this._elDef.nodeIndex).renderElement)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"injector",{get:function(){return new Mu(this._view,this._elDef)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"componentType",{get:function(){return this._component.constructor},enumerable:!0,configurable:!0}),t.prototype.destroy=function(){this._viewRef.destroy()},t.prototype.onDestroy=function(n){this._viewRef.onDestroy(n)},t}(zr);function Su(n,t,e){return new Au(n,t,e)}var Au=function(){function n(n,t,e){this._view=n,this._elDef=t,this._data=e,this._embeddedViews=[]}return Object.defineProperty(n.prototype,"element",{get:function(){return new Qr(this._data.renderElement)},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"injector",{get:function(){return new Mu(this._view,this._elDef)},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"parentInjector",{get:function(){for(var n=this._view,t=this._elDef.parent;!t&&n;)t=zl(n),n=n.parent;return n?new Mu(n,t):new Mu(this._view,null)},enumerable:!0,configurable:!0}),n.prototype.clear=function(){for(var n=this._embeddedViews.length-1;n>=0;n--){var t=mu(this._data,n);Cl.destroyView(t)}},n.prototype.get=function(n){var t=this._embeddedViews[n];if(t){var e=new Iu(t);return e.attachToViewContainerRef(this),e}return null},Object.defineProperty(n.prototype,"length",{get:function(){return this._embeddedViews.length},enumerable:!0,configurable:!0}),n.prototype.createEmbeddedView=function(n,t,e){var r=n.createEmbeddedView(t||{});return this.insert(r,e),r},n.prototype.createComponent=function(n,t,e,r,o){var i=e||this.parentInjector;o||n instanceof $r||(o=i.get(qe));var l=n.create(i,r,void 0,o);return this.insert(l.hostView,t),l},n.prototype.insert=function(n,t){if(n.destroyed)throw new Error("Cannot insert a destroyed View in a ViewContainer!");var e,r,o,i,l=n;return i=(e=this._data).viewContainer._embeddedViews,null==(r=t)&&(r=i.length),(o=l._view).viewContainerParent=this._view,wu(i,r,o),function(n,t){var e=Ll(t);if(e&&e!==n&&!(16&t.state)){t.state|=16;var r=e.template._projectedViews;r||(r=e.template._projectedViews=[]),r.push(t),function(n,e){if(!(4&e.flags)){t.parent.def.nodeFlags|=4,e.flags|=4;for(var r=e.parent;r;)r.childFlags|=4,r=r.parent}}(0,t.parentNodeDef)}}(e,o),Cl.dirtyParentQueries(o),bu(e,r>0?i[r-1]:null,o),l.attachToViewContainerRef(this),n},n.prototype.move=function(n,t){if(n.destroyed)throw new Error("Cannot move a destroyed View in a ViewContainer!");var e,r,o,i,l,u=this._embeddedViews.indexOf(n._view);return o=t,l=(i=(e=this._data).viewContainer._embeddedViews)[r=u],Cu(i,r),null==o&&(o=i.length),wu(i,o,l),Cl.dirtyParentQueries(l),_u(l),bu(e,o>0?i[o-1]:null,l),n},n.prototype.indexOf=function(n){return this._embeddedViews.indexOf(n._view)},n.prototype.remove=function(n){var t=mu(this._data,n);t&&Cl.destroyView(t)},n.prototype.detach=function(n){var t=mu(this._data,n);return t?new Iu(t):null},n}();function Tu(n){return new Iu(n)}var Iu=function(){function n(n){this._view=n,this._viewContainerRef=null,this._appRef=null}return Object.defineProperty(n.prototype,"rootNodes",{get:function(){return Wl(this._view,0,void 0,void 0,n=[]),n;var n},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"context",{get:function(){return this._view.context},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"destroyed",{get:function(){return 0!=(128&this._view.state)},enumerable:!0,configurable:!0}),n.prototype.markForCheck=function(){Hl(this._view)},n.prototype.detach=function(){this._view.state&=-5},n.prototype.detectChanges=function(){var n=this._view.root.rendererFactory;n.begin&&n.begin();try{Cl.checkAndUpdateView(this._view)}finally{n.end&&n.end()}},n.prototype.checkNoChanges=function(){Cl.checkNoChangesView(this._view)},n.prototype.reattach=function(){this._view.state|=4},n.prototype.onDestroy=function(n){this._view.disposables||(this._view.disposables=[]),this._view.disposables.push(n)},n.prototype.destroy=function(){this._appRef?this._appRef.detachView(this):this._viewContainerRef&&this._viewContainerRef.detach(this._viewContainerRef.indexOf(this)),Cl.destroyView(this._view)},n.prototype.detachFromAppRef=function(){this._appRef=null,_u(this._view),Cl.dirtyParentQueries(this._view)},n.prototype.attachToAppRef=function(n){if(this._viewContainerRef)throw new Error("This view is already attached to a ViewContainer!");this._appRef=n},n.prototype.attachToViewContainerRef=function(n){if(this._appRef)throw new Error("This view is already attached directly to the ApplicationRef!");this._viewContainerRef=n},n}();function Pu(n,t){return new Nu(n,t)}var Nu=function(n){function t(t,e){var r=n.call(this)||this;return r._parentView=t,r._def=e,r}return o(t,n),t.prototype.createEmbeddedView=function(n){return new Iu(Cl.createEmbeddedView(this._parentView,this._def,this._def.element.template,n))},Object.defineProperty(t.prototype,"elementRef",{get:function(){return new Qr(ml(this._parentView,this._def.nodeIndex).renderElement)},enumerable:!0,configurable:!0}),t}(Ro);function Du(n,t){return new Mu(n,t)}var Mu=function(){function n(n,t){this.view=n,this.elDef=t}return n.prototype.get=function(n,t){return void 0===t&&(t=Ne.THROW_IF_NOT_FOUND),Cl.resolveDep(this.view,this.elDef,!!this.elDef&&0!=(33554432&this.elDef.flags),{flags:0,token:n,tokenKey:Al(n)},t)},n}();function Vu(n,t){var e=n.def.nodes[t];if(1&e.flags){var r=ml(n,e.nodeIndex);return e.element.template?r.template:r.renderElement}if(2&e.flags)return yl(n,e.nodeIndex).renderText;if(20240&e.flags)return bl(n,e.nodeIndex).instance;throw new Error("Illegal state: read nodeValue for node index "+t)}function Hu(n){return new Ru(n.renderer)}var Ru=function(){function n(n){this.delegate=n}return n.prototype.selectRootElement=function(n){return this.delegate.selectRootElement(n)},n.prototype.createElement=function(n,t){var e=a(tu(t),2),r=this.delegate.createElement(e[1],e[0]);return n&&this.delegate.appendChild(n,r),r},n.prototype.createViewRoot=function(n){return n},n.prototype.createTemplateAnchor=function(n){var t=this.delegate.createComment("");return n&&this.delegate.appendChild(n,t),t},n.prototype.createText=function(n,t){var e=this.delegate.createText(t);return n&&this.delegate.appendChild(n,e),e},n.prototype.projectNodes=function(n,t){for(var e=0;e0,t.provider.value,t.provider.deps);if(t.outputs.length)for(var r=0;r0,r=t.provider;switch(201347067&t.flags){case 512:return es(n,t.parent,e,r.value,r.deps);case 1024:return function(n,t,e,r,o){var i=o.length;switch(i){case 0:return r();case 1:return r(os(n,t,e,o[0]));case 2:return r(os(n,t,e,o[0]),os(n,t,e,o[1]));case 3:return r(os(n,t,e,o[0]),os(n,t,e,o[1]),os(n,t,e,o[2]));default:for(var l=Array(i),u=0;u0)a=g,_s(g)||(c=g);else for(;a&&d===a.nodeIndex+a.childCount;){var m=a.parent;m&&(m.childFlags|=a.childFlags,m.childMatchedQueries|=a.childMatchedQueries),c=(a=m)&&_s(a)?a.renderParent:a}}return{factory:null,nodeFlags:l,rootNodeFlags:u,nodeMatchedQueries:s,flags:n,nodes:t,updateDirectives:e||kl,updateRenderer:r||kl,handleEvent:function(n,e,r,o){return t[e].element.handleEvent(n,r,o)},bindingCount:o,outputCount:i,lastRenderRootNode:p}}function _s(n){return 0!=(1&n.flags)&&null===n.element.name}function ws(n,t,e){var r=t.element&&t.element.template;if(r){if(!r.lastRenderRootNode)throw new Error("Illegal State: Embedded templates without nodes are not allowed!");if(r.lastRenderRootNode&&16777216&r.lastRenderRootNode.flags)throw new Error("Illegal State: Last root node of a template can't have embedded views, at index "+t.nodeIndex+"!")}if(20224&t.flags&&0==(1&(n?n.flags:0)))throw new Error("Illegal State: StaticProvider/Directive nodes need to be children of elements or anchors, at index "+t.nodeIndex+"!");if(t.query){if(67108864&t.flags&&(!n||0==(16384&n.flags)))throw new Error("Illegal State: Content Query nodes need to be children of directives, at index "+t.nodeIndex+"!");if(134217728&t.flags&&n)throw new Error("Illegal State: View Query nodes have to be top level nodes, at index "+t.nodeIndex+"!")}if(t.childCount){var o=n?n.nodeIndex+n.childCount:e-1;if(t.nodeIndex<=o&&t.nodeIndex+t.childCount>o)throw new Error("Illegal State: childCount of node leads outside of parent, at index "+t.nodeIndex+"!")}}function Cs(n,t,e,r){var o=Os(n.root,n.renderer,n,t,e);return ks(o,n.component,r),Ss(o),o}function Es(n,t,e){var r=Os(n,n.renderer,null,null,t);return ks(r,e,e),Ss(r),r}function xs(n,t,e,r){var o,i=t.element.componentRendererType;return o=i?n.root.rendererFactory.createRenderer(r,i):n.root.renderer,Os(n.root,o,n,t.element.componentProvider,e)}function Os(n,t,e,r,o){var i=new Array(o.nodes.length),l=o.outputCount?new Array(o.outputCount):null;return{def:o,parent:e,viewContainerParent:null,parentNodeDef:r,context:null,component:null,nodes:i,state:13,root:n,renderer:t,oldValues:new Array(o.bindingCount),disposables:l,initIndex:-1}}function ks(n,t,e){n.component=t,n.context=e}function Ss(n){var t;Bl(n)&&(t=ml(n.parent,n.parentNodeDef.parent.nodeIndex).renderElement);for(var e=n.def,r=n.nodes,o=0;o0&&cu(n,t,0,e)&&(p=!0),h>1&&cu(n,t,1,r)&&(p=!0),h>2&&cu(n,t,2,o)&&(p=!0),h>3&&cu(n,t,3,i)&&(p=!0),h>4&&cu(n,t,4,l)&&(p=!0),h>5&&cu(n,t,5,u)&&(p=!0),h>6&&cu(n,t,6,s)&&(p=!0),h>7&&cu(n,t,7,a)&&(p=!0),h>8&&cu(n,t,8,c)&&(p=!0),h>9&&cu(n,t,9,f)&&(p=!0),p}(n,t,e,r,o,i,l,u,s,a,c,f);case 2:return function(n,t,e,r,o,i,l,u,s,a,c,f){var h=!1,p=t.bindings,d=p.length;if(d>0&&Ml(n,t,0,e)&&(h=!0),d>1&&Ml(n,t,1,r)&&(h=!0),d>2&&Ml(n,t,2,o)&&(h=!0),d>3&&Ml(n,t,3,i)&&(h=!0),d>4&&Ml(n,t,4,l)&&(h=!0),d>5&&Ml(n,t,5,u)&&(h=!0),d>6&&Ml(n,t,6,s)&&(h=!0),d>7&&Ml(n,t,7,a)&&(h=!0),d>8&&Ml(n,t,8,c)&&(h=!0),d>9&&Ml(n,t,9,f)&&(h=!0),h){var g=t.text.prefix;d>0&&(g+=ms(e,p[0])),d>1&&(g+=ms(r,p[1])),d>2&&(g+=ms(o,p[2])),d>3&&(g+=ms(i,p[3])),d>4&&(g+=ms(l,p[4])),d>5&&(g+=ms(u,p[5])),d>6&&(g+=ms(s,p[6])),d>7&&(g+=ms(a,p[7])),d>8&&(g+=ms(c,p[8])),d>9&&(g+=ms(f,p[9]));var v=yl(n,t.nodeIndex).renderText;n.renderer.setValue(v,g)}return h}(n,t,e,r,o,i,l,u,s,a,c,f);case 16384:return function(n,t,e,r,o,i,l,u,s,a,c,f){var h=bl(n,t.nodeIndex),p=h.instance,d=!1,g=void 0,v=t.bindings.length;return v>0&&Dl(n,t,0,e)&&(d=!0,g=ls(n,h,t,0,e,g)),v>1&&Dl(n,t,1,r)&&(d=!0,g=ls(n,h,t,1,r,g)),v>2&&Dl(n,t,2,o)&&(d=!0,g=ls(n,h,t,2,o,g)),v>3&&Dl(n,t,3,i)&&(d=!0,g=ls(n,h,t,3,i,g)),v>4&&Dl(n,t,4,l)&&(d=!0,g=ls(n,h,t,4,l,g)),v>5&&Dl(n,t,5,u)&&(d=!0,g=ls(n,h,t,5,u,g)),v>6&&Dl(n,t,6,s)&&(d=!0,g=ls(n,h,t,6,s,g)),v>7&&Dl(n,t,7,a)&&(d=!0,g=ls(n,h,t,7,a,g)),v>8&&Dl(n,t,8,c)&&(d=!0,g=ls(n,h,t,8,c,g)),v>9&&Dl(n,t,9,f)&&(d=!0,g=ls(n,h,t,9,f,g)),g&&p.ngOnChanges(g),65536&t.flags&&vl(n,256,t.nodeIndex)&&p.ngOnInit(),262144&t.flags&&p.ngDoCheck(),d}(n,t,e,r,o,i,l,u,s,a,c,f);case 32:case 64:case 128:return function(n,t,e,r,o,i,l,u,s,a,c,f){var h=t.bindings,p=!1,d=h.length;if(d>0&&Ml(n,t,0,e)&&(p=!0),d>1&&Ml(n,t,1,r)&&(p=!0),d>2&&Ml(n,t,2,o)&&(p=!0),d>3&&Ml(n,t,3,i)&&(p=!0),d>4&&Ml(n,t,4,l)&&(p=!0),d>5&&Ml(n,t,5,u)&&(p=!0),d>6&&Ml(n,t,6,s)&&(p=!0),d>7&&Ml(n,t,7,a)&&(p=!0),d>8&&Ml(n,t,8,c)&&(p=!0),d>9&&Ml(n,t,9,f)&&(p=!0),p){var g=_l(n,t.nodeIndex),v=void 0;switch(201347067&t.flags){case 32:v=new Array(h.length),d>0&&(v[0]=e),d>1&&(v[1]=r),d>2&&(v[2]=o),d>3&&(v[3]=i),d>4&&(v[4]=l),d>5&&(v[5]=u),d>6&&(v[6]=s),d>7&&(v[7]=a),d>8&&(v[8]=c),d>9&&(v[9]=f);break;case 64:v={},d>0&&(v[h[0].name]=e),d>1&&(v[h[1].name]=r),d>2&&(v[h[2].name]=o),d>3&&(v[h[3].name]=i),d>4&&(v[h[4].name]=l),d>5&&(v[h[5].name]=u),d>6&&(v[h[6].name]=s),d>7&&(v[h[7].name]=a),d>8&&(v[h[8].name]=c),d>9&&(v[h[9].name]=f);break;case 128:var y=e;switch(d){case 1:v=y.transform(e);break;case 2:v=y.transform(r);break;case 3:v=y.transform(r,o);break;case 4:v=y.transform(r,o,i);break;case 5:v=y.transform(r,o,i,l);break;case 6:v=y.transform(r,o,i,l,u);break;case 7:v=y.transform(r,o,i,l,u,s);break;case 8:v=y.transform(r,o,i,l,u,s,a);break;case 9:v=y.transform(r,o,i,l,u,s,a,c);break;case 10:v=y.transform(r,o,i,l,u,s,a,c,f)}}g.value=v}return p}(n,t,e,r,o,i,l,u,s,a,c,f);default:throw"unreachable"}}(n,t,r,o,i,l,u,s,a,f,h,p):function(n,t,e){switch(201347067&t.flags){case 1:return function(n,t,e){for(var r=!1,o=0;o0&&Vl(n,t,0,e),h>1&&Vl(n,t,1,r),h>2&&Vl(n,t,2,o),h>3&&Vl(n,t,3,i),h>4&&Vl(n,t,4,l),h>5&&Vl(n,t,5,u),h>6&&Vl(n,t,6,s),h>7&&Vl(n,t,7,a),h>8&&Vl(n,t,8,c),h>9&&Vl(n,t,9,f)}(n,t,r,o,i,l,u,s,a,c,f,h):function(n,t,e){for(var r=0;r0){var i=new Set(n.modules);Ws.forEach(function(t,r){if(i.has(bn(r).providedIn)){var o={token:r,flags:t.flags|(e?4096:0),deps:Zl(t.deps),value:t.value,index:n.providers.length};n.providers.push(o),n.providersByKey[Al(r)]=o}})}}(n=n.factory(function(){return kl})),n):n}(r))}var Qs=new Map,Ws=new Map,Ks=new Map;function Js(n){var t;Qs.set(n.token,n),"function"==typeof n.token&&(t=bn(n.token))&&"function"==typeof t.providedIn&&Ws.set(n.token,n)}function Ys(n,t){var e=Ql(t.viewDefFactory),r=Ql(e.nodes[0].element.componentView);Ks.set(n,r)}function Xs(){Qs.clear(),Ws.clear(),Ks.clear()}function na(n){if(0===Qs.size)return n;var t=function(n){for(var t=[],e=null,r=0;r=this.currentHistoricCoverage.lcq)return!1}else if("branchCoverageIncreaseOnly"===t){var r=this.branchCoverage;if(isNaN(r)||r<=this.currentHistoricCoverage.bcq)return!1}else if("branchCoverageDecreaseOnly"===t&&(r=this.branchCoverage,isNaN(r)||r>=this.currentHistoricCoverage.bcq))return!1;return!0},t.prototype.updateCurrentHistoricCoverage=function(n){if(this.currentHistoricCoverage=null,""!==n)for(var t=0;t-1&&null===e,r}return o(t,n),t.prototype.visible=function(n,t){if(""!==n&&this.name.toLowerCase().indexOf(n.toLowerCase())>-1)return!0;for(var e=0;et&&(r[o].collapsed=n.settings.collapseStates[t]),t++,e(r[o].subElements)};e(this.codeElements)},n}(),La=new I(function(n){return n.complete()}),za=function(n){function t(t,e){var r=n.call(this,t)||this;r.sources=e,r.completed=0,r.haveValues=0;var o=e.length;r.values=new Array(o);for(var i=0;i0},t.prototype.tagName=function(n){return n.tagName},t.prototype.attributeMap=function(n){for(var t=new Map,e=n.attributes,r=0;r0;l||(l=n[i]=[]);var s=$c(t)?Zone.root:Zone.current;if(0===l.length)l.push({zone:s,handler:o});else{for(var a=!1,c=0;c-1},t}(kc),tf=["alt","control","meta","shift"],ef={alt:function(n){return n.altKey},control:function(n){return n.ctrlKey},meta:function(n){return n.metaKey},shift:function(n){return n.shiftKey}},rf=function(n){function t(t){return n.call(this,t)||this}var e;return o(t,n),e=t,t.prototype.supports=function(n){return null!=e.parseEventName(n)},t.prototype.addEventListener=function(n,t,r){var o=e.parseEventName(t),i=e.eventCallback(o.fullKey,r,this.manager.getZone());return this.manager.getZone().runOutsideAngular(function(){return uc().onAndCancel(n,o.domEventName,i)})},t.parseEventName=function(n){var t=n.toLowerCase().split("."),r=t.shift();if(0===t.length||"keydown"!==r&&"keyup"!==r)return null;var o=e._normalizeKey(t.pop()),i="";if(tf.forEach(function(n){var e=t.indexOf(n);e>-1&&(t.splice(e,1),i+=n+".")}),i+=o,0!=t.length||0===o.length)return null;var l={};return l.domEventName=r,l.fullKey=i,l},t.getEventFullKey=function(n){var t="",e=uc().getEventKey(n);return" "===(e=e.toLowerCase())?e="space":"."===e&&(e="dot"),tf.forEach(function(r){r!=e&&(0,ef[r])(n)&&(t+=r+".")}),t+=e},t.eventCallback=function(n,t,r){return function(o){e.getEventFullKey(o)===n&&r.runGuarded(function(){return t(o)})}},t._normalizeKey=function(n){switch(n){case"esc":return"escape";default:return n}},t}(kc),of=function(){return function(){}}(),lf=function(n){function t(t){var e=n.call(this)||this;return e._doc=t,e}return o(t,n),t.prototype.sanitize=function(n,t){if(null==t)return null;switch(n){case Lo.NONE:return t;case Lo.HTML:return t instanceof sf?t.changingThisBreaksApplicationSecurity:(this.checkNotSafeValue(t,"HTML"),function(n,t){var e=null;try{_o=_o||new po(n);var r=t?String(t):"";e=_o.getInertBodyElement(r);var o=5,i=r;do{if(0===o)throw new Error("Failed to sanitize html because the input is unstable");o--,r=i,i=e.innerHTML,e=_o.getInertBodyElement(r)}while(r!==i);var l=new To,u=l.sanitizeChildren(Do(e)||e);return ho()&&l.sanitizedSomething&&console.warn("WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss)."),u}finally{if(e)for(var s=Do(e)||e;s.firstChild;)s.removeChild(s.firstChild)}}(this._doc,String(t)));case Lo.STYLE:return t instanceof af?t.changingThisBreaksApplicationSecurity:(this.checkNotSafeValue(t,"Style"),function(n){if(!(n=String(n).trim()))return"";var t=n.match(Bo);return t&&yo(t[1])===t[1]||n.match(Fo)&&function(n){for(var t=!0,e=!0,r=0;rn?{max:{max:n,actual:t.value}}:null}},n.required=function(n){return mf(n.value)?{required:!0}:null},n.requiredTrue=function(n){return!0===n.value?null:{required:!0}},n.email=function(n){return mf(n.value)?null:bf.test(n.value)?null:{email:!0}},n.minLength=function(n){return function(t){if(mf(t.value))return null;var e=t.value?t.value.length:0;return en?{maxlength:{requiredLength:n,actualLength:e}}:null}},n.pattern=function(t){return t?("string"==typeof t?(r="","^"!==t.charAt(0)&&(r+="^"),r+=t,"$"!==t.charAt(t.length-1)&&(r+="$"),e=new RegExp(r)):(r=t.toString(),e=t),function(n){if(mf(n.value))return null;var t=n.value;return e.test(t)?null:{pattern:{requiredPattern:r,actualValue:t}}}):n.nullValidator;var e,r},n.nullValidator=function(n){return null},n.compose=function(n){if(!n)return null;var t=n.filter(wf);return 0==t.length?null:function(n){return Ef(function(n,e){return t.map(function(t){return t(n)})}(n))}},n.composeAsync=function(n){if(!n)return null;var t=n.filter(wf);return 0==t.length?null:function(n){return function n(){for(var t,e=[],r=0;r=0;--t)if(this._accessors[t][1]===n)return void this._accessors.splice(t,1)},n.prototype.select=function(n){var t=this;this._accessors.forEach(function(e){t._isSameGroup(e,n)&&e[1]!==n&&e[1].fireUncheck(n.value)})},n.prototype._isSameGroup=function(n,t){return!!n[0].control&&n[0]._parent===t._control._parent&&n[1].name===t.name},n}(),Mf=function(){function n(n,t,e,r){this._renderer=n,this._elementRef=t,this._registry=e,this._injector=r,this.onChange=function(){},this.onTouched=function(){}}return n.prototype.ngOnInit=function(){this._control=this._injector.get(Nf),this._checkName(),this._registry.add(this._control,this)},n.prototype.ngOnDestroy=function(){this._registry.remove(this)},n.prototype.writeValue=function(n){this._state=n===this.value,this._renderer.setProperty(this._elementRef.nativeElement,"checked",this._state)},n.prototype.registerOnChange=function(n){var t=this;this._fn=n,this.onChange=function(){n(t.value),t._registry.select(t)}},n.prototype.fireUncheck=function(n){this.writeValue(n)},n.prototype.registerOnTouched=function(n){this.onTouched=n},n.prototype.setDisabledState=function(n){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",n)},n.prototype._checkName=function(){this.name&&this.formControlName&&this.name!==this.formControlName&&this._throwNameError(),!this.name&&this.formControlName&&(this.name=this.formControlName)},n.prototype._throwNameError=function(){throw new Error('\n If you define both a name and a formControlName attribute on your radio button, their values\n must match. Ex: \n ')},n}(),Vf=function(){function n(n,t){this._renderer=n,this._elementRef=t,this.onChange=function(n){},this.onTouched=function(){}}return n.prototype.writeValue=function(n){this._renderer.setProperty(this._elementRef.nativeElement,"value",parseFloat(n))},n.prototype.registerOnChange=function(n){this.onChange=function(t){n(""==t?null:parseFloat(t))}},n.prototype.registerOnTouched=function(n){this.onTouched=n},n.prototype.setDisabledState=function(n){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",n)},n}(),Hf='\n

\n
\n \n
\n
\n\n In your class:\n\n this.myGroup = new FormGroup({\n person: new FormGroup({ firstName: new FormControl() })\n });',Rf='\n
\n
\n \n
\n
';function jf(n,t){return null==n?""+t:(t&&"object"==typeof t&&(t="Object"),(n+": "+t).slice(0,50))}var Lf=function(){function n(n,t){this._renderer=n,this._elementRef=t,this._optionMap=new Map,this._idCounter=0,this.onChange=function(n){},this.onTouched=function(){},this._compareWith=Nn}return Object.defineProperty(n.prototype,"compareWith",{set:function(n){if("function"!=typeof n)throw new Error("compareWith must be a function, but received "+JSON.stringify(n));this._compareWith=n},enumerable:!0,configurable:!0}),n.prototype.writeValue=function(n){this.value=n;var t=this._getOptionId(n);null==t&&this._renderer.setProperty(this._elementRef.nativeElement,"selectedIndex",-1);var e=jf(t,n);this._renderer.setProperty(this._elementRef.nativeElement,"value",e)},n.prototype.registerOnChange=function(n){var t=this;this.onChange=function(e){t.value=t._getOptionValue(e),n(t.value)}},n.prototype.registerOnTouched=function(n){this.onTouched=n},n.prototype.setDisabledState=function(n){this._renderer.setProperty(this._elementRef.nativeElement,"disabled",n)},n.prototype._registerOption=function(){return(this._idCounter++).toString()},n.prototype._getOptionId=function(n){var t,e;try{for(var r=s(Array.from(this._optionMap.keys())),o=r.next();!o.done;o=r.next()){var i=o.value;if(this._compareWith(this._optionMap.get(i),n))return i}}catch(l){t={error:l}}finally{try{o&&!o.done&&(e=r.return)&&e.call(r)}finally{if(t)throw t.error}}return null},n.prototype._getOptionValue=function(n){var t=function(n){return n.split(":")[0]}(n);return this._optionMap.has(t)?this._optionMap.get(t):n},n}(),zf=function(){function n(n,t,e){this._element=n,this._renderer=t,this._select=e,this._select&&(this.id=this._select._registerOption())}return Object.defineProperty(n.prototype,"ngValue",{set:function(n){null!=this._select&&(this._select._optionMap.set(this.id,n),this._setElementValue(jf(this.id,n)),this._select.writeValue(this._select.value))},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"value",{set:function(n){this._setElementValue(n),this._select&&this._select.writeValue(this._select.value)},enumerable:!0,configurable:!0}),n.prototype._setElementValue=function(n){this._renderer.setProperty(this._element.nativeElement,"value",n)},n.prototype.ngOnDestroy=function(){this._select&&(this._select._optionMap.delete(this.id),this._select.writeValue(this._select.value))},n}();function Ff(n,t){return null==n?""+t:("string"==typeof t&&(t="'"+t+"'"),t&&"object"==typeof t&&(t="Object"),(n+": "+t).slice(0,50))}var Bf=function(){function n(n,t){this._renderer=n,this._elementRef=t,this._optionMap=new Map,this._idCounter=0,this.onChange=function(n){},this.onTouched=function(){},this._compareWith=Nn}return Object.defineProperty(n.prototype,"compareWith",{set:function(n){if("function"!=typeof n)throw new Error("compareWith must be a function, but received "+JSON.stringify(n));this._compareWith=n},enumerable:!0,configurable:!0}),n.prototype.writeValue=function(n){var t,e=this;if(this.value=n,Array.isArray(n)){var r=n.map(function(n){return e._getOptionId(n)});t=function(n,t){n._setSelected(r.indexOf(t.toString())>-1)}}else t=function(n,t){n._setSelected(!1)};this._optionMap.forEach(t)},n.prototype.registerOnChange=function(n){var t=this;this.onChange=function(e){var r=[];if(e.hasOwnProperty("selectedOptions"))for(var o=e.selectedOptions,i=0;i1?"path: '"+n.path.join(" -> ")+"'":n.path[0]?"name: '"+n.path+"'":"unspecified name attribute",new Error(t+" "+e)}function Qf(n){return null!=n?_f.compose(n.map(Af)):null}function Wf(n){return null!=n?_f.composeAsync(n.map(Tf)):null}var Kf=[Of,Vf,If,Lf,Bf,Mf],Jf=function(n){function t(){return null!==n&&n.apply(this,arguments)||this}return o(t,n),t.prototype.ngOnInit=function(){this._checkParentType(),this.formDirective.addFormGroup(this)},t.prototype.ngOnDestroy=function(){this.formDirective&&this.formDirective.removeFormGroup(this)},Object.defineProperty(t.prototype,"control",{get:function(){return this.formDirective.getFormGroup(this)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"path",{get:function(){return Gf(this.name,this._parent)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"formDirective",{get:function(){return this._parent?this._parent.formDirective:null},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"validator",{get:function(){return Qf(this._validators)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"asyncValidator",{get:function(){return Wf(this._asyncValidators)},enumerable:!0,configurable:!0}),t.prototype._checkParentType=function(){},t}(yf),Yf=function(n){function t(t){return n.call(this,t)||this}return o(t,n),t}(function(){function n(n){this._cd=n}return Object.defineProperty(n.prototype,"ngClassUntouched",{get:function(){return!!this._cd.control&&this._cd.control.untouched},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassTouched",{get:function(){return!!this._cd.control&&this._cd.control.touched},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassPristine",{get:function(){return!!this._cd.control&&this._cd.control.pristine},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassDirty",{get:function(){return!!this._cd.control&&this._cd.control.dirty},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassValid",{get:function(){return!!this._cd.control&&this._cd.control.valid},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassInvalid",{get:function(){return!!this._cd.control&&this._cd.control.invalid},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"ngClassPending",{get:function(){return!!this._cd.control&&this._cd.control.pending},enumerable:!0,configurable:!0}),n}());function Xf(n){var t=th(n)?n.validators:n;return Array.isArray(t)?Qf(t):t||null}function nh(n,t){var e=th(t)?t.asyncValidators:n;return Array.isArray(e)?Wf(e):e||null}function th(n){return null!=n&&!Array.isArray(n)&&"object"==typeof n}var eh=function(){function n(n,t){this.validator=n,this.asyncValidator=t,this._onCollectionChange=function(){},this.pristine=!0,this.touched=!1,this._onDisabledChange=[]}return Object.defineProperty(n.prototype,"parent",{get:function(){return this._parent},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"valid",{get:function(){return"VALID"===this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"invalid",{get:function(){return"INVALID"===this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"pending",{get:function(){return"PENDING"==this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"disabled",{get:function(){return"DISABLED"===this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"enabled",{get:function(){return"DISABLED"!==this.status},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"dirty",{get:function(){return!this.pristine},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"untouched",{get:function(){return!this.touched},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"updateOn",{get:function(){return this._updateOn?this._updateOn:this.parent?this.parent.updateOn:"change"},enumerable:!0,configurable:!0}),n.prototype.setValidators=function(n){this.validator=Xf(n)},n.prototype.setAsyncValidators=function(n){this.asyncValidator=nh(n)},n.prototype.clearValidators=function(){this.validator=null},n.prototype.clearAsyncValidators=function(){this.asyncValidator=null},n.prototype.markAsTouched=function(n){void 0===n&&(n={}),this.touched=!0,this._parent&&!n.onlySelf&&this._parent.markAsTouched(n)},n.prototype.markAsUntouched=function(n){void 0===n&&(n={}),this.touched=!1,this._pendingTouched=!1,this._forEachChild(function(n){n.markAsUntouched({onlySelf:!0})}),this._parent&&!n.onlySelf&&this._parent._updateTouched(n)},n.prototype.markAsDirty=function(n){void 0===n&&(n={}),this.pristine=!1,this._parent&&!n.onlySelf&&this._parent.markAsDirty(n)},n.prototype.markAsPristine=function(n){void 0===n&&(n={}),this.pristine=!0,this._pendingDirty=!1,this._forEachChild(function(n){n.markAsPristine({onlySelf:!0})}),this._parent&&!n.onlySelf&&this._parent._updatePristine(n)},n.prototype.markAsPending=function(n){void 0===n&&(n={}),this.status="PENDING",!1!==n.emitEvent&&this.statusChanges.emit(this.status),this._parent&&!n.onlySelf&&this._parent.markAsPending(n)},n.prototype.disable=function(n){void 0===n&&(n={}),this.status="DISABLED",this.errors=null,this._forEachChild(function(t){t.disable(i({},n,{onlySelf:!0}))}),this._updateValue(),!1!==n.emitEvent&&(this.valueChanges.emit(this.value),this.statusChanges.emit(this.status)),this._updateAncestors(n),this._onDisabledChange.forEach(function(n){return n(!0)})},n.prototype.enable=function(n){void 0===n&&(n={}),this.status="VALID",this._forEachChild(function(t){t.enable(i({},n,{onlySelf:!0}))}),this.updateValueAndValidity({onlySelf:!0,emitEvent:n.emitEvent}),this._updateAncestors(n),this._onDisabledChange.forEach(function(n){return n(!1)})},n.prototype._updateAncestors=function(n){this._parent&&!n.onlySelf&&(this._parent.updateValueAndValidity(n),this._parent._updatePristine(),this._parent._updateTouched())},n.prototype.setParent=function(n){this._parent=n},n.prototype.updateValueAndValidity=function(n){void 0===n&&(n={}),this._setInitialStatus(),this._updateValue(),this.enabled&&(this._cancelExistingSubscription(),this.errors=this._runValidator(),this.status=this._calculateStatus(),"VALID"!==this.status&&"PENDING"!==this.status||this._runAsyncValidator(n.emitEvent)),!1!==n.emitEvent&&(this.valueChanges.emit(this.value),this.statusChanges.emit(this.status)),this._parent&&!n.onlySelf&&this._parent.updateValueAndValidity(n)},n.prototype._updateTreeValidity=function(n){void 0===n&&(n={emitEvent:!0}),this._forEachChild(function(t){return t._updateTreeValidity(n)}),this.updateValueAndValidity({onlySelf:!0,emitEvent:n.emitEvent})},n.prototype._setInitialStatus=function(){this.status=this._allControlsDisabled()?"DISABLED":"VALID"},n.prototype._runValidator=function(){return this.validator?this.validator(this):null},n.prototype._runAsyncValidator=function(n){var t=this;if(this.asyncValidator){this.status="PENDING";var e=Cf(this.asyncValidator(this));this._asyncValidationSubscription=e.subscribe(function(e){return t.setErrors(e,{emitEvent:n})})}},n.prototype._cancelExistingSubscription=function(){this._asyncValidationSubscription&&this._asyncValidationSubscription.unsubscribe()},n.prototype.setErrors=function(n,t){void 0===t&&(t={}),this.errors=n,this._updateControlsErrors(!1!==t.emitEvent)},n.prototype.get=function(n){return function(n,t,e){return null==t?null:(t instanceof Array||(t=t.split(".")),t instanceof Array&&0===t.length?null:t.reduce(function(n,t){return n instanceof oh?n.controls.hasOwnProperty(t)?n.controls[t]:null:n instanceof ih&&n.at(t)||null},n))}(this,n)},n.prototype.getError=function(n,t){var e=t?this.get(t):this;return e&&e.errors?e.errors[n]:null},n.prototype.hasError=function(n,t){return!!this.getError(n,t)},Object.defineProperty(n.prototype,"root",{get:function(){for(var n=this;n._parent;)n=n._parent;return n},enumerable:!0,configurable:!0}),n.prototype._updateControlsErrors=function(n){this.status=this._calculateStatus(),n&&this.statusChanges.emit(this.status),this._parent&&this._parent._updateControlsErrors(n)},n.prototype._initObservables=function(){this.valueChanges=new Ho,this.statusChanges=new Ho},n.prototype._calculateStatus=function(){return this._allControlsDisabled()?"DISABLED":this.errors?"INVALID":this._anyControlsHaveStatus("PENDING")?"PENDING":this._anyControlsHaveStatus("INVALID")?"INVALID":"VALID"},n.prototype._anyControlsHaveStatus=function(n){return this._anyControls(function(t){return t.status===n})},n.prototype._anyControlsDirty=function(){return this._anyControls(function(n){return n.dirty})},n.prototype._anyControlsTouched=function(){return this._anyControls(function(n){return n.touched})},n.prototype._updatePristine=function(n){void 0===n&&(n={}),this.pristine=!this._anyControlsDirty(),this._parent&&!n.onlySelf&&this._parent._updatePristine(n)},n.prototype._updateTouched=function(n){void 0===n&&(n={}),this.touched=this._anyControlsTouched(),this._parent&&!n.onlySelf&&this._parent._updateTouched(n)},n.prototype._isBoxedValue=function(n){return"object"==typeof n&&null!==n&&2===Object.keys(n).length&&"value"in n&&"disabled"in n},n.prototype._registerOnCollectionChange=function(n){this._onCollectionChange=n},n.prototype._setUpdateStrategy=function(n){th(n)&&null!=n.updateOn&&(this._updateOn=n.updateOn)},n}(),rh=function(n){function t(t,e,r){void 0===t&&(t=null);var o=n.call(this,Xf(e),nh(r,e))||this;return o._onChange=[],o._applyFormState(t),o._setUpdateStrategy(e),o.updateValueAndValidity({onlySelf:!0,emitEvent:!1}),o._initObservables(),o}return o(t,n),t.prototype.setValue=function(n,t){var e=this;void 0===t&&(t={}),this.value=this._pendingValue=n,this._onChange.length&&!1!==t.emitModelToViewChange&&this._onChange.forEach(function(n){return n(e.value,!1!==t.emitViewToModelChange)}),this.updateValueAndValidity(t)},t.prototype.patchValue=function(n,t){void 0===t&&(t={}),this.setValue(n,t)},t.prototype.reset=function(n,t){void 0===n&&(n=null),void 0===t&&(t={}),this._applyFormState(n),this.markAsPristine(t),this.markAsUntouched(t),this.setValue(this.value,t),this._pendingChange=!1},t.prototype._updateValue=function(){},t.prototype._anyControls=function(n){return!1},t.prototype._allControlsDisabled=function(){return this.disabled},t.prototype.registerOnChange=function(n){this._onChange.push(n)},t.prototype._clearChangeFns=function(){this._onChange=[],this._onDisabledChange=[],this._onCollectionChange=function(){}},t.prototype.registerOnDisabledChange=function(n){this._onDisabledChange.push(n)},t.prototype._forEachChild=function(n){},t.prototype._syncPendingControls=function(){return!("submit"!==this.updateOn||(this._pendingDirty&&this.markAsDirty(),this._pendingTouched&&this.markAsTouched(),!this._pendingChange)||(this.setValue(this._pendingValue,{onlySelf:!0,emitModelToViewChange:!1}),0))},t.prototype._applyFormState=function(n){this._isBoxedValue(n)?(this.value=this._pendingValue=n.value,n.disabled?this.disable({onlySelf:!0,emitEvent:!1}):this.enable({onlySelf:!0,emitEvent:!1})):this.value=this._pendingValue=n},t}(eh),oh=function(n){function t(t,e,r){var o=n.call(this,Xf(e),nh(r,e))||this;return o.controls=t,o._initObservables(),o._setUpdateStrategy(e),o._setUpControls(),o.updateValueAndValidity({onlySelf:!0,emitEvent:!1}),o}return o(t,n),t.prototype.registerControl=function(n,t){return this.controls[n]?this.controls[n]:(this.controls[n]=t,t.setParent(this),t._registerOnCollectionChange(this._onCollectionChange),t)},t.prototype.addControl=function(n,t){this.registerControl(n,t),this.updateValueAndValidity(),this._onCollectionChange()},t.prototype.removeControl=function(n){this.controls[n]&&this.controls[n]._registerOnCollectionChange(function(){}),delete this.controls[n],this.updateValueAndValidity(),this._onCollectionChange()},t.prototype.setControl=function(n,t){this.controls[n]&&this.controls[n]._registerOnCollectionChange(function(){}),delete this.controls[n],t&&this.registerControl(n,t),this.updateValueAndValidity(),this._onCollectionChange()},t.prototype.contains=function(n){return this.controls.hasOwnProperty(n)&&this.controls[n].enabled},t.prototype.setValue=function(n,t){var e=this;void 0===t&&(t={}),this._checkAllValuesPresent(n),Object.keys(n).forEach(function(r){e._throwIfControlMissing(r),e.controls[r].setValue(n[r],{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)},t.prototype.patchValue=function(n,t){var e=this;void 0===t&&(t={}),Object.keys(n).forEach(function(r){e.controls[r]&&e.controls[r].patchValue(n[r],{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)},t.prototype.reset=function(n,t){void 0===n&&(n={}),void 0===t&&(t={}),this._forEachChild(function(e,r){e.reset(n[r],{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t),this._updatePristine(t),this._updateTouched(t)},t.prototype.getRawValue=function(){return this._reduceChildren({},function(n,t,e){return n[e]=t instanceof rh?t.value:t.getRawValue(),n})},t.prototype._syncPendingControls=function(){var n=this._reduceChildren(!1,function(n,t){return!!t._syncPendingControls()||n});return n&&this.updateValueAndValidity({onlySelf:!0}),n},t.prototype._throwIfControlMissing=function(n){if(!Object.keys(this.controls).length)throw new Error("\n There are no form controls registered with this group yet. If you're using ngModel,\n you may want to check next tick (e.g. use setTimeout).\n ");if(!this.controls[n])throw new Error("Cannot find form control with name: "+n+".")},t.prototype._forEachChild=function(n){var t=this;Object.keys(this.controls).forEach(function(e){return n(t.controls[e],e)})},t.prototype._setUpControls=function(){var n=this;this._forEachChild(function(t){t.setParent(n),t._registerOnCollectionChange(n._onCollectionChange)})},t.prototype._updateValue=function(){this.value=this._reduceValue()},t.prototype._anyControls=function(n){var t=this,e=!1;return this._forEachChild(function(r,o){e=e||t.contains(o)&&n(r)}),e},t.prototype._reduceValue=function(){var n=this;return this._reduceChildren({},function(t,e,r){return(e.enabled||n.disabled)&&(t[r]=e.value),t})},t.prototype._reduceChildren=function(n,t){var e=n;return this._forEachChild(function(n,r){e=t(e,n,r)}),e},t.prototype._allControlsDisabled=function(){var n,t;try{for(var e=s(Object.keys(this.controls)),r=e.next();!r.done;r=e.next())if(this.controls[r.value].enabled)return!1}catch(o){n={error:o}}finally{try{r&&!r.done&&(t=e.return)&&t.call(e)}finally{if(n)throw n.error}}return Object.keys(this.controls).length>0||this.disabled},t.prototype._checkAllValuesPresent=function(n){this._forEachChild(function(t,e){if(void 0===n[e])throw new Error("Must supply a value for form control with name: '"+e+"'.")})},t}(eh),ih=function(n){function t(t,e,r){var o=n.call(this,Xf(e),nh(r,e))||this;return o.controls=t,o._initObservables(),o._setUpdateStrategy(e),o._setUpControls(),o.updateValueAndValidity({onlySelf:!0,emitEvent:!1}),o}return o(t,n),t.prototype.at=function(n){return this.controls[n]},t.prototype.push=function(n){this.controls.push(n),this._registerControl(n),this.updateValueAndValidity(),this._onCollectionChange()},t.prototype.insert=function(n,t){this.controls.splice(n,0,t),this._registerControl(t),this.updateValueAndValidity()},t.prototype.removeAt=function(n){this.controls[n]&&this.controls[n]._registerOnCollectionChange(function(){}),this.controls.splice(n,1),this.updateValueAndValidity()},t.prototype.setControl=function(n,t){this.controls[n]&&this.controls[n]._registerOnCollectionChange(function(){}),this.controls.splice(n,1),t&&(this.controls.splice(n,0,t),this._registerControl(t)),this.updateValueAndValidity(),this._onCollectionChange()},Object.defineProperty(t.prototype,"length",{get:function(){return this.controls.length},enumerable:!0,configurable:!0}),t.prototype.setValue=function(n,t){var e=this;void 0===t&&(t={}),this._checkAllValuesPresent(n),n.forEach(function(n,r){e._throwIfControlMissing(r),e.at(r).setValue(n,{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)},t.prototype.patchValue=function(n,t){var e=this;void 0===t&&(t={}),n.forEach(function(n,r){e.at(r)&&e.at(r).patchValue(n,{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t)},t.prototype.reset=function(n,t){void 0===n&&(n=[]),void 0===t&&(t={}),this._forEachChild(function(e,r){e.reset(n[r],{onlySelf:!0,emitEvent:t.emitEvent})}),this.updateValueAndValidity(t),this._updatePristine(t),this._updateTouched(t)},t.prototype.getRawValue=function(){return this.controls.map(function(n){return n instanceof rh?n.value:n.getRawValue()})},t.prototype._syncPendingControls=function(){var n=this.controls.reduce(function(n,t){return!!t._syncPendingControls()||n},!1);return n&&this.updateValueAndValidity({onlySelf:!0}),n},t.prototype._throwIfControlMissing=function(n){if(!this.controls.length)throw new Error("\n There are no form controls registered with this array yet. If you're using ngModel,\n you may want to check next tick (e.g. use setTimeout).\n ");if(!this.at(n))throw new Error("Cannot find form control at index "+n)},t.prototype._forEachChild=function(n){this.controls.forEach(function(t,e){n(t,e)})},t.prototype._updateValue=function(){var n=this;this.value=this.controls.filter(function(t){return t.enabled||n.disabled}).map(function(n){return n.value})},t.prototype._anyControls=function(n){return this.controls.some(function(t){return t.enabled&&n(t)})},t.prototype._setUpControls=function(){var n=this;this._forEachChild(function(t){return n._registerControl(t)})},t.prototype._checkAllValuesPresent=function(n){this._forEachChild(function(t,e){if(void 0===n[e])throw new Error("Must supply a value for form control at index: "+e+".")})},t.prototype._allControlsDisabled=function(){var n,t;try{for(var e=s(this.controls),r=e.next();!r.done;r=e.next())if(r.value.enabled)return!1}catch(o){n={error:o}}finally{try{r&&!r.done&&(t=e.return)&&t.call(e)}finally{if(n)throw n.error}}return this.controls.length>0||this.disabled},t.prototype._registerControl=function(n){n.setParent(this),n._registerOnCollectionChange(this._onCollectionChange)},t}(eh),lh=Promise.resolve(null),uh=function(n){function t(t,e){var r=n.call(this)||this;return r.submitted=!1,r._directives=[],r.ngSubmit=new Ho,r.form=new oh({},Qf(t),Wf(e)),r}return o(t,n),t.prototype.ngAfterViewInit=function(){this._setUpdateStrategy()},Object.defineProperty(t.prototype,"formDirective",{get:function(){return this},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"control",{get:function(){return this.form},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"path",{get:function(){return[]},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"controls",{get:function(){return this.form.controls},enumerable:!0,configurable:!0}),t.prototype.addControl=function(n){var t=this;lh.then(function(){var e=t._findContainer(n.path);n.control=e.registerControl(n.name,n.control),Zf(n.control,n),n.control.updateValueAndValidity({emitEvent:!1}),t._directives.push(n)})},t.prototype.getControl=function(n){return this.form.get(n.path)},t.prototype.removeControl=function(n){var t=this;lh.then(function(){var e,r,o=t._findContainer(n.path);o&&o.removeControl(n.name),(r=(e=t._directives).indexOf(n))>-1&&e.splice(r,1)})},t.prototype.addFormGroup=function(n){var t=this;lh.then(function(){var e=t._findContainer(n.path),r=new oh({});(function(n,t){null==n&&$f(t,"Cannot find control with"),n.validator=_f.compose([n.validator,t.validator]),n.asyncValidator=_f.composeAsync([n.asyncValidator,t.asyncValidator])})(r,n),e.registerControl(n.name,r),r.updateValueAndValidity({emitEvent:!1})})},t.prototype.removeFormGroup=function(n){var t=this;lh.then(function(){var e=t._findContainer(n.path);e&&e.removeControl(n.name)})},t.prototype.getFormGroup=function(n){return this.form.get(n.path)},t.prototype.updateModel=function(n,t){var e=this;lh.then(function(){e.form.get(n.path).setValue(t)})},t.prototype.setValue=function(n){this.control.setValue(n)},t.prototype.onSubmit=function(n){return this.submitted=!0,t=this._directives,this.form._syncPendingControls(),t.forEach(function(n){var t=n.control;"submit"===t.updateOn&&t._pendingChange&&(n.viewToModelUpdate(t._pendingValue),t._pendingChange=!1)}),this.ngSubmit.emit(n),!1;var t},t.prototype.onReset=function(){this.resetForm()},t.prototype.resetForm=function(n){void 0===n&&(n=void 0),this.form.reset(n),this.submitted=!1},t.prototype._setUpdateStrategy=function(){this.options&&null!=this.options.updateOn&&(this.form._updateOn=this.options.updateOn)},t.prototype._findContainer=function(n){return n.pop(),n.length?this.form.get(n):this.form},t}(yf),sh=function(){function n(){}return n.modelParentException=function(){throw new Error('\n ngModel cannot be used to register form controls with a parent formGroup directive. Try using\n formGroup\'s partner directive "formControlName" instead. Example:\n\n \n
\n \n
\n\n In your class:\n\n this.myGroup = new FormGroup({\n firstName: new FormControl()\n });\n\n Or, if you\'d like to avoid registering this form control, indicate that it\'s standalone in ngModelOptions:\n\n Example:\n\n \n
\n \n \n
\n ')},n.formGroupNameException=function(){throw new Error("\n ngModel cannot be used to register form controls with a parent formGroupName or formArrayName directive.\n\n Option 1: Use formControlName instead of ngModel (reactive strategy):\n\n "+Hf+"\n\n Option 2: Update ngModel's parent be ngModelGroup (template-driven strategy):\n\n "+Rf)},n.missingNameException=function(){throw new Error('If ngModel is used within a form tag, either the name attribute must be set or the form\n control must be defined as \'standalone\' in ngModelOptions.\n\n Example 1: \n Example 2: ')},n.modelGroupParentException=function(){throw new Error("\n ngModelGroup cannot be used with a parent formGroup directive.\n\n Option 1: Use formGroupName instead of ngModelGroup (reactive strategy):\n\n "+Hf+"\n\n Option 2: Use a regular form tag instead of the formGroup directive (template-driven strategy):\n\n "+Rf)},n.ngFormWarning=function(){console.warn("\n It looks like you're using 'ngForm'.\n\n Support for using the 'ngForm' element selector has been deprecated in Angular v6 and will be removed\n in Angular v9.\n\n Use 'ng-form' instead.\n\n Before:\n \n\n After:\n \n ")},n}(),ah=new wn("NgFormSelectorWarning"),ch=function(n){function t(t,e,r){var o=n.call(this)||this;return o._parent=t,o._validators=e,o._asyncValidators=r,o}var e;return o(t,n),e=t,t.prototype._checkParentType=function(){this._parent instanceof e||this._parent instanceof uh||sh.modelGroupParentException()},t}(Jf),fh=Promise.resolve(null),hh=function(n){function t(t,e,r,o){var i=n.call(this)||this;return i.control=new rh,i._registered=!1,i.update=new Ho,i._parent=t,i._rawValidators=e||[],i._rawAsyncValidators=r||[],i.valueAccessor=function(n,t){if(!t)return null;Array.isArray(t)||$f(n,"Value accessor was not provided as an array for form control with");var e=void 0,r=void 0,o=void 0;return t.forEach(function(t){var i;t.constructor===Sf?e=t:(i=t,Kf.some(function(n){return i.constructor===n})?(r&&$f(n,"More than one built-in value accessor matches form control with"),r=t):(o&&$f(n,"More than one custom value accessor matches form control with"),o=t))}),o||r||e||($f(n,"No valid value accessor for form control with"),null)}(i,o),i}return o(t,n),t.prototype.ngOnChanges=function(n){this._checkForErrors(),this._registered||this._setUpControl(),"isDisabled"in n&&this._updateDisabled(n),function(n,t){if(!n.hasOwnProperty("model"))return!1;var e=n.model;return!!e.isFirstChange()||!Nn(t,e.currentValue)}(n,this.viewModel)&&(this._updateValue(this.model),this.viewModel=this.model)},t.prototype.ngOnDestroy=function(){this.formDirective&&this.formDirective.removeControl(this)},Object.defineProperty(t.prototype,"path",{get:function(){return this._parent?Gf(this.name,this._parent):[this.name]},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"formDirective",{get:function(){return this._parent?this._parent.formDirective:null},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"validator",{get:function(){return Qf(this._rawValidators)},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"asyncValidator",{get:function(){return Wf(this._rawAsyncValidators)},enumerable:!0,configurable:!0}),t.prototype.viewToModelUpdate=function(n){this.viewModel=n,this.update.emit(n)},t.prototype._setUpControl=function(){this._setUpdateStrategy(),this._isStandalone()?this._setUpStandalone():this.formDirective.addControl(this),this._registered=!0},t.prototype._setUpdateStrategy=function(){this.options&&null!=this.options.updateOn&&(this.control._updateOn=this.options.updateOn)},t.prototype._isStandalone=function(){return!this._parent||!(!this.options||!this.options.standalone)},t.prototype._setUpStandalone=function(){Zf(this.control,this),this.control.updateValueAndValidity({emitEvent:!1})},t.prototype._checkForErrors=function(){this._isStandalone()||this._checkParentType(),this._checkName()},t.prototype._checkParentType=function(){!(this._parent instanceof ch)&&this._parent instanceof Jf?sh.formGroupNameException():this._parent instanceof ch||this._parent instanceof uh||sh.modelParentException()},t.prototype._checkName=function(){this.options&&this.options.name&&(this.name=this.options.name),this._isStandalone()||this.name||sh.missingNameException()},t.prototype._updateValue=function(n){var t=this;fh.then(function(){t.control.setValue(n,{emitViewToModelChange:!1})})},t.prototype._updateDisabled=function(n){var t=this,e=n.isDisabled.currentValue,r=""===e||e&&"false"!==e;fh.then(function(){r&&!t.control.disabled?t.control.disable():!r&&t.control.disabled&&t.control.enable()})},t}(Nf),ph=function(){return function(){}}(),dh=function(){function n(){}var t;return t=n,n.withConfig=function(n){return{ngModule:t,providers:[{provide:ah,useValue:n.warnOnDeprecatedNgFormSelector}]}},n}(),gh=Pl({encapsulation:2,styles:[],data:{}});function vh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,["",""]))],function(n,t){n(t,1,0,t.context.$implicit),n(t,2,0,t.context.$implicit)},function(n,t){n(t,3,0,t.context.$implicit)})}function yh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","20"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(-1,null,["20"]))],function(n,t){n(t,1,0,"20"),n(t,2,0,"20")},null)}function mh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","50"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(-1,null,["50"]))],function(n,t){n(t,1,0,"50"),n(t,2,0,"50")},null)}function bh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","100"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(-1,null,["100"]))],function(n,t){n(t,1,0,"100"),n(t,2,0,"100")},null)}function _h(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,["",""]))],function(n,t){var e=t.component;n(t,1,0,e.totalNumberOfRiskHotspots),n(t,2,0,e.totalNumberOfRiskHotspots)},function(n,t){n(t,3,0,t.component.translations.all)})}function wh(n){return bs(0,[(n()(),lu(0,0,null,null,17,"select",[],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"change"],[null,"blur"]],function(n,t,e){var r=!0,o=n.component;return"change"===t&&(r=!1!==Vu(n,1).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,1).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.numberOfRiskHotspots=e)&&r),r},null,null)),Qu(1,16384,null,0,Lf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n){return[n]},[Lf]),Qu(3,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(5,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(6,0,null,null,3,"option",[["value","10"]],null,null,null,null,null)),Qu(7,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(8,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(-1,null,["10"])),(n()(),iu(16777216,null,null,1,null,yh)),Qu(11,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,mh)),Qu(13,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,bh)),Qu(15,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,_h)),Qu(17,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component;n(t,3,0,e.settings.numberOfRiskHotspots),n(t,7,0,"10"),n(t,8,0,"10"),n(t,11,0,e.totalNumberOfRiskHotspots>10),n(t,13,0,e.totalNumberOfRiskHotspots>20),n(t,15,0,e.totalNumberOfRiskHotspots>50),n(t,17,0,e.totalNumberOfRiskHotspots>100)},function(n,t){n(t,0,0,Vu(t,5).ngClassUntouched,Vu(t,5).ngClassTouched,Vu(t,5).ngClassPristine,Vu(t,5).ngClassDirty,Vu(t,5).ngClassValid,Vu(t,5).ngClassInvalid,Vu(t,5).ngClassPending)})}function Ch(n){return bs(0,[(n()(),lu(0,0,null,null,0,"col",[["class","column105"]],null,null,null,null,null))],null,null)}function Eh(n){return bs(0,[(n()(),lu(0,0,null,null,7,"th",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting(""+n.context.index,e)&&r),r},null,null)),(n()(),lu(2,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(3,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(4,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(5,null,["",""])),(n()(),lu(6,0,null,null,1,"a",[],[[8,"href",4]],null,null,null,null)),(n()(),lu(7,0,null,null,0,"i",[["class","icon-info-circled"]],null,null,null,null,null))],function(n,t){var e=t.component,r=n(t,4,0,e.settings.sortBy===""+t.context.index&&"desc"===e.settings.sortOrder,e.settings.sortBy===""+t.context.index&&"asc"===e.settings.sortOrder,e.settings.sortBy!==""+t.context.index);n(t,3,0,"icon-down-dir",r)},function(n,t){n(t,5,0,t.context.$implicit.name),n(t,6,0,ru(1,"",t.context.$implicit.explanationUrl,""))})}function xh(n){return bs(0,[(n()(),lu(0,0,null,null,3,"td",[["class","right"]],null,null,null,null,null)),Qu(1,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(2,{lightred:0,lightgreen:1}),(n()(),vs(3,null,["",""]))],function(n,t){var e=n(t,2,0,t.context.$implicit.exceeded,!t.context.$implicit.exceeded);n(t,1,0,"right",e)},function(n,t){n(t,3,0,t.context.$implicit.value)})}function Oh(n){return bs(0,[(n()(),lu(0,0,null,null,10,"tr",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,1,"td",[],null,null,null,null,null)),(n()(),vs(2,null,["",""])),(n()(),lu(3,0,null,null,2,"td",[],null,null,null,null,null)),(n()(),lu(4,0,null,null,1,"a",[],[[8,"href",4]],null,null,null,null)),(n()(),vs(5,null,["",""])),(n()(),lu(6,0,null,null,2,"td",[],[[8,"title",0]],null,null,null,null)),(n()(),lu(7,0,null,null,1,"a",[],[[8,"href",4]],null,null,null,null)),(n()(),vs(8,null,[" "," "])),(n()(),iu(16777216,null,null,1,null,xh)),Qu(10,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null)],function(n,t){n(t,10,0,t.context.$implicit.metrics)},function(n,t){n(t,2,0,t.context.$implicit.assembly),n(t,4,0,t.context.$implicit.reportPath),n(t,5,0,t.context.$implicit.class),n(t,6,0,t.context.$implicit.methodName),n(t,7,0,t.context.$implicit.reportPath+"#file"+t.context.$implicit.fileIndex+"_line"+t.context.$implicit.line),n(t,8,0,t.context.$implicit.methodShortName)})}function kh(n){return bs(0,[(n()(),lu(0,0,null,null,62,"div",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,28,"div",[["class","customizebox"]],null,null,null,null,null)),(n()(),lu(2,0,null,null,12,"div",[],null,null,null,null,null)),(n()(),lu(3,0,null,null,11,"select",[["name","assembly"]],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"change"],[null,"blur"]],function(n,t,e){var r=!0,o=n.component;return"change"===t&&(r=!1!==Vu(n,4).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,4).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.assembly=e)&&r),"ngModelChange"===t&&(r=!1!==o.updateRiskHotpots()&&r),r},null,null)),Qu(4,16384,null,0,Lf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n){return[n]},[Lf]),Qu(6,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{name:[0,"name"],model:[1,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(8,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(9,0,null,null,3,"option",[["value",""]],null,null,null,null,null)),Qu(10,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(11,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(12,null,["",""])),(n()(),iu(16777216,null,null,1,null,vh)),Qu(14,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),lu(15,0,null,null,4,"div",[["class","center"]],null,null,null,null,null)),(n()(),lu(16,0,null,null,1,"span",[],null,null,null,null,null)),(n()(),vs(17,null,["",""])),(n()(),iu(16777216,null,null,1,null,wh)),Qu(19,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(20,0,null,null,0,"div",[["class","center"]],null,null,null,null,null)),(n()(),lu(21,0,null,null,8,"div",[["class","right"]],null,null,null,null,null)),(n()(),lu(22,0,null,null,1,"span",[],null,null,null,null,null)),(n()(),vs(23,null,[""," "])),(n()(),lu(24,0,null,null,5,"input",[["type","text"]],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"input"],[null,"blur"],[null,"compositionstart"],[null,"compositionend"]],function(n,t,e){var r=!0,o=n.component;return"input"===t&&(r=!1!==Vu(n,25)._handleInput(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,25).onTouched()&&r),"compositionstart"===t&&(r=!1!==Vu(n,25)._compositionStart()&&r),"compositionend"===t&&(r=!1!==Vu(n,25)._compositionEnd(e.target.value)&&r),"ngModelChange"===t&&(r=!1!==(o.settings.filter=e)&&r),"ngModelChange"===t&&(r=!1!==o.updateRiskHotpots()&&r),r},null,null)),Qu(25,16384,null,0,Sf,[Xr,Qr,[2,kf]],null,null),Wu(1024,null,xf,function(n){return[n]},[Sf]),Qu(27,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(29,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(30,0,null,null,32,"table",[["class","overview table-fixed stripped"]],null,null,null,null,null)),(n()(),lu(31,0,null,null,5,"colgroup",[],null,null,null,null,null)),(n()(),lu(32,0,null,null,0,"col",[],null,null,null,null,null)),(n()(),lu(33,0,null,null,0,"col",[],null,null,null,null,null)),(n()(),lu(34,0,null,null,0,"col",[],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Ch)),Qu(36,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),lu(37,0,null,null,21,"thead",[],null,null,null,null,null)),(n()(),lu(38,0,null,null,20,"tr",[],null,null,null,null,null)),(n()(),lu(39,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(40,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("assembly",e)&&r),r},null,null)),(n()(),lu(41,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(42,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(43,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(44,null,["",""])),(n()(),lu(45,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(46,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("class",e)&&r),r},null,null)),(n()(),lu(47,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(48,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(49,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(50,null,["",""])),(n()(),lu(51,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(52,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("method",e)&&r),r},null,null)),(n()(),lu(53,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(54,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(55,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(56,null,["",""])),(n()(),iu(16777216,null,null,1,null,Eh)),Qu(58,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),lu(59,0,null,null,3,"tbody",[],null,null,null,null,null)),(n()(),iu(16777216,null,null,2,null,Oh)),Qu(61,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(t=0,e=ec,r=[],Ku(-1,t|=16,null,0,e,e,r))],function(n,t){var e=t.component;n(t,6,0,"assembly",e.settings.assembly),n(t,10,0,""),n(t,11,0,""),n(t,14,0,e.assemblies),n(t,19,0,e.totalNumberOfRiskHotspots>10),n(t,27,0,e.settings.filter),n(t,36,0,e.riskHotspotMetrics);var r=n(t,43,0,"assembly"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"assembly"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"assembly"!==e.settings.sortBy);n(t,42,0,"icon-down-dir",r);var o=n(t,49,0,"class"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"class"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"class"!==e.settings.sortBy);n(t,48,0,"icon-down-dir",o);var i=n(t,55,0,"method"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"method"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"method"!==e.settings.sortBy);n(t,54,0,"icon-down-dir",i),n(t,58,0,e.riskHotspotMetrics),n(t,61,0,function(n,t,e,r){if(_t.isWrapped(r)){r=_t.unwrap(r);var o=n.def.nodes[61].bindingIndex+0,i=_t.unwrap(n.oldValues[o]);n.oldValues[o]=new _t(i)}return r}(t,0,0,Vu(t,62).transform(e.riskHotspots,0,e.settings.numberOfRiskHotspots)))},function(n,t){var e=t.component;n(t,3,0,Vu(t,8).ngClassUntouched,Vu(t,8).ngClassTouched,Vu(t,8).ngClassPristine,Vu(t,8).ngClassDirty,Vu(t,8).ngClassValid,Vu(t,8).ngClassInvalid,Vu(t,8).ngClassPending),n(t,12,0,e.translations.assembly),n(t,17,0,e.translations.top),n(t,23,0,e.translations.filter),n(t,24,0,Vu(t,29).ngClassUntouched,Vu(t,29).ngClassTouched,Vu(t,29).ngClassPristine,Vu(t,29).ngClassDirty,Vu(t,29).ngClassValid,Vu(t,29).ngClassInvalid,Vu(t,29).ngClassPending),n(t,44,0,e.translations.assembly),n(t,50,0,e.translations.class),n(t,56,0,e.translations.method)});var t,e,r}function Sh(n){return bs(0,[(n()(),iu(16777216,null,null,1,null,kh)),Qu(1,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){n(t,1,0,t.component.totalNumberOfRiskHotspots>0)},null)}function Ah(n){return bs(0,[(n()(),lu(0,0,null,null,1,"risk-hotspots",[],null,[["window","beforeunload"]],function(n,t,e){var r=!0;return"window:beforeunload"===t&&(r=!1!==Vu(n,1).onDonBeforeUnlodad()&&r),r},Sh,gh)),Qu(1,114688,null,0,Na,[Ta],null,null)],function(n,t){n(t,1,0)},null)}var Th=xu("risk-hotspots",Na,Ah,{},{},[]),Ih=function(){function n(){this.grayVisible=!0,this.greenVisible=!1,this.redVisible=!1,this.greenClass="",this.redClass="",this._percentage=NaN}return Object.defineProperty(n.prototype,"percentage",{get:function(){return this._percentage},set:function(n){this._percentage=n,this.grayVisible=isNaN(n),this.greenVisible=!isNaN(n)&&Math.round(n)>0,this.redVisible=!isNaN(n)&&100-Math.round(n)>0,this.greenClass="covered"+Math.round(n),this.redClass="covered"+(100-Math.round(n))},enumerable:!0,configurable:!0}),n}(),Ph=Pl({encapsulation:2,styles:[],data:{}});function Nh(n){return bs(0,[(n()(),lu(0,0,null,null,0,"td",[["class","gray covered100"]],null,null,null,null,null))],null,null)}function Dh(n){return bs(0,[(n()(),lu(0,0,null,null,0,"td",[],[[8,"className",0]],null,null,null,null))],null,function(n,t){n(t,0,0,ru(1,"green ",t.component.greenClass,""))})}function Mh(n){return bs(0,[(n()(),lu(0,0,null,null,0,"td",[],[[8,"className",0]],null,null,null,null))],null,function(n,t){n(t,0,0,ru(1,"red ",t.component.redClass,""))})}function Vh(n){return bs(2,[(n()(),lu(0,0,null,null,6,"table",[["class","coverage"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Nh)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Dh)),Qu(4,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Mh)),Qu(6,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component;n(t,2,0,e.grayVisible),n(t,4,0,e.greenVisible),n(t,6,0,e.redVisible)},null)}var Hh=function(){return function(){this.element=null,this.collapsed=!1,this.branchCoverageAvailable=!1}}(),Rh=Pl({encapsulation:2,styles:[],data:{}});function jh(n){return bs(0,[(n()(),lu(0,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(1,null,["",""]))],null,function(n,t){n(t,1,0,t.component.element.branchCoveragePercentage)})}function Lh(n){return bs(0,[(n()(),lu(0,0,null,null,2,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(1,0,null,null,1,"coverage-bar",[],null,null,null,Vh,Ph)),Qu(2,49152,null,0,Ih,[],{percentage:[0,"percentage"]},null)],function(n,t){n(t,2,0,t.component.element.branchCoverage)},null)}function zh(n){return bs(2,[(n()(),lu(0,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.element.toggleCollapse(e)&&r),r},null,null)),(n()(),lu(2,0,null,null,2,"i",[],null,null,null,null,null)),Qu(3,278528,null,0,Wa,[ol,il,Qr,Xr],{ngClass:[0,"ngClass"]},null),gs(4,{"icon-plus":0,"icon-minus":1}),(n()(),vs(5,null,[" ",""])),(n()(),lu(6,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(7,null,["",""])),(n()(),lu(8,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(9,null,["",""])),(n()(),lu(10,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(11,null,["",""])),(n()(),lu(12,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(13,null,["",""])),(n()(),lu(14,0,null,null,1,"th",[["class","right"]],null,null,null,null,null)),(n()(),vs(15,null,["",""])),(n()(),lu(16,0,null,null,2,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(17,0,null,null,1,"coverage-bar",[],null,null,null,Vh,Ph)),Qu(18,49152,null,0,Ih,[],{percentage:[0,"percentage"]},null),(n()(),iu(16777216,null,null,1,null,jh)),Qu(20,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Lh)),Qu(22,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component,r=n(t,4,0,e.element.collapsed,!e.element.collapsed);n(t,3,0,r),n(t,18,0,e.element.coverage),n(t,20,0,e.branchCoverageAvailable),n(t,22,0,e.branchCoverageAvailable)},function(n,t){var e=t.component;n(t,5,0,e.element.name),n(t,7,0,e.element.coveredLines),n(t,9,0,e.element.uncoveredLines),n(t,11,0,e.element.coverableLines),n(t,13,0,e.element.totalLines),n(t,15,0,e.element.coveragePercentage)})}var Fh=function(){function n(){this.path=null,this._historicCoverages=[]}return Object.defineProperty(n.prototype,"historicCoverages",{get:function(){return this._historicCoverages},set:function(n){if(this._historicCoverages=n,n.length>1){for(var t="",e=0;et?"lightgreen":n1),n(t,4,0,null!==e.clazz.currentHistoricCoverage),n(t,6,0,null===e.clazz.currentHistoricCoverage)},null)}function ap(n){return bs(0,[(n()(),lu(0,0,null,null,2,"td",[["class","right"]],null,null,null,null,null)),(n()(),lu(1,0,null,null,1,"coverage-bar",[],null,null,null,Vh,Ph)),Qu(2,49152,null,0,Ih,[],{percentage:[0,"percentage"]},null)],function(n,t){n(t,2,0,t.component.clazz.branchCoverage)},null)}function cp(n){return bs(2,[(n()(),lu(0,0,null,null,4,"td",[],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,qh)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,$h)),Qu(4,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(5,0,null,null,4,"td",[["class","right"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Qh)),Qu(7,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Wh)),Qu(9,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(10,0,null,null,4,"td",[["class","right"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Kh)),Qu(12,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Jh)),Qu(14,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(15,0,null,null,4,"td",[["class","right"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Yh)),Qu(17,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Xh)),Qu(19,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(20,0,null,null,4,"td",[["class","right"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,np)),Qu(22,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,tp)),Qu(24,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(25,0,null,null,6,"td",[["class","right"]],[[8,"title",0]],null,null,null,null)),(n()(),iu(16777216,null,null,1,null,ep)),Qu(27,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,rp)),Qu(29,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,op)),Qu(31,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(32,0,null,null,2,"td",[["class","right"]],null,null,null,null,null)),(n()(),lu(33,0,null,null,1,"coverage-bar",[],null,null,null,Vh,Ph)),Qu(34,49152,null,0,Ih,[],{percentage:[0,"percentage"]},null),(n()(),iu(16777216,null,null,1,null,sp)),Qu(36,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,ap)),Qu(38,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component;n(t,2,0,""!==e.clazz.reportPath),n(t,4,0,""===e.clazz.reportPath),n(t,7,0,null!==e.clazz.currentHistoricCoverage),n(t,9,0,null===e.clazz.currentHistoricCoverage),n(t,12,0,null!==e.clazz.currentHistoricCoverage),n(t,14,0,null===e.clazz.currentHistoricCoverage),n(t,17,0,null!==e.clazz.currentHistoricCoverage),n(t,19,0,null===e.clazz.currentHistoricCoverage),n(t,22,0,null!==e.clazz.currentHistoricCoverage),n(t,24,0,null===e.clazz.currentHistoricCoverage),n(t,27,0,e.clazz.lineCoverageHistory.length>1),n(t,29,0,null!==e.clazz.currentHistoricCoverage),n(t,31,0,null===e.clazz.currentHistoricCoverage),n(t,34,0,e.clazz.coverage),n(t,36,0,e.branchCoverageAvailable),n(t,38,0,e.branchCoverageAvailable)},function(n,t){n(t,25,0,t.component.clazz.coverageType)})}var fp=Pl({encapsulation:2,styles:[],data:{}});function hp(n){return bs(0,[(n()(),lu(0,0,null,null,1,null,null,null,null,null,null,null)),(n()(),vs(1,null,["",""]))],null,function(n,t){n(t,1,0,t.component.translations.noGrouping)})}function pp(n){return bs(0,[(n()(),lu(0,0,null,null,1,null,null,null,null,null,null,null)),(n()(),vs(1,null,["",""]))],null,function(n,t){n(t,1,0,t.component.translations.byAssembly)})}function dp(n){return bs(0,[(n()(),lu(0,0,null,null,1,null,null,null,null,null,null,null)),(n()(),vs(1,null,["",""]))],null,function(n,t){var e=t.component;n(t,1,0,e.translations.byNamespace+" "+e.settings.grouping)})}function gp(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,["",""]))],function(n,t){n(t,1,0,t.context.$implicit),n(t,2,0,t.context.$implicit)},function(n,t){n(t,3,0,t.context.$implicit)})}function vp(n){return bs(0,[(n()(),lu(0,0,null,null,0,"br",[],null,null,null,null,null))],null,null)}function yp(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","branchCoverageIncreaseOnly"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,[" "," "]))],function(n,t){n(t,1,0,"branchCoverageIncreaseOnly"),n(t,2,0,"branchCoverageIncreaseOnly")},function(n,t){n(t,3,0,t.component.translations.branchCoverageIncreaseOnly)})}function mp(n){return bs(0,[(n()(),lu(0,0,null,null,3,"option",[["value","branchCoverageDecreaseOnly"]],null,null,null,null,null)),Qu(1,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(2,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(3,null,[" "," "]))],function(n,t){n(t,1,0,"branchCoverageDecreaseOnly"),n(t,2,0,"branchCoverageDecreaseOnly")},function(n,t){n(t,3,0,t.component.translations.branchCoverageDecreaseOnly)})}function bp(n){return bs(0,[(n()(),lu(0,0,null,null,26,"div",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,25,"select",[],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"change"],[null,"blur"]],function(n,t,e){var r=!0,o=n.component;return"change"===t&&(r=!1!==Vu(n,2).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,2).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.historyComparisionType=e)&&r),r},null,null)),Qu(2,16384,null,0,Lf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n){return[n]},[Lf]),Qu(4,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(6,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(7,0,null,null,3,"option",[["value",""]],null,null,null,null,null)),Qu(8,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(9,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(10,null,["",""])),(n()(),lu(11,0,null,null,3,"option",[["value","allChanges"]],null,null,null,null,null)),Qu(12,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(13,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(14,null,["",""])),(n()(),lu(15,0,null,null,3,"option",[["value","lineCoverageIncreaseOnly"]],null,null,null,null,null)),Qu(16,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(17,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(18,null,["",""])),(n()(),lu(19,0,null,null,3,"option",[["value","lineCoverageDecreaseOnly"]],null,null,null,null,null)),Qu(20,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(21,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(22,null,["",""])),(n()(),iu(16777216,null,null,1,null,yp)),Qu(24,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,mp)),Qu(26,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){var e=t.component;n(t,4,0,e.settings.historyComparisionType),n(t,8,0,""),n(t,9,0,""),n(t,12,0,"allChanges"),n(t,13,0,"allChanges"),n(t,16,0,"lineCoverageIncreaseOnly"),n(t,17,0,"lineCoverageIncreaseOnly"),n(t,20,0,"lineCoverageDecreaseOnly"),n(t,21,0,"lineCoverageDecreaseOnly"),n(t,24,0,e.branchCoverageAvailable),n(t,26,0,e.branchCoverageAvailable)},function(n,t){var e=t.component;n(t,1,0,Vu(t,6).ngClassUntouched,Vu(t,6).ngClassTouched,Vu(t,6).ngClassPristine,Vu(t,6).ngClassDirty,Vu(t,6).ngClassValid,Vu(t,6).ngClassInvalid,Vu(t,6).ngClassPending),n(t,10,0,e.translations.filter),n(t,14,0,e.translations.allChanges),n(t,18,0,e.translations.lineCoverageIncreaseOnly),n(t,22,0,e.translations.lineCoverageDecreaseOnly)})}function _p(n){return bs(0,[(n()(),lu(0,0,null,null,18,null,null,null,null,null,null,null)),(n()(),lu(1,0,null,null,13,"div",[],null,null,null,null,null)),(n()(),vs(2,null,[" "," "])),(n()(),lu(3,0,null,null,11,"select",[],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"change"],[null,"blur"]],function(n,t,e){var r=!0,o=n.component;return"change"===t&&(r=!1!==Vu(n,4).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,4).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.historyComparisionDate=e)&&r),"ngModelChange"===t&&(r=!1!==o.updateCurrentHistoricCoverage()&&r),r},null,null)),Qu(4,16384,null,0,Lf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n){return[n]},[Lf]),Qu(6,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(8,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(9,0,null,null,3,"option",[["value",""]],null,null,null,null,null)),Qu(10,147456,null,0,zf,[Qr,Xr,[2,Lf]],{value:[0,"value"]},null),Qu(11,147456,null,0,Uf,[Qr,Xr,[8,null]],{value:[0,"value"]},null),(n()(),vs(12,null,["",""])),(n()(),iu(16777216,null,null,1,null,gp)),Qu(14,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),iu(16777216,null,null,1,null,vp)),Qu(16,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,bp)),Qu(18,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component;n(t,6,0,e.settings.historyComparisionDate),n(t,10,0,""),n(t,11,0,""),n(t,14,0,e.historicCoverageExecutionTimes),n(t,16,0,""!==e.settings.historyComparisionDate),n(t,18,0,""!==e.settings.historyComparisionDate)},function(n,t){var e=t.component;n(t,2,0,e.translations.compareHistory),n(t,3,0,Vu(t,8).ngClassUntouched,Vu(t,8).ngClassTouched,Vu(t,8).ngClassPristine,Vu(t,8).ngClassDirty,Vu(t,8).ngClassValid,Vu(t,8).ngClassInvalid,Vu(t,8).ngClassPending),n(t,12,0,e.translations.date)})}function wp(n){return bs(0,[(n()(),lu(0,0,null,null,0,"col",[["class","column98"]],null,null,null,null,null))],null,null)}function Cp(n){return bs(0,[(n()(),lu(0,0,null,null,0,"col",[["class","column112"]],null,null,null,null,null))],null,null)}function Ep(n){return bs(0,[(n()(),lu(0,0,null,null,5,"th",[["class","center"],["colspan","2"]],null,null,null,null,null)),(n()(),lu(1,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("coverage",e)&&r),r},null,null)),(n()(),lu(2,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(3,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(4,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(5,null,["",""]))],function(n,t){var e=t.component,r=n(t,4,0,"coverage"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"coverage"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"coverage"!==e.settings.sortBy);n(t,3,0,"icon-down-dir",r)},function(n,t){n(t,5,0,t.component.translations.coverage)})}function xp(n){return bs(0,[(n()(),lu(0,0,null,null,5,"th",[["class","center"],["colspan","2"]],null,null,null,null,null)),(n()(),lu(1,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("branchcoverage",e)&&r),r},null,null)),(n()(),lu(2,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(3,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(4,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(5,null,["",""]))],function(n,t){var e=t.component,r=n(t,4,0,"branchcoverage"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"branchcoverage"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"branchcoverage"!==e.settings.sortBy);n(t,3,0,"icon-down-dir",r)},function(n,t){n(t,5,0,t.component.translations.branchCoverage)})}function Op(n){return bs(0,[(n()(),lu(0,0,null,null,1,"tr",[["codeelement-row",""]],null,null,null,zh,Rh)),Qu(1,49152,null,0,Hh,[],{element:[0,"element"],collapsed:[1,"collapsed"],branchCoverageAvailable:[2,"branchCoverageAvailable"]},null)],function(n,t){n(t,1,0,t.parent.context.$implicit,t.parent.context.$implicit.collapsed,t.component.branchCoverageAvailable)},null)}function kp(n){return bs(0,[(n()(),lu(0,0,null,null,1,"tr",[["class-row",""]],null,null,null,cp,Zh)),Qu(1,49152,null,0,Gh,[],{clazz:[0,"clazz"],translations:[1,"translations"],branchCoverageAvailable:[2,"branchCoverageAvailable"],historyComparisionDate:[3,"historyComparisionDate"]},null)],function(n,t){var e=t.component;n(t,1,0,t.parent.context.$implicit,e.translations,e.branchCoverageAvailable,e.settings.historyComparisionDate)},null)}function Sp(n){return bs(0,[(n()(),lu(0,0,null,null,2,null,null,null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,kp)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component,r=!t.parent.context.$implicit.collapsed&&t.context.$implicit.visible(e.settings.filter,e.settings.historyComparisionType);n(t,2,0,r)},null)}function Ap(n){return bs(0,[(n()(),lu(0,0,null,null,1,"tr",[["class","namespace"],["class-row",""]],null,null,null,cp,Zh)),Qu(1,49152,null,0,Gh,[],{clazz:[0,"clazz"],translations:[1,"translations"],branchCoverageAvailable:[2,"branchCoverageAvailable"],historyComparisionDate:[3,"historyComparisionDate"]},null)],function(n,t){var e=t.component;n(t,1,0,t.parent.context.$implicit,e.translations,e.branchCoverageAvailable,e.settings.historyComparisionDate)},null)}function Tp(n){return bs(0,[(n()(),lu(0,0,null,null,2,null,null,null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Ap)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component,r=!t.parent.parent.context.$implicit.collapsed&&t.context.$implicit.visible(e.settings.filter,e.settings.historyComparisionType);n(t,2,0,r)},null)}function Ip(n){return bs(0,[(n()(),lu(0,0,null,null,4,null,null,null,null,null,null,null)),(n()(),lu(1,0,null,null,1,"tr",[["class","namespace"],["codeelement-row",""]],null,null,null,zh,Rh)),Qu(2,49152,null,0,Hh,[],{element:[0,"element"],collapsed:[1,"collapsed"],branchCoverageAvailable:[2,"branchCoverageAvailable"]},null),(n()(),iu(16777216,null,null,1,null,Tp)),Qu(4,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),iu(0,null,null,0))],function(n,t){n(t,2,0,t.parent.context.$implicit,t.parent.context.$implicit.collapsed,t.component.branchCoverageAvailable),n(t,4,0,t.parent.context.$implicit.classes)},null)}function Pp(n){return bs(0,[(n()(),lu(0,0,null,null,2,null,null,null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Ip)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component,r=!t.parent.context.$implicit.collapsed&&t.context.$implicit.visible(e.settings.filter,e.settings.historyComparisionType);n(t,2,0,r)},null)}function Np(n){return bs(0,[(n()(),lu(0,0,null,null,6,null,null,null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Op)),Qu(2,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Sp)),Qu(4,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),iu(16777216,null,null,1,null,Pp)),Qu(6,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null),(n()(),iu(0,null,null,0))],function(n,t){var e=t.component,r=t.context.$implicit.visible(e.settings.filter,e.settings.historyComparisionType);n(t,2,0,r),n(t,4,0,t.context.$implicit.classes),n(t,6,0,t.context.$implicit.subElements)},null)}function Dp(n){return bs(0,[(n()(),lu(0,0,null,null,87,"div",[],null,null,null,null,null)),(n()(),lu(1,0,null,null,34,"div",[["class","customizebox"]],null,null,null,null,null)),(n()(),lu(2,0,null,null,5,"div",[],null,null,null,null,null)),(n()(),lu(3,0,null,null,1,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.collapseAll(e)&&r),r},null,null)),(n()(),vs(4,null,["",""])),(n()(),vs(-1,null,[" | "])),(n()(),lu(6,0,null,null,1,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.expandAll(e)&&r),r},null,null)),(n()(),vs(7,null,["",""])),(n()(),lu(8,0,null,null,15,"div",[["class","center"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,hp)),Qu(10,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,pp)),Qu(12,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,dp)),Qu(14,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(15,0,null,null,0,"br",[],null,null,null,null,null)),(n()(),vs(16,null,[" "," "])),(n()(),lu(17,0,null,null,6,"input",[["min","-1"],["step","1"],["type","range"]],[[8,"max",0],[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"input"],[null,"blur"],[null,"compositionstart"],[null,"compositionend"],[null,"change"]],function(n,t,e){var r=!0,o=n.component;return"input"===t&&(r=!1!==Vu(n,18)._handleInput(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,18).onTouched()&&r),"compositionstart"===t&&(r=!1!==Vu(n,18)._compositionStart()&&r),"compositionend"===t&&(r=!1!==Vu(n,18)._compositionEnd(e.target.value)&&r),"change"===t&&(r=!1!==Vu(n,19).onChange(e.target.value)&&r),"input"===t&&(r=!1!==Vu(n,19).onChange(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,19).onTouched()&&r),"ngModelChange"===t&&(r=!1!==(o.settings.grouping=e)&&r),"ngModelChange"===t&&(r=!1!==o.updateCoverageInfo()&&r),r},null,null)),Qu(18,16384,null,0,Sf,[Xr,Qr,[2,kf]],null,null),Qu(19,16384,null,0,Vf,[Xr,Qr],null,null),Wu(1024,null,xf,function(n,t){return[n,t]},[Sf,Vf]),Qu(21,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(23,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(24,0,null,null,2,"div",[["class","center"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,_p)),Qu(26,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(27,0,null,null,8,"div",[["class","right"]],null,null,null,null,null)),(n()(),lu(28,0,null,null,1,"span",[],null,null,null,null,null)),(n()(),vs(29,null,[""," "])),(n()(),lu(30,0,null,null,5,"input",[["type","text"]],[[2,"ng-untouched",null],[2,"ng-touched",null],[2,"ng-pristine",null],[2,"ng-dirty",null],[2,"ng-valid",null],[2,"ng-invalid",null],[2,"ng-pending",null]],[[null,"ngModelChange"],[null,"input"],[null,"blur"],[null,"compositionstart"],[null,"compositionend"]],function(n,t,e){var r=!0,o=n.component;return"input"===t&&(r=!1!==Vu(n,31)._handleInput(e.target.value)&&r),"blur"===t&&(r=!1!==Vu(n,31).onTouched()&&r),"compositionstart"===t&&(r=!1!==Vu(n,31)._compositionStart()&&r),"compositionend"===t&&(r=!1!==Vu(n,31)._compositionEnd(e.target.value)&&r),"ngModelChange"===t&&(r=!1!==(o.settings.filter=e)&&r),r},null,null)),Qu(31,16384,null,0,Sf,[Xr,Qr,[2,kf]],null,null),Wu(1024,null,xf,function(n){return[n]},[Sf]),Qu(33,671744,null,0,hh,[[8,null],[8,null],[8,null],[6,xf]],{model:[0,"model"]},{update:"ngModelChange"}),Wu(2048,null,Nf,null,[hh]),Qu(35,16384,null,0,Yf,[[4,Nf]],null,null),(n()(),lu(36,0,null,null,51,"table",[["class","overview table-fixed stripped"]],null,null,null,null,null)),(n()(),lu(37,0,null,null,11,"colgroup",[],null,null,null,null,null)),(n()(),lu(38,0,null,null,0,"col",[],null,null,null,null,null)),(n()(),lu(39,0,null,null,0,"col",[["class","column90"]],null,null,null,null,null)),(n()(),lu(40,0,null,null,0,"col",[["class","column105"]],null,null,null,null,null)),(n()(),lu(41,0,null,null,0,"col",[["class","column100"]],null,null,null,null,null)),(n()(),lu(42,0,null,null,0,"col",[["class","column70"]],null,null,null,null,null)),(n()(),lu(43,0,null,null,0,"col",[["class","column98"]],null,null,null,null,null)),(n()(),lu(44,0,null,null,0,"col",[["class","column112"]],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,wp)),Qu(46,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,Cp)),Qu(48,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(49,0,null,null,35,"thead",[],null,null,null,null,null)),(n()(),lu(50,0,null,null,34,"tr",[],null,null,null,null,null)),(n()(),lu(51,0,null,null,5,"th",[],null,null,null,null,null)),(n()(),lu(52,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("name",e)&&r),r},null,null)),(n()(),lu(53,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(54,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(55,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(56,null,["",""])),(n()(),lu(57,0,null,null,5,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(58,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("covered",e)&&r),r},null,null)),(n()(),lu(59,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(60,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(61,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(62,null,["",""])),(n()(),lu(63,0,null,null,5,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(64,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("uncovered",e)&&r),r},null,null)),(n()(),lu(65,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(66,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(67,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(68,null,["",""])),(n()(),lu(69,0,null,null,5,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(70,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("coverable",e)&&r),r},null,null)),(n()(),lu(71,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(72,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(73,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(74,null,["",""])),(n()(),lu(75,0,null,null,5,"th",[["class","right"]],null,null,null,null,null)),(n()(),lu(76,0,null,null,4,"a",[["href","#"]],null,[[null,"click"]],function(n,t,e){var r=!0;return"click"===t&&(r=!1!==n.component.updateSorting("total",e)&&r),r},null,null)),(n()(),lu(77,0,null,null,2,"i",[["class","icon-down-dir"]],null,null,null,null,null)),Qu(78,278528,null,0,Wa,[ol,il,Qr,Xr],{klass:[0,"klass"],ngClass:[1,"ngClass"]},null),gs(79,{"icon-up-dir_active":0,"icon-down-dir_active":1,"icon-down-dir":2}),(n()(),vs(80,null,["",""])),(n()(),iu(16777216,null,null,1,null,Ep)),Qu(82,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),iu(16777216,null,null,1,null,xp)),Qu(84,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null),(n()(),lu(85,0,null,null,2,"tbody",[],null,null,null,null,null)),(n()(),iu(16777216,null,null,1,null,Np)),Qu(87,278528,null,0,Ja,[ji,Ro,ol],{ngForOf:[0,"ngForOf"]},null)],function(n,t){var e=t.component;n(t,10,0,-1===e.settings.grouping),n(t,12,0,0===e.settings.grouping),n(t,14,0,e.settings.grouping>0),n(t,21,0,e.settings.grouping),n(t,26,0,e.historicCoverageExecutionTimes.length>0),n(t,33,0,e.settings.filter),n(t,46,0,e.branchCoverageAvailable),n(t,48,0,e.branchCoverageAvailable);var r=n(t,55,0,"name"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"name"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"name"!==e.settings.sortBy);n(t,54,0,"icon-down-dir",r);var o=n(t,61,0,"covered"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"covered"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"covered"!==e.settings.sortBy);n(t,60,0,"icon-down-dir",o);var i=n(t,67,0,"uncovered"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"uncovered"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"uncovered"!==e.settings.sortBy);n(t,66,0,"icon-down-dir",i);var l=n(t,73,0,"coverable"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"coverable"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"coverable"!==e.settings.sortBy);n(t,72,0,"icon-down-dir",l);var u=n(t,79,0,"total"===e.settings.sortBy&&"desc"===e.settings.sortOrder,"total"===e.settings.sortBy&&"asc"===e.settings.sortOrder,"total"!==e.settings.sortBy);n(t,78,0,"icon-down-dir",u),n(t,82,0,e.branchCoverageAvailable),n(t,84,0,e.branchCoverageAvailable),n(t,87,0,e.codeElements)},function(n,t){var e=t.component;n(t,4,0,e.translations.collapseAll),n(t,7,0,e.translations.expandAll),n(t,16,0,e.translations.grouping),n(t,17,0,e.settings.groupingMaximum,Vu(t,23).ngClassUntouched,Vu(t,23).ngClassTouched,Vu(t,23).ngClassPristine,Vu(t,23).ngClassDirty,Vu(t,23).ngClassValid,Vu(t,23).ngClassInvalid,Vu(t,23).ngClassPending),n(t,29,0,e.translations.filter),n(t,30,0,Vu(t,35).ngClassUntouched,Vu(t,35).ngClassTouched,Vu(t,35).ngClassPristine,Vu(t,35).ngClassDirty,Vu(t,35).ngClassValid,Vu(t,35).ngClassInvalid,Vu(t,35).ngClassPending),n(t,56,0,e.translations.name),n(t,62,0,e.translations.covered),n(t,68,0,e.translations.uncovered),n(t,74,0,e.translations.coverable),n(t,80,0,e.translations.total)})}function Mp(n){return bs(0,[(n()(),iu(16777216,null,null,1,null,Dp)),Qu(1,16384,null,0,Xa,[ji,Ro],{ngIf:[0,"ngIf"]},null)],function(n,t){n(t,1,0,t.component.codeElements.length>0)},null)}function Vp(n){return bs(0,[(n()(),lu(0,0,null,null,1,"coverage-info",[],null,[["window","beforeunload"]],function(n,t,e){var r=!0;return"window:beforeunload"===t&&(r=!1!==Vu(n,1).onDonBeforeUnlodad()&&r),r},Mp,fp)),Qu(1,114688,null,0,ja,[Ta],null,null)],function(n,t){n(t,1,0)},null)}var Hp=xu("coverage-info",ja,Vp,{},{},[]),Rp=ka(Aa,[Na,ja],function(n){return function(n){for(var t={},e=[],r=!1,o=0;odiv { width: 25%; display: inline-block; } -.customizebox div.right input { font-size: 0.8em; width: 150px; } -#namespaceslider { width: 200px; display: inline-block; margin-left: 8px; } - -.percentagebarundefined { - border-left: 2px solid #fff; - padding-left: 3px; -} -.percentagebar0 { - border-left: 2px solid #c10909; - padding-left: 3px; -} -.percentagebar10 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 90%, #0aad0a 90%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar20 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 80%, #0aad0a 80%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar30 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 70%, #0aad0a 70%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar40 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 60%, #0aad0a 60%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar50 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 50%, #0aad0a 50%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar60 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 40%, #0aad0a 40%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar70 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 30%, #0aad0a 30%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar80 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 20%, #0aad0a 20%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar90 { - border-left: 2px solid; - border-image: linear-gradient(to bottom, #c10909 10%, #0aad0a 10%, #0aad0a 100%) 1; - padding-left: 3px; -} -.percentagebar100 { - border-left: 2px solid #0aad0a; - padding-left: 3px; -} - -.hidden, .ng-hide { display: none; } -.right { text-align: right; } -.center { text-align: center; } -.rightmargin { padding-right: 8px; } -.leftmargin { padding-left: 5px; } -.green { background-color: #0aad0a; } -.lightgreen { background-color: #dcf4dc; } -.red { background-color: #c10909; } -.lightred { background-color: #f7dede; } -.orange { background-color: #FFA500; } -.lightorange { background-color: #FFEFD5; } -.gray { background-color: #dcdcdc; } -.lightgray { color: #888888; } -.lightgraybg { background-color: #dadada; } - -.toggleZoom { text-align:right; } - -.ct-chart { position: relative; } -.ct-chart .ct-line { stroke-width: 2px !important; } -.ct-chart .ct-point { stroke-width: 6px !important; transition: stroke-width .2s; } -.ct-chart .ct-point:hover { stroke-width: 10px !important; } -.ct-chart .ct-series.ct-series-a .ct-line, .ct-chart .ct-series.ct-series-a .ct-point { stroke: #c00 !important;} -.ct-chart .ct-series.ct-series-b .ct-line, .ct-chart .ct-series.ct-series-b .ct-point { stroke: #1c2298 !important;} - -.tinylinecoveragechart, .tinybranchcoveragechart { background-color: #fff; margin-left: -3px; float: left; border: solid 1px #c1c1c1; width: 30px; height: 18px; } -.historiccoverageoffset { margin-top: 7px; } - -.tinylinecoveragechart .ct-line, .tinybranchcoveragechart .ct-line { stroke-width: 1px !important; } -.tinybranchcoveragechart .ct-series.ct-series-a .ct-line { stroke: #1c2298 !important; } - -.linecoverage { background-color: #c00; width: 10px; height: 8px; border: 1px solid #000; display: inline-block; } -.branchcoverage { background-color: #1c2298; width: 10px; height: 8px; border: 1px solid #000; display: inline-block; } - -.tooltip { position: absolute; display: none; padding: 5px; background: #F4C63D;color: #453D3F; pointer-events: none; z-index: 1; } -.tooltip:after { content: ""; position: absolute; top: 100%; left: 50%; width: 0; height: 0; margin-left: -15px; border: 15px solid transparent; border-top-color: #F4C63D; } - -.column1324 { max-width: 1324px; } -.column674 { max-width: 674px; } -.column60 { width: 60px; } -.column70 { width: 70px; } -.column90 { width: 90px; } -.column98 { width: 98px; } -.column100 { width: 100px; } -.column105 { width: 105px; } -.column112 { width: 112px; } -.column135 { width: 135px; } -.column150 { width: 150px; } - -.covered0 { width: 0px; } -.covered1 { width: 1px; } -.covered2 { width: 2px; } -.covered3 { width: 3px; } -.covered4 { width: 4px; } -.covered5 { width: 5px; } -.covered6 { width: 6px; } -.covered7 { width: 7px; } -.covered8 { width: 8px; } -.covered9 { width: 9px; } -.covered10 { width: 10px; } -.covered11 { width: 11px; } -.covered12 { width: 12px; } -.covered13 { width: 13px; } -.covered14 { width: 14px; } -.covered15 { width: 15px; } -.covered16 { width: 16px; } -.covered17 { width: 17px; } -.covered18 { width: 18px; } -.covered19 { width: 19px; } -.covered20 { width: 20px; } -.covered21 { width: 21px; } -.covered22 { width: 22px; } -.covered23 { width: 23px; } -.covered24 { width: 24px; } -.covered25 { width: 25px; } -.covered26 { width: 26px; } -.covered27 { width: 27px; } -.covered28 { width: 28px; } -.covered29 { width: 29px; } -.covered30 { width: 30px; } -.covered31 { width: 31px; } -.covered32 { width: 32px; } -.covered33 { width: 33px; } -.covered34 { width: 34px; } -.covered35 { width: 35px; } -.covered36 { width: 36px; } -.covered37 { width: 37px; } -.covered38 { width: 38px; } -.covered39 { width: 39px; } -.covered40 { width: 40px; } -.covered41 { width: 41px; } -.covered42 { width: 42px; } -.covered43 { width: 43px; } -.covered44 { width: 44px; } -.covered45 { width: 45px; } -.covered46 { width: 46px; } -.covered47 { width: 47px; } -.covered48 { width: 48px; } -.covered49 { width: 49px; } -.covered50 { width: 50px; } -.covered51 { width: 51px; } -.covered52 { width: 52px; } -.covered53 { width: 53px; } -.covered54 { width: 54px; } -.covered55 { width: 55px; } -.covered56 { width: 56px; } -.covered57 { width: 57px; } -.covered58 { width: 58px; } -.covered59 { width: 59px; } -.covered60 { width: 60px; } -.covered61 { width: 61px; } -.covered62 { width: 62px; } -.covered63 { width: 63px; } -.covered64 { width: 64px; } -.covered65 { width: 65px; } -.covered66 { width: 66px; } -.covered67 { width: 67px; } -.covered68 { width: 68px; } -.covered69 { width: 69px; } -.covered70 { width: 70px; } -.covered71 { width: 71px; } -.covered72 { width: 72px; } -.covered73 { width: 73px; } -.covered74 { width: 74px; } -.covered75 { width: 75px; } -.covered76 { width: 76px; } -.covered77 { width: 77px; } -.covered78 { width: 78px; } -.covered79 { width: 79px; } -.covered80 { width: 80px; } -.covered81 { width: 81px; } -.covered82 { width: 82px; } -.covered83 { width: 83px; } -.covered84 { width: 84px; } -.covered85 { width: 85px; } -.covered86 { width: 86px; } -.covered87 { width: 87px; } -.covered88 { width: 88px; } -.covered89 { width: 89px; } -.covered90 { width: 90px; } -.covered91 { width: 91px; } -.covered92 { width: 92px; } -.covered93 { width: 93px; } -.covered94 { width: 94px; } -.covered95 { width: 95px; } -.covered96 { width: 96px; } -.covered97 { width: 97px; } -.covered98 { width: 98px; } -.covered99 { width: 99px; } -.covered100 { width: 100px; } - - @media print { - html, body { background-color: #fff; } - .container { max-width: 100%; width: 100%; padding: 0; } - .overview colgroup col:first-child { width: 300px; } -} - -.icon-up-dir_active { - background-image: url(icon_up-dir.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiNjMDAiIGQ9Ik0xNDA4IDEyMTZxMCAyNi0xOSA0NXQtNDUgMTloLTg5NnEtMjYgMC00NS0xOXQtMTktNDUgMTktNDVsNDQ4LTQ0OHExOS0xOSA0NS0xOXQ0NSAxOWw0NDggNDQ4cTE5IDE5IDE5IDQ1eiIvPjwvc3ZnPg==); - background-repeat: no-repeat; - background-size: contain; - padding-left: 15px; - height: 0.9em; - display: inline-block; - position: relative; - top: 3px; -} -.icon-down-dir_active { - background-image: url(icon_up-dir_active.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiNjMDAiIGQ9Ik0xNDA4IDcwNHEwIDI2LTE5IDQ1bC00NDggNDQ4cS0xOSAxOS00NSAxOXQtNDUtMTlsLTQ0OC00NDhxLTE5LTE5LTE5LTQ1dDE5LTQ1IDQ1LTE5aDg5NnEyNiAwIDQ1IDE5dDE5IDQ1eiIvPjwvc3ZnPg==); - background-repeat: no-repeat; - background-size: contain; - padding-left: 15px; - height: 0.9em; - display: inline-block; - position: relative; - top: 3px; -} -.icon-down-dir { - background-image: url(icon_down-dir_active.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNDA4IDcwNHEwIDI2LTE5IDQ1bC00NDggNDQ4cS0xOSAxOS00NSAxOXQtNDUtMTlsLTQ0OC00NDhxLTE5LTE5LTE5LTQ1dDE5LTQ1IDQ1LTE5aDg5NnEyNiAwIDQ1IDE5dDE5IDQ1eiIvPjwvc3ZnPg==); - background-repeat: no-repeat; - background-size: contain; - padding-left: 15px; - height: 0.9em; - display: inline-block; - position: relative; - top: 3px; -} -.icon-info-circled { - background-image: url(icon_info-circled.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxjaXJjbGUgY3g9Ijg5NiIgY3k9Ijg5NiIgcj0iNzUwIiBmaWxsPSIjZmZmIiAvPjxwYXRoIGZpbGw9IiMyOEE1RkYiIGQ9Ik0xMTUyIDEzNzZ2LTE2MHEwLTE0LTktMjN0LTIzLTloLTk2di01MTJxMC0xNC05LTIzdC0yMy05aC0zMjBxLTE0IDAtMjMgOXQtOSAyM3YxNjBxMCAxNCA5IDIzdDIzIDloOTZ2MzIwaC05NnEtMTQgMC0yMyA5dC05IDIzdjE2MHEwIDE0IDkgMjN0MjMgOWg0NDhxMTQgMCAyMy05dDktMjN6bS0xMjgtODk2di0xNjBxMC0xNC05LTIzdC0yMy05aC0xOTJxLTE0IDAtMjMgOXQtOSAyM3YxNjBxMCAxNCA5IDIzdDIzIDloMTkycTE0IDAgMjMtOXQ5LTIzem02NDAgNDE2cTAgMjA5LTEwMyAzODUuNXQtMjc5LjUgMjc5LjUtMzg1LjUgMTAzLTM4NS41LTEwMy0yNzkuNS0yNzkuNS0xMDMtMzg1LjUgMTAzLTM4NS41IDI3OS41LTI3OS41IDM4NS41LTEwMyAzODUuNSAxMDMgMjc5LjUgMjc5LjUgMTAzIDM4NS41eiIvPjwvc3ZnPg==); - background-repeat: no-repeat; - background-size: contain; - padding-left: 15px; - height: 0.9em; - display: inline-block; -} -.icon-plus { - background-image: url(icon_plus.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNjAwIDczNnYxOTJxMCA0MC0yOCA2OHQtNjggMjhoLTQxNnY0MTZxMCA0MC0yOCA2OHQtNjggMjhoLTE5MnEtNDAgMC02OC0yOHQtMjgtNjh2LTQxNmgtNDE2cS00MCAwLTY4LTI4dC0yOC02OHYtMTkycTAtNDAgMjgtNjh0NjgtMjhoNDE2di00MTZxMC00MCAyOC02OHQ2OC0yOGgxOTJxNDAgMCA2OCAyOHQyOCA2OHY0MTZoNDE2cTQwIDAgNjggMjh0MjggNjh6Ii8+PC9zdmc+); - background-repeat: no-repeat; - background-size: contain; - padding-left: 15px; - height: 0.9em; - display: inline-block; - position: relative; - top: 3px; -} -.icon-minus { - background-image: url(icon_minus.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiNjMDAiIGQ9Ik0xNjAwIDczNnYxOTJxMCA0MC0yOCA2OHQtNjggMjhoLTEyMTZxLTQwIDAtNjgtMjh0LTI4LTY4di0xOTJxMC00MCAyOC02OHQ2OC0yOGgxMjE2cTQwIDAgNjggMjh0MjggNjh6Ii8+PC9zdmc+); - background-repeat: no-repeat; - background-size: contain; - padding-left: 15px; - height: 0.9em; - display: inline-block; - position: relative; - top: 3px; -} -.icon-wrench { - background-image: url(icon_wrench.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxNzkyIiBoZWlnaHQ9IjE3OTIiIHN0eWxlPSJmaWxsOiNlNWU1ZTUiIC8+PHBhdGggZD0iTTQ0OCAxNDcycTAtMjYtMTktNDV0LTQ1LTE5LTQ1IDE5LTE5IDQ1IDE5IDQ1IDQ1IDE5IDQ1LTE5IDE5LTQ1em02NDQtNDIwbC02ODIgNjgycS0zNyAzNy05MCAzNy01MiAwLTkxLTM3bC0xMDYtMTA4cS0zOC0zNi0zOC05MCAwLTUzIDM4LTkxbDY4MS02ODFxMzkgOTggMTE0LjUgMTczLjV0MTczLjUgMTE0LjV6bTYzNC00MzVxMCAzOS0yMyAxMDYtNDcgMTM0LTE2NC41IDIxNy41dC0yNTguNSA4My41cS0xODUgMC0zMTYuNS0xMzEuNXQtMTMxLjUtMzE2LjUgMTMxLjUtMzE2LjUgMzE2LjUtMTMxLjVxNTggMCAxMjEuNSAxNi41dDEwNy41IDQ2LjVxMTYgMTEgMTYgMjh0LTE2IDI4bC0yOTMgMTY5djIyNGwxOTMgMTA3cTUtMyA3OS00OC41dDEzNS41LTgxIDcwLjUtMzUuNXExNSAwIDIzLjUgMTB0OC41IDI1eiIvPjwvc3ZnPg==); - background-repeat: no-repeat; - background-size: contain; - padding-left: 20px; - height: 0.9em; - display: inline-block; -} -.icon-fork { - background-image: url(icon_fork.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxNzkyIiBoZWlnaHQ9IjE3OTIiIHN0eWxlPSJmaWxsOiNmZmYiIC8+PHBhdGggZD0iTTY3MiAxNDcycTAtNDAtMjgtNjh0LTY4LTI4LTY4IDI4LTI4IDY4IDI4IDY4IDY4IDI4IDY4LTI4IDI4LTY4em0wLTExNTJxMC00MC0yOC02OHQtNjgtMjgtNjggMjgtMjggNjggMjggNjggNjggMjggNjgtMjggMjgtNjh6bTY0MCAxMjhxMC00MC0yOC02OHQtNjgtMjgtNjggMjgtMjggNjggMjggNjggNjggMjggNjgtMjggMjgtNjh6bTk2IDBxMCA1Mi0yNiA5Ni41dC03MCA2OS41cS0yIDI4Ny0yMjYgNDE0LTY3IDM4LTIwMyA4MS0xMjggNDAtMTY5LjUgNzF0LTQxLjUgMTAwdjI2cTQ0IDI1IDcwIDY5LjV0MjYgOTYuNXEwIDgwLTU2IDEzNnQtMTM2IDU2LTEzNi01Ni01Ni0xMzZxMC01MiAyNi05Ni41dDcwLTY5LjV2LTgyMHEtNDQtMjUtNzAtNjkuNXQtMjYtOTYuNXEwLTgwIDU2LTEzNnQxMzYtNTYgMTM2IDU2IDU2IDEzNnEwIDUyLTI2IDk2LjV0LTcwIDY5LjV2NDk3cTU0LTI2IDE1NC01NyA1NS0xNyA4Ny41LTI5LjV0NzAuNS0zMSA1OS0zOS41IDQwLjUtNTEgMjgtNjkuNSA4LjUtOTEuNXEtNDQtMjUtNzAtNjkuNXQtMjYtOTYuNXEwLTgwIDU2LTEzNnQxMzYtNTYgMTM2IDU2IDU2IDEzNnoiLz48L3N2Zz4=); - background-repeat: no-repeat; - background-size: contain; - padding-left: 20px; - height: 0.9em; - display: inline-block; -} -.icon-cube { - background-image: url(icon_cube.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxNzkyIiBoZWlnaHQ9IjE3OTIiIHN0eWxlPSJmaWxsOiNlNWU1ZTUiIC8+PHBhdGggZD0iTTg5NiAxNjI5bDY0MC0zNDl2LTYzNmwtNjQwIDIzM3Y3NTJ6bS02NC04NjVsNjk4LTI1NC02OTgtMjU0LTY5OCAyNTR6bTgzMi0yNTJ2NzY4cTAgMzUtMTggNjV0LTQ5IDQ3bC03MDQgMzg0cS0yOCAxNi02MSAxNnQtNjEtMTZsLTcwNC0zODRxLTMxLTE3LTQ5LTQ3dC0xOC02NXYtNzY4cTAtNDAgMjMtNzN0NjEtNDdsNzA0LTI1NnEyMi04IDQ0LTh0NDQgOGw3MDQgMjU2cTM4IDE0IDYxIDQ3dDIzIDczeiIvPjwvc3ZnPg==); - background-repeat: no-repeat; - background-size: contain; - padding-left: 20px; - height: 0.9em; - display: inline-block; -} -.icon-search-plus { - background-image: url(icon_search-plus.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiM2ZjZmNmYiIGQ9Ik0xMDg4IDgwMHY2NHEwIDEzLTkuNSAyMi41dC0yMi41IDkuNWgtMjI0djIyNHEwIDEzLTkuNSAyMi41dC0yMi41IDkuNWgtNjRxLTEzIDAtMjIuNS05LjV0LTkuNS0yMi41di0yMjRoLTIyNHEtMTMgMC0yMi41LTkuNXQtOS41LTIyLjV2LTY0cTAtMTMgOS41LTIyLjV0MjIuNS05LjVoMjI0di0yMjRxMC0xMyA5LjUtMjIuNXQyMi41LTkuNWg2NHExMyAwIDIyLjUgOS41dDkuNSAyMi41djIyNGgyMjRxMTMgMCAyMi41IDkuNXQ5LjUgMjIuNXptMTI4IDMycTAtMTg1LTEzMS41LTMxNi41dC0zMTYuNS0xMzEuNS0zMTYuNSAxMzEuNS0xMzEuNSAzMTYuNSAxMzEuNSAzMTYuNSAzMTYuNSAxMzEuNSAzMTYuNS0xMzEuNSAxMzEuNS0zMTYuNXptNTEyIDgzMnEwIDUzLTM3LjUgOTAuNXQtOTAuNSAzNy41cS01NCAwLTkwLTM4bC0zNDMtMzQycS0xNzkgMTI0LTM5OSAxMjQtMTQzIDAtMjczLjUtNTUuNXQtMjI1LTE1MC0xNTAtMjI1LTU1LjUtMjczLjUgNTUuNS0yNzMuNSAxNTAtMjI1IDIyNS0xNTAgMjczLjUtNTUuNSAyNzMuNSA1NS41IDIyNSAxNTAgMTUwIDIyNSA1NS41IDI3My41cTAgMjIwLTEyNCAzOTlsMzQzIDM0M3EzNyAzNyAzNyA5MHoiLz48L3N2Zz4=); - background-repeat: no-repeat; - background-size: contain; - padding-left: 20px; - height: 0.9em; - display: inline-block; -} -.icon-search-minus { - background-image: url(icon_search-minus.svg), url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGZpbGw9IiM2ZjZmNmYiIGQ9Ik0xMDg4IDgwMHY2NHEwIDEzLTkuNSAyMi41dC0yMi41IDkuNWgtNTc2cS0xMyAwLTIyLjUtOS41dC05LjUtMjIuNXYtNjRxMC0xMyA5LjUtMjIuNXQyMi41LTkuNWg1NzZxMTMgMCAyMi41IDkuNXQ5LjUgMjIuNXptMTI4IDMycTAtMTg1LTEzMS41LTMxNi41dC0zMTYuNS0xMzEuNS0zMTYuNSAxMzEuNS0xMzEuNSAzMTYuNSAxMzEuNSAzMTYuNSAzMTYuNSAxMzEuNSAzMTYuNS0xMzEuNSAxMzEuNS0zMTYuNXptNTEyIDgzMnEwIDUzLTM3LjUgOTAuNXQtOTAuNSAzNy41cS01NCAwLTkwLTM4bC0zNDMtMzQycS0xNzkgMTI0LTM5OSAxMjQtMTQzIDAtMjczLjUtNTUuNXQtMjI1LTE1MC0xNTAtMjI1LTU1LjUtMjczLjUgNTUuNS0yNzMuNSAxNTAtMjI1IDIyNS0xNTAgMjczLjUtNTUuNSAyNzMuNSA1NS41IDIyNSAxNTAgMTUwIDIyNSA1NS41IDI3My41cTAgMjIwLTEyNCAzOTlsMzQzIDM0M3EzNyAzNyAzNyA5MHoiLz48L3N2Zz4=); - background-repeat: no-repeat; - background-size: contain; - padding-left: 20px; - height: 0.9em; - display: inline-block; -} - -.ct-double-octave:after,.ct-major-eleventh:after,.ct-major-second:after,.ct-major-seventh:after,.ct-major-sixth:after,.ct-major-tenth:after,.ct-major-third:after,.ct-major-twelfth:after,.ct-minor-second:after,.ct-minor-seventh:after,.ct-minor-sixth:after,.ct-minor-third:after,.ct-octave:after,.ct-perfect-fifth:after,.ct-perfect-fourth:after,.ct-square:after{content:"";clear:both}.ct-label{fill:rgba(0,0,0,.4);color:rgba(0,0,0,.4);font-size:.75rem;line-height:1}.ct-grid-background,.ct-line{fill:none}.ct-chart-bar .ct-label,.ct-chart-line .ct-label{display:block;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}.ct-chart-donut .ct-label,.ct-chart-pie .ct-label{dominant-baseline:central}.ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label.ct-vertical.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-end;-webkit-justify-content:flex-end;-ms-flex-pack:flex-end;justify-content:flex-end;text-align:right;text-anchor:end}.ct-label.ct-vertical.ct-end{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar .ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;text-anchor:start}.ct-chart-bar .ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-start{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:flex-end;-webkit-justify-content:flex-end;-ms-flex-pack:flex-end;justify-content:flex-end;text-align:right;text-anchor:end}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-end{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:end}.ct-grid{stroke:rgba(0,0,0,.2);stroke-width:1px;stroke-dasharray:2px}.ct-point{stroke-width:10px;stroke-linecap:round}.ct-line{stroke-width:4px}.ct-area{stroke:none;fill-opacity:.1}.ct-bar{fill:none;stroke-width:10px}.ct-slice-donut{fill:none;stroke-width:60px}.ct-series-a .ct-bar,.ct-series-a .ct-line,.ct-series-a .ct-point,.ct-series-a .ct-slice-donut{stroke:#d70206}.ct-series-a .ct-area,.ct-series-a .ct-slice-donut-solid,.ct-series-a .ct-slice-pie{fill:#d70206}.ct-series-b .ct-bar,.ct-series-b .ct-line,.ct-series-b .ct-point,.ct-series-b .ct-slice-donut{stroke:#f05b4f}.ct-series-b .ct-area,.ct-series-b .ct-slice-donut-solid,.ct-series-b .ct-slice-pie{fill:#f05b4f}.ct-series-c .ct-bar,.ct-series-c .ct-line,.ct-series-c .ct-point,.ct-series-c .ct-slice-donut{stroke:#f4c63d}.ct-series-c .ct-area,.ct-series-c .ct-slice-donut-solid,.ct-series-c .ct-slice-pie{fill:#f4c63d}.ct-series-d .ct-bar,.ct-series-d .ct-line,.ct-series-d .ct-point,.ct-series-d .ct-slice-donut{stroke:#d17905}.ct-series-d .ct-area,.ct-series-d .ct-slice-donut-solid,.ct-series-d .ct-slice-pie{fill:#d17905}.ct-series-e .ct-bar,.ct-series-e .ct-line,.ct-series-e .ct-point,.ct-series-e .ct-slice-donut{stroke:#453d3f}.ct-series-e .ct-area,.ct-series-e .ct-slice-donut-solid,.ct-series-e .ct-slice-pie{fill:#453d3f}.ct-series-f .ct-bar,.ct-series-f .ct-line,.ct-series-f .ct-point,.ct-series-f .ct-slice-donut{stroke:#59922b}.ct-series-f .ct-area,.ct-series-f .ct-slice-donut-solid,.ct-series-f .ct-slice-pie{fill:#59922b}.ct-series-g .ct-bar,.ct-series-g .ct-line,.ct-series-g .ct-point,.ct-series-g .ct-slice-donut{stroke:#0544d3}.ct-series-g .ct-area,.ct-series-g .ct-slice-donut-solid,.ct-series-g .ct-slice-pie{fill:#0544d3}.ct-series-h .ct-bar,.ct-series-h .ct-line,.ct-series-h .ct-point,.ct-series-h .ct-slice-donut{stroke:#6b0392}.ct-series-h .ct-area,.ct-series-h .ct-slice-donut-solid,.ct-series-h .ct-slice-pie{fill:#6b0392}.ct-series-i .ct-bar,.ct-series-i .ct-line,.ct-series-i .ct-point,.ct-series-i .ct-slice-donut{stroke:#f05b4f}.ct-series-i .ct-area,.ct-series-i .ct-slice-donut-solid,.ct-series-i .ct-slice-pie{fill:#f05b4f}.ct-series-j .ct-bar,.ct-series-j .ct-line,.ct-series-j .ct-point,.ct-series-j .ct-slice-donut{stroke:#dda458}.ct-series-j .ct-area,.ct-series-j .ct-slice-donut-solid,.ct-series-j .ct-slice-pie{fill:#dda458}.ct-series-k .ct-bar,.ct-series-k .ct-line,.ct-series-k .ct-point,.ct-series-k .ct-slice-donut{stroke:#eacf7d}.ct-series-k .ct-area,.ct-series-k .ct-slice-donut-solid,.ct-series-k .ct-slice-pie{fill:#eacf7d}.ct-series-l .ct-bar,.ct-series-l .ct-line,.ct-series-l .ct-point,.ct-series-l .ct-slice-donut{stroke:#86797d}.ct-series-l .ct-area,.ct-series-l .ct-slice-donut-solid,.ct-series-l .ct-slice-pie{fill:#86797d}.ct-series-m .ct-bar,.ct-series-m .ct-line,.ct-series-m .ct-point,.ct-series-m .ct-slice-donut{stroke:#b2c326}.ct-series-m .ct-area,.ct-series-m .ct-slice-donut-solid,.ct-series-m .ct-slice-pie{fill:#b2c326}.ct-series-n .ct-bar,.ct-series-n .ct-line,.ct-series-n .ct-point,.ct-series-n .ct-slice-donut{stroke:#6188e2}.ct-series-n .ct-area,.ct-series-n .ct-slice-donut-solid,.ct-series-n .ct-slice-pie{fill:#6188e2}.ct-series-o .ct-bar,.ct-series-o .ct-line,.ct-series-o .ct-point,.ct-series-o .ct-slice-donut{stroke:#a748ca}.ct-series-o .ct-area,.ct-series-o .ct-slice-donut-solid,.ct-series-o .ct-slice-pie{fill:#a748ca}.ct-square{display:block;position:relative;width:100%}.ct-square:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:100%}.ct-square:after{display:table}.ct-square>svg{display:block;position:absolute;top:0;left:0}.ct-minor-second{display:block;position:relative;width:100%}.ct-minor-second:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:93.75%}.ct-minor-second:after{display:table}.ct-minor-second>svg{display:block;position:absolute;top:0;left:0}.ct-major-second{display:block;position:relative;width:100%}.ct-major-second:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:88.8888888889%}.ct-major-second:after{display:table}.ct-major-second>svg{display:block;position:absolute;top:0;left:0}.ct-minor-third{display:block;position:relative;width:100%}.ct-minor-third:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:83.3333333333%}.ct-minor-third:after{display:table}.ct-minor-third>svg{display:block;position:absolute;top:0;left:0}.ct-major-third{display:block;position:relative;width:100%}.ct-major-third:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:80%}.ct-major-third:after{display:table}.ct-major-third>svg{display:block;position:absolute;top:0;left:0}.ct-perfect-fourth{display:block;position:relative;width:100%}.ct-perfect-fourth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:75%}.ct-perfect-fourth:after{display:table}.ct-perfect-fourth>svg{display:block;position:absolute;top:0;left:0}.ct-perfect-fifth{display:block;position:relative;width:100%}.ct-perfect-fifth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:66.6666666667%}.ct-perfect-fifth:after{display:table}.ct-perfect-fifth>svg{display:block;position:absolute;top:0;left:0}.ct-minor-sixth{display:block;position:relative;width:100%}.ct-minor-sixth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:62.5%}.ct-minor-sixth:after{display:table}.ct-minor-sixth>svg{display:block;position:absolute;top:0;left:0}.ct-golden-section{display:block;position:relative;width:100%}.ct-golden-section:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:61.804697157%}.ct-golden-section:after{content:"";display:table;clear:both}.ct-golden-section>svg{display:block;position:absolute;top:0;left:0}.ct-major-sixth{display:block;position:relative;width:100%}.ct-major-sixth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:60%}.ct-major-sixth:after{display:table}.ct-major-sixth>svg{display:block;position:absolute;top:0;left:0}.ct-minor-seventh{display:block;position:relative;width:100%}.ct-minor-seventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:56.25%}.ct-minor-seventh:after{display:table}.ct-minor-seventh>svg{display:block;position:absolute;top:0;left:0}.ct-major-seventh{display:block;position:relative;width:100%}.ct-major-seventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:53.3333333333%}.ct-major-seventh:after{display:table}.ct-major-seventh>svg{display:block;position:absolute;top:0;left:0}.ct-octave{display:block;position:relative;width:100%}.ct-octave:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:50%}.ct-octave:after{display:table}.ct-octave>svg{display:block;position:absolute;top:0;left:0}.ct-major-tenth{display:block;position:relative;width:100%}.ct-major-tenth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:40%}.ct-major-tenth:after{display:table}.ct-major-tenth>svg{display:block;position:absolute;top:0;left:0}.ct-major-eleventh{display:block;position:relative;width:100%}.ct-major-eleventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:37.5%}.ct-major-eleventh:after{display:table}.ct-major-eleventh>svg{display:block;position:absolute;top:0;left:0}.ct-major-twelfth{display:block;position:relative;width:100%}.ct-major-twelfth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:33.3333333333%}.ct-major-twelfth:after{display:table}.ct-major-twelfth>svg{display:block;position:absolute;top:0;left:0}.ct-double-octave{display:block;position:relative;width:100%}.ct-double-octave:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:25%}.ct-double-octave:after{display:table}.ct-double-octave>svg{display:block;position:absolute;top:0;left:0} \ No newline at end of file From 801bea6e0407825602c8db4dd8fed135883d2b1e Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 7 Apr 2021 14:24:22 +0800 Subject: [PATCH 042/424] add miss file --- .../ConsoleAppNetFx48/Worker.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs b/Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs new file mode 100644 index 00000000..e8cf870b --- /dev/null +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace ConsoleAppNetFx48 +{ + public class Worker : BackgroundService + { + private readonly ILogger _logger; + + public Worker(ILogger logger) + { + this._logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + this._logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); + await Task.Delay(1000, stoppingToken); + } + } + } +} \ No newline at end of file From 4fd70b0467719569ae12d3ca179ff4321a4e22fd Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 7 Apr 2021 18:18:54 +0800 Subject: [PATCH 043/424] add batch file --- .../CallSafeCreateService.bat | 15 +++ .../ConsoleAppNetFx48.csproj | 18 +++ .../ConsoleAppNetFx48/SafeCreateService.bat | 117 ++++++++++++++++++ .../ConsoleAppNetFx48/SafeDeleteService.bat | 78 ++++++++++++ .../ConsoleAppNetFx48/SafeStartService.bat | 66 ++++++++++ .../ConsoleAppNetFx48/SafeStopService.bat | 65 ++++++++++ .../ConsoleAppNetFx48/Worker.cs | 1 + 7 files changed, 360 insertions(+) create mode 100644 Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat create mode 100644 Host/Lab.WorkerService/ConsoleAppNetFx48/SafeCreateService.bat create mode 100644 Host/Lab.WorkerService/ConsoleAppNetFx48/SafeDeleteService.bat create mode 100644 Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStartService.bat create mode 100644 Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStopService.bat diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat b/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat new file mode 100644 index 00000000..2db2e9fe --- /dev/null +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat @@ -0,0 +1,15 @@ +@echo off +set batchFolder=%~dp0 +set serviceName=ConsoleAppNetFx48 +set serviceDisplayName=ConsoleAppNetFx48 +set serviceDescription="測試" +set serviceLaunchPath=%batchFolder%ConsoleAppNetFx48.exe +set serviceLogonId=.\setup +set serviceLogonPassword=pass@w0rd1~ +::set serverName=\\YAO-S658RF +set serverName= +Call SafeStopService %serviceName% %serverName% +Call SafeDeleteService %serviceName% %serverName% +Call SafeCreateService %serviceName% %serviceDisplayName% %serviceDescription% %serviceLaunchPath% %serviceLogonId% %serviceLogonPassword% %serverName% +::Call SafeStartService %serviceName% %serverName% + diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj b/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj index 1a8ca194..bf0e87b7 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj @@ -14,4 +14,22 @@ + + + + Always + + + Always + + + Always + + + Always + + + Always + + diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeCreateService.bat b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeCreateService.bat new file mode 100644 index 00000000..b907a7f6 --- /dev/null +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeCreateService.bat @@ -0,0 +1,117 @@ +@echo off + +IF [%1]==[] GOTO usage +IF [%2]==[] GOTO usage +IF [%3]==[] GOTO usage +IF [%4]==[] GOTO usage +IF [%5]==[] GOTO usage + +IF NOT "%1"=="" SET serviceName=%1 +IF NOT "%2"=="" SET serviceDisplayName=%2 +IF NOT "%3"=="" SET serviceDescription=%3 +IF NOT "%4"=="" SET serviceLaunchPath=%4 +IF NOT "%5"=="" SET serviceLogonId=%5 +IF NOT "%6"=="" SET serviceLogonPassword=%6 +IF NOT "%7"=="" SET serverName=%7 + +SC %serverName% query %serviceName% + +IF errorlevel 1060 GOTO ServiceNotFound +IF errorlevel 1722 GOTO SystemOffline +IF errorlevel 1001 GOTO DeletingServiceDelay + +:ResolveInitialState +SC %serverName% query %serviceName% | FIND "STATE" | FIND "RUNNING" + +IF errorlevel 0 IF NOT errorlevel 1 GOTO StopService + +SC %serverName% query %serviceName% | FIND "STATE" | FIND "STOPPED" + +IF errorlevel 0 IF NOT errorlevel 1 GOTO StoppedService + +SC %serverName% query %serviceName% | FIND "STATE" | FIND "PAUSED" + +IF errorlevel 0 IF NOT errorlevel 1 GOTO SystemOffline + +echo Service State is changing, waiting for service to resolve its state before making changes + +sc %serverName% query %serviceName% | Find "STATE" +ping -n 2 127.0.0.1 > NUL +GOTO ResolveInitialState + +:StopService +echo Stopping %serviceName% on %serverName% +sc %serverName% stop %serviceName% +GOTO StoppingService + +:StoppingServiceDelay +echo Waiting for %serviceName% to stop +ping -n 2 127.0.0.1 > NUL + +:StoppingService +SC %serverName% query %serviceName% | FIND "STATE" | FIND "STOPPED" +IF errorlevel 1 GOTO StoppingServiceDelay + +:StoppedService +echo %serviceName% on %serverName% is stopped +GOTO DeleteService + +:DeleteService +echo Deleting %serviceName% on %serverName% +SC %serverName% delete %serviceName% + +:DeletingServiceDelay +echo Waiting for %serviceName% to get deleted +ping -n 2 127.0.0.1 > NUL + +:DeletingService +SC %serverName% query %serviceName% +IF NOT errorlevel 1060 GOTO DeletingServiceDelay + +:DeletedService +echo %serviceName% on %serverName% is deleted +GOTO CreateService + +:SystemOffline +echo Server %serverName% is not accessible or is offline +GOTO End + +:ServiceNotFound +echo Service %serviceName% is not installed on Server %serverName% +GOTO CreateService + +:CreateService +echo Creating %serviceName% on %serverName% +::SC %serverName% create %serviceName% binpath= "%serviceLaunchPath%" displayname= "THS MSMQ %serviceDisplayName% Agent" +SC %serverName% create %serviceName% binpath= "%serviceLaunchPath%" +SC %serverName% config %serviceName% displayname= "%serviceDisplayName%" +SC %serverName% config %serviceName% obj= %serviceLogonId% password= "%serviceLogonPassword%" +SC %serverName% config %serviceName% start= auto +SC %serverName% description %serviceName% "%serviceDescription%" +::SC "%serverName%" config "%serviceName%" type= share start= auto + +:CreatingServiceDelay +echo Waiting for %serviceName% to get created +ping -n 2 127.0.0.1 > NUL + +:CreatingService +::SC %serverName% query %serviceName% >NUL +SC %serverName% query %serviceName% | FIND "STATE" | FIND "STOPPED" +IF errorlevel 1 GOTO CreatingServiceDelay + +:CreatedService +echo %serviceName% on %serverName% is created +GOTO End + +:usage +echo Will cause a local/remote service to START (if not already started). +echo This script will waiting for the service to enter the started state if necessary. +echo. +echo %0 [service name] [system name] +echo Example: %0 MyService server1 +echo Example: %0 MyService (for local PC) +echo. + +::GOTO:eof +:End +::pause \ No newline at end of file diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeDeleteService.bat b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeDeleteService.bat new file mode 100644 index 00000000..4c3057a5 --- /dev/null +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeDeleteService.bat @@ -0,0 +1,78 @@ +@echo off + +IF [%1]==[] GOTO usage +IF NOT "%1"=="" SET serviceName=%1 +IF NOT "%2"=="" SET serverName=%2 + +SC %serverName% query %serviceName% +IF errorlevel 1060 GOTO ServiceNotFound +IF errorlevel 1722 GOTO SystemOffline +IF errorlevel 1001 GOTO DeletingServiceDelay + +:ResolveInitialState +SC %serverName% query %serviceName% | FIND "STATE" | FIND "RUNNING" +IF errorlevel 0 IF NOT errorlevel 1 GOTO StopService + +SC %serverName% query %serviceName% | FIND "STATE" | FIND "STOPPED" +IF errorlevel 0 IF NOT errorlevel 1 GOTO StoppedService + +SC %serverName% query %serviceName% | FIND "STATE" | FIND "PAUSED" +IF errorlevel 0 IF NOT errorlevel 1 GOTO SystemOffline +echo Service State is changing, waiting for service to resolve its state before making changes + +SC %serverName% query %serviceName% | Find "STATE" +ping -n 2 127.0.0.1 > NUL +GOTO ResolveInitialState + +:StopService +echo Stopping %serviceName% on %serverName% +SC %serverName% stop %serviceName% + +GOTO StoppingService +:StoppingServiceDelay +echo Waiting for %serviceName% to stop +ping -n 2 127.0.0.1 > NUL + +:StoppingService +SC %serverName% query %serviceName% | FIND "STATE" | FIND "STOPPED" +IF errorlevel 1 GOTO StoppingServiceDelay + +:StoppedService +echo %serviceName% on %serverName% is stopped +GOTO DeleteService + +:DeleteService +SC %serverName% delete %serviceName% + +:DeletingServiceDelay +echo Waiting for %serviceName% to get deleted +ping -n 2 127.0.0.1 > NUL + +:DeletingService +SC %serverName% query %serviceName% +IF NOT errorlevel 1060 GOTO DeletingServiceDelay + +:DeletedService +echo %serviceName% on %serverName% is deleted +GOTO End + +:SystemOffline +echo Server %serverName% is not accessible or is offline +GOTO End + +:ServiceNotFound +echo Service %serviceName% is not installed on Server %serverName% +::exit /b 0 +GOTO End + +:usage +echo Will cause a local/remote service to START (if not already started). +echo This script will waiting for the service to enter the started state if necessary. +echo. +echo %0 [service name] [system name] +echo Example: %0 MyService server1 +echo Example: %0 MyService (for local PC) +echo. + +:End +::pause \ No newline at end of file diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStartService.bat b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStartService.bat new file mode 100644 index 00000000..90f24eae --- /dev/null +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStartService.bat @@ -0,0 +1,66 @@ +@echo off + +IF [%1]==[] GOTO usage +IF NOT "%1"=="" SET serviceName=%1 +IF NOT "%2"=="" SET serverName=%2 + +SC %serverName% query %serviceName% +IF errorlevel 1060 GOTO ServiceNotFound +IF errorlevel 1722 GOTO SystemOffline + +:ResolveInitialState + +SC %serverName% query %serviceName% | FIND "STATE" | FIND "STOPPED" +IF errorlevel 0 IF NOT errorlevel 1 GOTO StartService + +SC %serverName% query %serviceName% | FIND "STATE" | FIND "RUNNING" +IF errorlevel 0 IF NOT errorlevel 1 GOTO StartedService + +SC %serverName% query %serviceName% | FIND "STATE" | FIND "PAUSED" +IF errorlevel 0 IF NOT errorlevel 1 GOTO SystemOffline +echo Service State is changing, waiting for service to resolve its state before making changes + +SC %serverName% query %serviceName% | Find "STATE" >NUL +ping -n 2 127.0.0.1 > NUL + +GOTO ResolveInitialState + +:StartService +echo Starting %serviceName% on %serverName% +SC %serverName% start %serviceName% + +GOTO StartingService + +:StartingServiceDelay +echo Waiting for %serviceName% to start +ping -n 2 127.0.0.1 > NUL + +:StartingService +SC %serverName% query %serviceName% | FIND "STATE" | FIND "RUNNING" +IF errorlevel 1 GOTO StartingServiceDelay + +:StartedService +echo %serviceName% on %serverName% is started +GOTO End + +:SystemOffline +echo Server %serverName% is not accessible or is offline +GOTO End + +:ServiceNotFound +echo Service %serviceName% is not installed on Server %serverName% +::exit /b 0 +GOTO End + +:usage +echo Will cause a local/remote service to START (if not already started). +echo This script will waiting for the service to enter the started state if necessary. +echo. +echo %0 [service name] [system name] +echo Example: %0 MyService server1 +echo Example: %0 MyService (for local PC) +echo. + +::GOTO:eof +:End +::pause \ No newline at end of file diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStopService.bat b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStopService.bat new file mode 100644 index 00000000..7bdadfc4 --- /dev/null +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStopService.bat @@ -0,0 +1,65 @@ +@echo off + +IF [%1]==[] GOTO usage +IF NOT "%1"=="" SET serviceName=%1 +IF NOT "%2"=="" SET serverName=%2 + +SC %serverName% query %serviceName% +IF errorlevel 1060 GOTO ServiceNotFound +IF errorlevel 1722 GOTO SystemOffline + +:ResolveInitialState +SC %serverName% query %serviceName% | FIND "STATE" | FIND "RUNNING" +IF errorlevel 0 IF NOT errorlevel 1 GOTO StopService + +SC %serverName% query %serviceName% | FIND "STATE" | FIND "STOPPED" +IF errorlevel 0 IF NOT errorlevel 1 GOTO StoppedService + +SC %serverName% query %serviceName% | FIND "STATE" | FIND "PAUSED" +IF errorlevel 0 IF NOT errorlevel 1 GOTO SystemOffline +echo Service State is changing, waiting for service to resolve its state before making changes + +SC %serverName% query %serviceName% | Find "STATE" +ping -n 2 127.0.0.1 > NUL +GOTO ResolveInitialState + +:StopService +echo Stopping %serviceName% on %serverName% +SC %serverName% stop %serviceName% +GOTO StoppingService + +:StoppingServiceDelay +echo Waiting for %serviceName% to stop +ping -n 2 127.0.0.1 > NUL + +:StoppingService +SC %serverName% query %serviceName% | FIND "STATE" | FIND "STOPPED" +IF errorlevel 1 GOTO StoppingServiceDelay + +:StoppedService +echo %serviceName% on %serverName% is stopped +GOTO End + +:SystemOffline +echo Server %serverName% is not accessible or is offline +GOTO End + +:ServiceNotFound +echo Service %serviceName% is not installed on Server %serverName% +::exit /b 0 +GOTO End + +:usage +echo Will cause a local/remote service to STOP (if not already stopped). +echo This script will waiting for the service to enter the stopped state if necessary. +echo. +echo %0 [service name] [system name] {reason} +echo Example: %0 MyService server1 {reason} +echo Example: %0 MyService (for local PC, DO NOT specify reason) +echo. +echo For reason codes, run "sc stop" + + +::GOTO:eof +:End +::pause \ No newline at end of file diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs b/Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs index e8cf870b..e2c4435f 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; From ea8c40bd40e61feef066b833e4722504f8b36161 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 7 Apr 2021 18:37:20 +0800 Subject: [PATCH 044/424] add batch file --- .../ConsoleAppNetFx48/CallSafeCreateService.bat | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat b/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat index 2db2e9fe..5730ad38 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat @@ -2,12 +2,12 @@ set batchFolder=%~dp0 set serviceName=ConsoleAppNetFx48 set serviceDisplayName=ConsoleAppNetFx48 -set serviceDescription="測試" +set serviceDescription="" set serviceLaunchPath=%batchFolder%ConsoleAppNetFx48.exe set serviceLogonId=.\setup -set serviceLogonPassword=pass@w0rd1~ +set serviceLogonPassword=password ::set serverName=\\YAO-S658RF -set serverName= +set serverName="" Call SafeStopService %serviceName% %serverName% Call SafeDeleteService %serviceName% %serverName% Call SafeCreateService %serviceName% %serviceDisplayName% %serviceDescription% %serviceLaunchPath% %serviceLogonId% %serviceLogonPassword% %serverName% From aba89a366f9b24a4eb44c7b01c47c2c687edd290 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 7 Apr 2021 18:40:32 +0800 Subject: [PATCH 045/424] add batch file --- .../ConsoleAppNetFx48/CallSafeCreateService.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat b/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat index 5730ad38..e4a37b2c 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat @@ -7,7 +7,7 @@ set serviceLaunchPath=%batchFolder%ConsoleAppNetFx48.exe set serviceLogonId=.\setup set serviceLogonPassword=password ::set serverName=\\YAO-S658RF -set serverName="" +set serverName= Call SafeStopService %serviceName% %serverName% Call SafeDeleteService %serviceName% %serverName% Call SafeCreateService %serviceName% %serviceDisplayName% %serviceDescription% %serviceLaunchPath% %serviceLogonId% %serviceLogonPassword% %serverName% From 18be3d01d1479686c3b4696775190fef992aabc7 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 7 Apr 2021 18:48:52 +0800 Subject: [PATCH 046/424] add batch file --- .../ConsoleAppNetFx48/CallSafeCreateService.bat | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat b/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat index e4a37b2c..5d5b9686 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat @@ -3,13 +3,13 @@ set batchFolder=%~dp0 set serviceName=ConsoleAppNetFx48 set serviceDisplayName=ConsoleAppNetFx48 set serviceDescription="" -set serviceLaunchPath=%batchFolder%ConsoleAppNetFx48.exe +set serviceLaunchPath=%batchFolder%bin\ConsoleAppNetFx48.exe set serviceLogonId=.\setup -set serviceLogonPassword=password -::set serverName=\\YAO-S658RF +set serviceLogonPassword=pass@w0rd1~ +::set serverName=\\Computer Name set serverName= Call SafeStopService %serviceName% %serverName% Call SafeDeleteService %serviceName% %serverName% Call SafeCreateService %serviceName% %serviceDisplayName% %serviceDescription% %serviceLaunchPath% %serviceLogonId% %serviceLogonPassword% %serverName% -::Call SafeStartService %serviceName% %serverName% +Call SafeStartService %serviceName% %serverName% From d50a334ff0ef5e7b31be2c9435e7b858e88e9e3d Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 7 Apr 2021 18:49:16 +0800 Subject: [PATCH 047/424] add batch file --- .../ConsoleAppNetFx48/CallSafeCreateService.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat b/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat index 5d5b9686..8e500b2b 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/CallSafeCreateService.bat @@ -5,7 +5,7 @@ set serviceDisplayName=ConsoleAppNetFx48 set serviceDescription="" set serviceLaunchPath=%batchFolder%bin\ConsoleAppNetFx48.exe set serviceLogonId=.\setup -set serviceLogonPassword=pass@w0rd1~ +set serviceLogonPassword=password ::set serverName=\\Computer Name set serverName= Call SafeStopService %serviceName% %serverName% From 7221b37673a340abc0b7fb114b1a164753147e38 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 8 Apr 2021 11:38:23 +0800 Subject: [PATCH 048/424] refactor --- Host/Lab.WorkerService/ConsoleAppNetFx48/SafeCreateService.bat | 3 +-- Host/Lab.WorkerService/ConsoleAppNetFx48/SafeDeleteService.bat | 1 - Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStartService.bat | 3 +-- Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStopService.bat | 3 +-- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeCreateService.bat b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeCreateService.bat index b907a7f6..5851866c 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeCreateService.bat +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeCreateService.bat @@ -113,5 +113,4 @@ echo Example: %0 MyService (for local PC) echo. ::GOTO:eof -:End -::pause \ No newline at end of file +:End \ No newline at end of file diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeDeleteService.bat b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeDeleteService.bat index 4c3057a5..1e045a82 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeDeleteService.bat +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeDeleteService.bat @@ -75,4 +75,3 @@ echo Example: %0 MyService (for local PC) echo. :End -::pause \ No newline at end of file diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStartService.bat b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStartService.bat index 90f24eae..98bdf838 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStartService.bat +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStartService.bat @@ -62,5 +62,4 @@ echo Example: %0 MyService (for local PC) echo. ::GOTO:eof -:End -::pause \ No newline at end of file +:End \ No newline at end of file diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStopService.bat b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStopService.bat index 7bdadfc4..952915f2 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStopService.bat +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/SafeStopService.bat @@ -61,5 +61,4 @@ echo For reason codes, run "sc stop" ::GOTO:eof -:End -::pause \ No newline at end of file +:End \ No newline at end of file From 7195b7effba1d248cfc929247ba0cdec7aec8605 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 8 Apr 2021 14:58:44 +0800 Subject: [PATCH 049/424] refactor --- .../ConsoleAppNetFx48.csproj | 4 +- .../ConsoleAppNetFx48/Program.cs | 51 +++++++++---------- .../ConsoleAppNetFx48/Worker.cs | 6 ++- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj b/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj index bf0e87b7..6a9de990 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/Program.cs b/Host/Lab.WorkerService/ConsoleAppNetFx48/Program.cs index cc743c08..89547e27 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/Program.cs +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/Program.cs @@ -1,38 +1,37 @@ +using System; +using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Topshelf; +using Topshelf.Extensions.Hosting; +using Host = Microsoft.Extensions.Hosting.Host; namespace ConsoleAppNetFx48 { public class Program { - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .UseWindowsService() - .ConfigureServices((hostContext, services) => - { - services.AddHostedService(); - }); - - public static void Main(string[] args) + private static void Main(string[] args) { - // HostFactory.Run(x => - // { - // x.Service(s => - // { - // s.ConstructUsing(name => new DoThing()); - // s.WhenStarted(tc => tc.Start()); - // s.WhenStopped(tc => tc.Stop()); - // }); - // x.UseNLog(); - // x.RunAsLocalSystem(); - // var assemblyName = Assembly.GetEntryAssembly().GetName().Name; - // x.SetDescription("Sample Topshelf Host"); - // x.SetDisplayName(assemblyName); - // x.SetServiceName(assemblyName); - // }); - var hostBuilder = CreateHostBuilder(args); - hostBuilder.Build().Run(); + + var exitCode = + hostBuilder.RunAsTopshelfService(config => + { + var assemblyName = Assembly.GetEntryAssembly().GetName().Name; + config.SetServiceName(assemblyName); + config.SetDisplayName(assemblyName); + config.SetDescription("Runs a generic host as a Topshelf service."); + config.RunAsPrompt(); + }); + Console.WriteLine($"服務控制狀態:{exitCode}"); + // hostBuilder.Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + // .UseWindowsService() + .ConfigureServices((hostContext, services) => { services.AddHostedService(); }); } } } \ No newline at end of file diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs b/Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs index e2c4435f..5609eb04 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/Worker.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; @@ -11,7 +10,10 @@ public class Worker : BackgroundService { private readonly ILogger _logger; - public Worker(ILogger logger) + public Worker(ILogger logger, + IHostApplicationLifetime appLifetime, + IHostLifetime hostLifetime, + IHostEnvironment hostEnvironment) { this._logger = logger; } From e2c2f43c1f611e4cbbc03522e7e81bf9b58389a4 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 8 Apr 2021 16:02:30 +0800 Subject: [PATCH 050/424] add Topshelf.Extensions.Configuration --- .../ConsoleAppNetFx48.csproj | 16 ++++++++++- .../ConsoleAppNetFx48/Player.cs | 9 ++++++ .../ConsoleAppNetFx48/Program.cs | 28 +++++++++++++++---- .../ConsoleAppNetFx48/appsettings.json | 11 ++++++++ 4 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 Host/Lab.WorkerService/ConsoleAppNetFx48/Player.cs diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj b/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj index 6a9de990..eacddb05 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/ConsoleAppNetFx48.csproj @@ -11,7 +11,8 @@ - + + @@ -32,4 +33,17 @@ Always + + + + true + Always + PreserveNewest + + + true + Always + PreserveNewest + + diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/Player.cs b/Host/Lab.WorkerService/ConsoleAppNetFx48/Player.cs new file mode 100644 index 00000000..8969ad99 --- /dev/null +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/Player.cs @@ -0,0 +1,9 @@ +namespace ConsoleAppNetFx48 +{ + public struct Player + { + public string AppId { get; set; } + + public string Key { get; set; } + } +} \ No newline at end of file diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/Program.cs b/Host/Lab.WorkerService/ConsoleAppNetFx48/Program.cs index 89547e27..814cb9a2 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/Program.cs +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/Program.cs @@ -1,8 +1,12 @@ using System; +using System.IO; using System.Reflection; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Topshelf; +using Topshelf.Configuration; using Topshelf.Extensions.Hosting; using Host = Microsoft.Extensions.Hosting.Host; @@ -17,19 +21,33 @@ private static void Main(string[] args) var exitCode = hostBuilder.RunAsTopshelfService(config => { - var assemblyName = Assembly.GetEntryAssembly().GetName().Name; - config.SetServiceName(assemblyName); - config.SetDisplayName(assemblyName); - config.SetDescription("Runs a generic host as a Topshelf service."); - config.RunAsPrompt(); + // var assemblyName = Assembly.GetEntryAssembly().GetName().Name; + // config.SetServiceName(assemblyName); + // config.SetDisplayName(assemblyName); + // config.SetDescription("Runs a generic host as a Topshelf service."); + // config.RunAsPrompt(); + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddConsole(); + }); + config.UseLoggingExtensions(loggerFactory); + + var configRoot = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + var topshelfSection = configRoot.GetSection("Topshelf"); + config.ApplyConfiguration(topshelfSection); }); Console.WriteLine($"服務控制狀態:{exitCode}"); + // hostBuilder.Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) { return Host.CreateDefaultBuilder(args) + // .UseWindowsService() .ConfigureServices((hostContext, services) => { services.AddHostedService(); }); } diff --git a/Host/Lab.WorkerService/ConsoleAppNetFx48/appsettings.json b/Host/Lab.WorkerService/ConsoleAppNetFx48/appsettings.json index 8983e0fc..b8026813 100644 --- a/Host/Lab.WorkerService/ConsoleAppNetFx48/appsettings.json +++ b/Host/Lab.WorkerService/ConsoleAppNetFx48/appsettings.json @@ -5,5 +5,16 @@ "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } + }, + "Topshelf": { + "ServiceName": "ConsoleAppNetFx48", + "DisplayName": "ConsoleAppNetFx48", + "Description":"Runs a generic host as a Topshelf service.", + "Instance":"1", + "Account":{ + "Username":".\\setup", + "Password":"password" + }, + "StopTimeout":"60" } } From ad476db1a2400a00d8d6d72b2012afebd0e0ec4a Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 9 Apr 2021 11:45:19 +0800 Subject: [PATCH 051/424] add user secret test --- .../NetCore/Lab.Config/NetFx48/NetFx48.csproj | 5 ++- .../NetFx48/SurveyUserSecretTests.cs | 42 +++++++++++++++++++ .../Lab.Config/NetFx48/appsettings.json | 3 +- 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/SurveyUserSecretTests.cs diff --git a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj index 954e0d74..c347c8c3 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj +++ b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj @@ -1,4 +1,4 @@ - + net48 @@ -10,7 +10,8 @@ Debug;Release;QA AnyCPU - + 659be13b-676e-4c9e-a0b9-0df2ffd75cfc + true diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyUserSecretTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyUserSecretTests.cs new file mode 100644 index 00000000..67e005f8 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyUserSecretTests.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NetFx48 +{ + [TestClass] + public class SurveyUserSecretTests + { + [TestMethod] + public void HostŪK() + { + var builder = Host.CreateDefaultBuilder() + .ConfigureHostConfiguration(config => + { + config.AddJsonFile("appsettings.json", false, true); + }) + ; + var host = builder.Build(); + + var config = host.Services.GetService(); + Console.WriteLine($"Player:Key = {config["Player:Key"]}"); + Console.WriteLine($"DbPassword = {config["DbPassword"]}"); + } + + [TestMethod] + public void ʹҤƲպAŪK() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .AddUserSecrets() + ; + + var config = builder.Build(); + Console.WriteLine($"Player:Key = {config["Player:Key"]}"); + } + } +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.json b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.json index db5d9362..52fc1bcc 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.json +++ b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.json @@ -6,5 +6,6 @@ "AppId": "player1", "Key": "1234567890" }, - "Environment": "Development" + "Environment": "Development", + "ApplicationName":"NetFx48" } \ No newline at end of file From 985ba5f19471ef04f51fa42a7c499973063f0d6b Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 9 Apr 2021 23:19:23 +0800 Subject: [PATCH 052/424] =?UTF-8?q?=E8=AA=BF=E6=95=B4=E6=9E=B6=E6=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Lab.DAL.TestProject.csproj | 6 ++ .../Lab.DAL.TestProject/UnitTest1.cs | 24 ++++- .../Lab.DAL/DbContextOptionManager.cs | 31 ------ .../Lab.DAL/DefaultDbContextManager.cs | 94 +++++++++++++++++++ .../DomainModel/Employee/InsertRequest.cs | 6 ++ .../Lab.DAL/EmployeeContextFactory.cs | 19 ---- .../Lab.DAL/EmployeeRepository.cs | 38 ++++++++ .../Lab.DAL/EntityModel/EmployeeContext.cs | 48 +++++++--- .../Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj | 2 +- 9 files changed, 203 insertions(+), 65 deletions(-) delete mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/DbContextOptionManager.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs delete mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeContextFactory.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj index 7b4935b5..e94f8450 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj @@ -12,6 +12,12 @@ + + + + + + diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs index 21530ddd..314999cd 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs @@ -1,7 +1,10 @@ using System; using System.Linq; +using Lab.DAL.DomainModel.Employee; using Lab.DAL.EntityModel; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Lab.DAL.UnitTest @@ -12,7 +15,7 @@ public class UnitTest1 [TestMethod] public void TestMethod1() { - var options = DbContextOptionManager.CreateEmployeeDbContextOptions(); + var options = DefaultDbContextManager.CreateEmployeeDbContextOptions(); using (var dbContext = new EmployeeContext(options)) { var employees = dbContext.Employees.AsNoTracking().ToList(); @@ -22,7 +25,7 @@ public void TestMethod1() [TestMethod] public void TestMethod2() { - var options = DbContextOptionManager.CreateEmployeeDbContextOptions(); + var options = DefaultDbContextManager.CreateEmployeeDbContextOptions(); using (var dbContext = new EmployeeContext(options)) { @@ -39,5 +42,22 @@ public void TestMethod2() Assert.AreEqual(true, toDb.SequenceId != 0); } } + + [TestMethod] + public void 注入DbContextFactor操作真實資料庫() + { + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(service => + { + service + .AddDbContextFactory(DefaultDbContextManager + .ApplyConfigurePhysical); + service.AddSingleton(); + }); + var host = builder.Build(); + var repository = host.Services.GetService(); + var count = repository.InsertAsync(new InsertRequest(), "").Result; + Assert.AreEqual(1, count); + } } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DbContextOptionManager.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DbContextOptionManager.cs deleted file mode 100644 index 851b770f..00000000 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DbContextOptionManager.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Lab.DAL.EntityModel; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; - -namespace Lab.DAL -{ - public class DbContextOptionManager - { - public static DbContextOptions CreateEmployeeDbContextOptions() - { - var configuration = new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .Build(); - var connectionString = configuration.GetConnectionString("DefaultConnection"); - var loggerFactory = LoggerFactory.Create(builder => - { - builder - //.AddFilter("Microsoft", LogLevel.Warning) - //.AddFilter("System", LogLevel.Warning) - .AddFilter("Lab.DAL", LogLevel.Debug) - .AddConsole() - ; - }); - return new DbContextOptionsBuilder() - .UseSqlServer(connectionString) - .UseLoggerFactory(loggerFactory) - .Options; - } - } -} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs new file mode 100644 index 00000000..156a99f7 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs @@ -0,0 +1,94 @@ +using System; +using Lab.DAL.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.DAL +{ + public class DefaultDbContextManager + { + public static void ApplyConfigureMemory(IServiceProvider provider, + DbContextOptionsBuilder optionsBuilder) + { + var configuration = provider.GetService(); + var connectionString = configuration.GetConnectionString("DefaultConnection"); + var loggerFactory = LoggerFactory.Create(builder => + { + builder + + //.AddFilter("Microsoft", LogLevel.Warning) + //.AddFilter("System", LogLevel.Warning) + .AddFilter("Lab.DAL", LogLevel.Debug) + .AddConsole() + ; + }); + optionsBuilder.UseSqlServer(connectionString) + .UseInMemoryDatabase("Demo") + .UseLoggerFactory(loggerFactory) + ; + } + + public static void ApplyConfigurePhysical(IServiceProvider provider, + DbContextOptionsBuilder optionsBuilder) + { + var configuration = provider.GetService(); + var connectionString = configuration.GetConnectionString("DefaultConnection"); + var loggerFactory = LoggerFactory.Create(builder => + { + builder + + //.AddFilter("Microsoft", LogLevel.Warning) + //.AddFilter("System", LogLevel.Warning) + .AddFilter("Lab.DAL", LogLevel.Debug) + .AddConsole() + ; + }); + optionsBuilder.UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + ; + } + public static DbContextOptionsBuilder CreateEmployeeDbContextOptionsBuilder() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .Build(); + var connectionString = configuration.GetConnectionString("DefaultConnection"); + var loggerFactory = LoggerFactory.Create(builder => + { + builder + //.AddFilter("Microsoft", LogLevel.Warning) + //.AddFilter("System", LogLevel.Warning) + .AddFilter("Lab.DAL", LogLevel.Debug) + .AddConsole() + ; + }); + return new DbContextOptionsBuilder() + .UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + ; + } + + public static DbContextOptions CreateEmployeeDbContextOptions() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .Build(); + var connectionString = configuration.GetConnectionString("DefaultConnection"); + var loggerFactory = LoggerFactory.Create(builder => + { + builder + //.AddFilter("Microsoft", LogLevel.Warning) + //.AddFilter("System", LogLevel.Warning) + .AddFilter("Lab.DAL", LogLevel.Debug) + .AddConsole() + ; + }); + return new DbContextOptionsBuilder() + .UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + .Options; + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs new file mode 100644 index 00000000..243a534c --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs @@ -0,0 +1,6 @@ +namespace Lab.DAL.DomainModel.Employee +{ + public class InsertRequest + { + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeContextFactory.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeContextFactory.cs deleted file mode 100644 index 3214e39e..00000000 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeContextFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Lab.DAL.EntityModel; -using Microsoft.EntityFrameworkCore; - -namespace Lab.DAL -{ - public class EmployeeContextFactory - { - private DbContextOptions _options; - - public EmployeeContextFactory(DbContextOptions options) - { - this._options = options; - } - - public EmployeeContextFactory():this(DbContextOptionManager.CreateEmployeeDbContextOptions()) - { - } - } -} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs new file mode 100644 index 00000000..a2eddcbb --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs @@ -0,0 +1,38 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Lab.DAL.DomainModel.Employee; +using Lab.DAL.EntityModel; +using Microsoft.EntityFrameworkCore; + +namespace Lab.DAL +{ + public class EmployeeRepository + { + private readonly IDbContextFactory _factory; + + public EmployeeRepository(IDbContextFactory factory) + { + this._factory = factory; + } + + // public EmployeeRepository(EmployeeContext factory) + // { + // } + public async Task InsertAsync(InsertRequest request, + string accessId, + CancellationToken cancel = default) + { + using var dbContext = this._factory.CreateDbContext(); + var id = Guid.NewGuid(); + var toDb = new Employee + { + Id = id, + Name = "yao", + Age = 18, + }; + await dbContext.Employees.AddAsync(toDb, cancel); + return await dbContext.SaveChangesAsync(cancel); + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs index 142d3f56..6be4b774 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; namespace Lab.DAL.EntityModel { @@ -12,14 +13,36 @@ public class EmployeeContext : DbContext public virtual DbSet Orders { get; set; } - public EmployeeContext() - { - - } + // public EmployeeContext() + // { + // + // } + // public EmployeeContext(string connectionString) + // { + // this._connectionString = connectionString; + // if (!s_migrated[0]) + // { + // lock (s_migrated) + // { + // if (!s_migrated[0]) + // { + // this.Database.Migrate(); + // s_migrated[0] = true; + // } + // } + // } + // } + public string ConnectionString { get; } public EmployeeContext(DbContextOptions options) : base(options) { + var sqlServerOptionsExtension = options.FindExtension(); + if (sqlServerOptionsExtension != null) + { + this.ConnectionString = sqlServerOptionsExtension.ConnectionString; + } + if (!s_migrated[0]) { lock (s_migrated) @@ -33,16 +56,17 @@ public EmployeeContext(DbContextOptions options) } } + // 建構函數配置失敗才需要以下處理 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { -// //var connectionString = "Server=(localdb)\\mssqllocaldb;Database=LabEmployee.DAL;Trusted_Connection=True;"; -// var connectionString = DbOptionsFactory.ConnectionString; -// if (!optionsBuilder.IsConfigured) -// { -// #warning To protect potentially sensitive information in your connection string, you should move it out of source code. See http://go.microsoft.com/fwlink/?LinkId=723263 for guidance on storing connection strings. -// optionsBuilder -// .UseSqlServer(connectionString); -// } + // var connectionString = + // "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL.UnitTest;Trusted_Connection=True;MultipleActiveResultSets=true"; + // + // // var connectionString = this._connectionString; + // if (optionsBuilder.IsConfigured == false) + // { + // optionsBuilder.UseSqlServer(connectionString); + // } } } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj index 99c5ead8..4fb1d415 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj @@ -7,10 +7,10 @@ + - From d3651c7e7afbd03bd49896c25ae4189e2e50869d Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Sat, 10 Apr 2021 16:42:15 +0800 Subject: [PATCH 053/424] =?UTF-8?q?=E8=AA=BF=E6=95=B4=E6=9E=B6=E6=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Lab.Biz/IEmployeeRepository.cs | 8 +++ .../Lab.VirtualDb/Lab.Biz/Lab.Biz.csproj | 7 +++ .../Lab.DAL.TestProject/UnitTest1.cs | 30 +++++++--- .../Lab.VirtualDb/Lab.DAL/DbOptionsFactory.cs | 37 ------------ ...tManager.cs => DefaultDbContextBuilder.cs} | 58 +++++++++---------- .../Lab.DAL/EmployeeRepository.cs | 13 ++--- .../Lab.DAL/EntityModel/EmployeeContext.cs | 38 +++--------- ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln | 6 ++ 8 files changed, 86 insertions(+), 111 deletions(-) create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.Biz/IEmployeeRepository.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.Biz/Lab.Biz.csproj delete mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/DbOptionsFactory.cs rename ORM/EFCore/Lab.VirtualDb/Lab.DAL/{DefaultDbContextManager.cs => DefaultDbContextBuilder.cs} (65%) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.Biz/IEmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.Biz/IEmployeeRepository.cs new file mode 100644 index 00000000..5f0cc7f0 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.Biz/IEmployeeRepository.cs @@ -0,0 +1,8 @@ +using System; + +namespace Lab.Biz +{ + public interface IEmployeeRepository + { + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.Biz/Lab.Biz.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.Biz/Lab.Biz.csproj new file mode 100644 index 00000000..cbfa5815 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.Biz/Lab.Biz.csproj @@ -0,0 +1,7 @@ + + + + net5.0 + + + diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs index 314999cd..eee4ed3a 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs @@ -15,7 +15,7 @@ public class UnitTest1 [TestMethod] public void TestMethod1() { - var options = DefaultDbContextManager.CreateEmployeeDbContextOptions(); + var options = DefaultDbContextBuilder.CreateEmployeeDbContextOptions(); using (var dbContext = new EmployeeContext(options)) { var employees = dbContext.Employees.AsNoTracking().ToList(); @@ -25,7 +25,7 @@ public void TestMethod1() [TestMethod] public void TestMethod2() { - var options = DefaultDbContextManager.CreateEmployeeDbContextOptions(); + var options = DefaultDbContextBuilder.CreateEmployeeDbContextOptions(); using (var dbContext = new EmployeeContext(options)) { @@ -47,12 +47,28 @@ public void TestMethod2() public void 注入DbContextFactor操作真實資料庫() { var builder = Host.CreateDefaultBuilder() - .ConfigureServices(service => + .ConfigureServices(services => { - service - .AddDbContextFactory(DefaultDbContextManager - .ApplyConfigurePhysical); - service.AddSingleton(); + services + .AddDbContextFactory + (DefaultDbContextBuilder.ApplyConfigurePhysical); + services.AddSingleton(); + }); + var host = builder.Build(); + var repository = host.Services.GetService(); + var count = repository.InsertAsync(new InsertRequest(), "").Result; + Assert.AreEqual(1, count); + } + + [TestMethod] + public void 注入DbContextFactor操作記憶體() + { + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(services => + { + services.AddDbContextFactory + (DefaultDbContextBuilder.ApplyConfigureMemory); + services.AddSingleton(); }); var host = builder.Build(); var repository = host.Services.GetService(); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DbOptionsFactory.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DbOptionsFactory.cs deleted file mode 100644 index 059eec32..00000000 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DbOptionsFactory.cs +++ /dev/null @@ -1,37 +0,0 @@ -// using Lab.DAL.EntityModel; -// using Microsoft.EntityFrameworkCore; -// using Microsoft.Extensions.Configuration; -// using Microsoft.Extensions.Logging; -// -// namespace Lab.DAL -// { -// public class DbOptionsFactory -// { -// public static DbContextOptions DbContextOptions { get; } -// -// public static string ConnectionString { get; } -// public static readonly ILoggerFactory MyLoggerFactory -// = LoggerFactory.Create(builder => { builder.AddConsole(); }); -// -// static DbOptionsFactory() -// { -// var configuration = new ConfigurationBuilder() -// .AddJsonFile("appsettings.json") -// .Build(); -// ConnectionString = configuration.GetConnectionString("DefaultConnection"); -// var loggerFactory = LoggerFactory.Create(builder => -// { -// builder -// //.AddFilter("Microsoft", LogLevel.Warning) -// //.AddFilter("System", LogLevel.Warning) -// .AddFilter("Lab.DAL", LogLevel.Debug) -// .AddConsole() -// ; -// }); -// DbContextOptions = new DbContextOptionsBuilder() -// .UseSqlServer(ConnectionString) -// .UseLoggerFactory(loggerFactory) -// .Options; -// } -// } -// } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextBuilder.cs similarity index 65% rename from ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs rename to ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextBuilder.cs index 156a99f7..4e7c7d1e 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextBuilder.cs @@ -7,13 +7,24 @@ namespace Lab.DAL { - public class DefaultDbContextManager + public class DefaultDbContextBuilder { - public static void ApplyConfigureMemory(IServiceProvider provider, - DbContextOptionsBuilder optionsBuilder) + private IServiceProvider _serviceProvider; + + public DefaultDbContextBuilder(IServiceCollection services) + { + if (services == null) + { + services = new ServiceCollection(); + } + services.AddDbContextFactory(ApplyConfigurePhysical); + this._serviceProvider = services.BuildServiceProvider(); + services.AddSingleton(); + } + + public static void ApplyConfigureMemory(IServiceProvider provider, + DbContextOptionsBuilder optionsBuilder) { - var configuration = provider.GetService(); - var connectionString = configuration.GetConnectionString("DefaultConnection"); var loggerFactory = LoggerFactory.Create(builder => { builder @@ -24,14 +35,13 @@ public static void ApplyConfigureMemory(IServiceProvider provider, .AddConsole() ; }); - optionsBuilder.UseSqlServer(connectionString) - .UseInMemoryDatabase("Demo") + optionsBuilder.UseInMemoryDatabase("Demo") .UseLoggerFactory(loggerFactory) ; } - public static void ApplyConfigurePhysical(IServiceProvider provider, - DbContextOptionsBuilder optionsBuilder) + public static void ApplyConfigurePhysical(IServiceProvider provider, + DbContextOptionsBuilder optionsBuilder) { var configuration = provider.GetService(); var connectionString = configuration.GetConnectionString("DefaultConnection"); @@ -49,28 +59,13 @@ public static void ApplyConfigurePhysical(IServiceProvider provider, .UseLoggerFactory(loggerFactory) ; } - public static DbContextOptionsBuilder CreateEmployeeDbContextOptionsBuilder() + + public static DbContextOptions CreateEmployeeDbContextOptions() { - var configuration = new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .Build(); - var connectionString = configuration.GetConnectionString("DefaultConnection"); - var loggerFactory = LoggerFactory.Create(builder => - { - builder - //.AddFilter("Microsoft", LogLevel.Warning) - //.AddFilter("System", LogLevel.Warning) - .AddFilter("Lab.DAL", LogLevel.Debug) - .AddConsole() - ; - }); - return new DbContextOptionsBuilder() - .UseSqlServer(connectionString) - .UseLoggerFactory(loggerFactory) - ; - } + return CreateEmployeeDbContextOptionsBuilder().Options; + } - public static DbContextOptions CreateEmployeeDbContextOptions() + public static DbContextOptionsBuilder CreateEmployeeDbContextOptionsBuilder() { var configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") @@ -79,6 +74,7 @@ public static DbContextOptions CreateEmployeeDbContextOptions( var loggerFactory = LoggerFactory.Create(builder => { builder + //.AddFilter("Microsoft", LogLevel.Warning) //.AddFilter("System", LogLevel.Warning) .AddFilter("Lab.DAL", LogLevel.Debug) @@ -88,7 +84,7 @@ public static DbContextOptions CreateEmployeeDbContextOptions( return new DbContextOptionsBuilder() .UseSqlServer(connectionString) .UseLoggerFactory(loggerFactory) - .Options; - } + ; + } } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs index a2eddcbb..d5a5db8b 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs @@ -11,14 +11,13 @@ public class EmployeeRepository { private readonly IDbContextFactory _factory; - public EmployeeRepository(IDbContextFactory factory) - { - this._factory = factory; - } - - // public EmployeeRepository(EmployeeContext factory) + // public EmployeeRepository(IDbContextFactory factory) // { + // this._factory = factory; // } + public EmployeeRepository(EmployeeContext factory) + { + } public async Task InsertAsync(InsertRequest request, string accessId, CancellationToken cancel = default) @@ -27,7 +26,7 @@ public async Task InsertAsync(InsertRequest request, var id = Guid.NewGuid(); var toDb = new Employee { - Id = id, + Id = id, Name = "yao", Age = 18, }; diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs index 6be4b774..4660cb3f 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs @@ -1,5 +1,5 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; namespace Lab.DAL.EntityModel { @@ -13,43 +13,23 @@ public class EmployeeContext : DbContext public virtual DbSet Orders { get; set; } - // public EmployeeContext() - // { - // - // } - // public EmployeeContext(string connectionString) - // { - // this._connectionString = connectionString; - // if (!s_migrated[0]) - // { - // lock (s_migrated) - // { - // if (!s_migrated[0]) - // { - // this.Database.Migrate(); - // s_migrated[0] = true; - // } - // } - // } - // } public string ConnectionString { get; } public EmployeeContext(DbContextOptions options) : base(options) { - var sqlServerOptionsExtension = options.FindExtension(); - if (sqlServerOptionsExtension != null) - { - this.ConnectionString = sqlServerOptionsExtension.ConnectionString; - } - - if (!s_migrated[0]) + if (s_migrated[0] == false) { lock (s_migrated) { - if (!s_migrated[0]) + if (s_migrated[0] == false) { - this.Database.Migrate(); + var memoryOptions = options.FindExtension(); + if (memoryOptions == null) + { + this.Database.Migrate(); + } + s_migrated[0] = true; } } diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln b/ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln index 2d4c828a..f401e0a4 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln +++ b/ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln @@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.DAL", "Lab.DAL\Lab.DAL. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.DAL.TestProject", "Lab.DAL.TestProject\Lab.DAL.TestProject.csproj", "{3DAC8D3D-E494-459B-BEAD-AD306034E441}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Biz", "Lab.Biz\Lab.Biz.csproj", "{0056BEF7-8B47-4387-9110-788A3B73E452}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {3DAC8D3D-E494-459B-BEAD-AD306034E441}.Debug|Any CPU.Build.0 = Debug|Any CPU {3DAC8D3D-E494-459B-BEAD-AD306034E441}.Release|Any CPU.ActiveCfg = Release|Any CPU {3DAC8D3D-E494-459B-BEAD-AD306034E441}.Release|Any CPU.Build.0 = Release|Any CPU + {0056BEF7-8B47-4387-9110-788A3B73E452}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0056BEF7-8B47-4387-9110-788A3B73E452}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0056BEF7-8B47-4387-9110-788A3B73E452}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0056BEF7-8B47-4387-9110-788A3B73E452}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 7f3e111371c1524e6afe1dcf6ac5ce79b778e447 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Sun, 11 Apr 2021 23:25:53 +0800 Subject: [PATCH 054/424] refactor --- .../Lab.DAL.TestProject/UnitTest1.cs | 19 ++--- ...tBuilder.cs => DefaultDbContextFactory.cs} | 76 ++++++++++++++++--- .../Lab.DAL/EmployeeRepository.cs | 32 +++++--- .../Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj | 6 +- 4 files changed, 102 insertions(+), 31 deletions(-) rename ORM/EFCore/Lab.VirtualDb/Lab.DAL/{DefaultDbContextBuilder.cs => DefaultDbContextFactory.cs} (60%) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs index eee4ed3a..fe4cceb1 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs @@ -3,6 +3,7 @@ using Lab.DAL.DomainModel.Employee; using Lab.DAL.EntityModel; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -15,7 +16,7 @@ public class UnitTest1 [TestMethod] public void TestMethod1() { - var options = DefaultDbContextBuilder.CreateEmployeeDbContextOptions(); + var options = DefaultDbContextFactory.CreateEmployeeDbContextOptions(); using (var dbContext = new EmployeeContext(options)) { var employees = dbContext.Employees.AsNoTracking().ToList(); @@ -25,7 +26,7 @@ public void TestMethod1() [TestMethod] public void TestMethod2() { - var options = DefaultDbContextBuilder.CreateEmployeeDbContextOptions(); + var options = DefaultDbContextFactory.CreateEmployeeDbContextOptions(); using (var dbContext = new EmployeeContext(options)) { @@ -43,15 +44,14 @@ public void TestMethod2() } } + + [TestMethod] - public void 注入DbContextFactor操作真實資料庫() + public void 注入DbContextFactory操作真實資料庫() { var builder = Host.CreateDefaultBuilder() .ConfigureServices(services => { - services - .AddDbContextFactory - (DefaultDbContextBuilder.ApplyConfigurePhysical); services.AddSingleton(); }); var host = builder.Build(); @@ -61,16 +61,17 @@ public void 注入DbContextFactor操作真實資料庫() } [TestMethod] - public void 注入DbContextFactor操作記憶體() + public void 注入DbContextFactory操作記憶體() { var builder = Host.CreateDefaultBuilder() .ConfigureServices(services => { - services.AddDbContextFactory - (DefaultDbContextBuilder.ApplyConfigureMemory); services.AddSingleton(); }); var host = builder.Build(); + + DefaultDbContextFactory.UseMemory(); + var repository = host.Services.GetService(); var count = repository.InsertAsync(new InsertRequest(), "").Result; Assert.AreEqual(1, count); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextBuilder.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextFactory.cs similarity index 60% rename from ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextBuilder.cs rename to ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextFactory.cs index 4e7c7d1e..e2b97023 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextBuilder.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextFactory.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using Lab.DAL.EntityModel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -7,19 +8,59 @@ namespace Lab.DAL { - public class DefaultDbContextBuilder + public static class DefaultDbContextFactory { - private IServiceProvider _serviceProvider; + private static readonly Lazy s_serviceProviderLazy; + private static readonly Lazy s_configurationLazy; + private static ServiceProvider s_serviceProvider; + private static IConfiguration s_configuration; - public DefaultDbContextBuilder(IServiceCollection services) + public static ServiceProvider ServiceProvider { - if (services == null) + get { - services = new ServiceCollection(); + if (s_serviceProvider == null) + { + s_serviceProvider = s_serviceProviderLazy.Value; + } + + return s_serviceProvider; + } + set => s_serviceProvider = value; + } + + public static IConfiguration Configuration + { + get + { + if (s_configuration == null) + { + s_configuration = s_configurationLazy.Value; + } + + return s_configuration; } - services.AddDbContextFactory(ApplyConfigurePhysical); - this._serviceProvider = services.BuildServiceProvider(); - services.AddSingleton(); + set => s_configuration = value; + } + + static DefaultDbContextFactory() + { + s_serviceProviderLazy = + new Lazy(() => + { + var services = new ServiceCollection(); + services.AddDbContextFactory(ApplyConfigurePhysical); + + return services.BuildServiceProvider(); + }); + s_configurationLazy + = new Lazy(() => + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + return configBuilder.Build(); + }); } public static void ApplyConfigureMemory(IServiceProvider provider, @@ -43,7 +84,12 @@ public static void ApplyConfigureMemory(IServiceProvider provider, public static void ApplyConfigurePhysical(IServiceProvider provider, DbContextOptionsBuilder optionsBuilder) { - var configuration = provider.GetService(); + var configuration = provider.GetService(); + if (configuration == null) + { + configuration = Configuration; + } + var connectionString = configuration.GetConnectionString("DefaultConnection"); var loggerFactory = LoggerFactory.Create(builder => { @@ -86,5 +132,17 @@ public static DbContextOptionsBuilder CreateEmployeeDbContextOp .UseLoggerFactory(loggerFactory) ; } + + public static T GetInstance() + { + return ServiceProvider.GetService(); + } + + public static void UseMemory() + { + var services = new ServiceCollection(); + services.AddDbContextFactory(ApplyConfigureMemory); + ServiceProvider = services.BuildServiceProvider(); + } } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs index d5a5db8b..42e8066d 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs @@ -4,29 +4,37 @@ using Lab.DAL.DomainModel.Employee; using Lab.DAL.EntityModel; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace Lab.DAL { public class EmployeeRepository { - private readonly IDbContextFactory _factory; - - // public EmployeeRepository(IDbContextFactory factory) - // { - // this._factory = factory; - // } - public EmployeeRepository(EmployeeContext factory) + internal IDbContextFactory DbContextFactory { - } - public async Task InsertAsync(InsertRequest request, - string accessId, + get + { + if (this._dbContextFactory == null) + { + this._dbContextFactory = DefaultDbContextFactory.GetInstance>(); + } + + return this._dbContextFactory; + } + set => this._dbContextFactory = value; + } + + private IDbContextFactory _dbContextFactory; + + public async Task InsertAsync(InsertRequest request, + string accessId, CancellationToken cancel = default) { - using var dbContext = this._factory.CreateDbContext(); + using var dbContext = this.DbContextFactory.CreateDbContext(); var id = Guid.NewGuid(); var toDb = new Employee { - Id = id, + Id = id, Name = "yao", Age = 18, }; diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj index 4fb1d415..29ca09ce 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj @@ -17,5 +17,9 @@ Always - + + + <_Parameter1>Lab.DAL.TestProject + + From bd5cec8250cd0d4708d562ecd04fc8302f279d1d Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Mon, 12 Apr 2021 09:17:40 +0800 Subject: [PATCH 055/424] add --- .../Lab.Biz/IEmployeeRepository.cs | 8 + .../Lab.VirtualDb/Lab.Biz/Lab.Biz.csproj | 7 + .../Lab.DAL.TestProject.csproj | 33 ++++ .../Lab.DAL.TestProject/UnitTest1.cs | 36 +++++ .../Lab.DAL.TestProject/appsettings.json | 5 + .../Lab.DAL/DefaultDbContextFactory.cs | 142 ++++++++++++++++++ .../DomainModel/Employee/InsertRequest.cs | 24 +++ .../Lab.DAL/EmployeeRepository.cs | 45 ++++++ .../Lab.DAL/EntityModel/Employee.cs | 34 +++++ .../Lab.DAL/EntityModel/EmployeeContext.cs | 52 +++++++ .../Lab.DAL/EntityModel/Identity.cs | 27 ++++ .../Lab.DAL/EntityModel/Order.cs | 24 +++ .../Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj | 25 +++ .../Lab.VirtualDb/Lab.DAL/appsettings.json | 5 + ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln | 28 ++++ 15 files changed, 495 insertions(+) create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.Biz/IEmployeeRepository.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.Biz/Lab.Biz.csproj create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/appsettings.json create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextFactory.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Order.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/appsettings.json create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.Biz/IEmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.Biz/IEmployeeRepository.cs new file mode 100644 index 00000000..5f0cc7f0 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.Biz/IEmployeeRepository.cs @@ -0,0 +1,8 @@ +using System; + +namespace Lab.Biz +{ + public interface IEmployeeRepository + { + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.Biz/Lab.Biz.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.Biz/Lab.Biz.csproj new file mode 100644 index 00000000..cbfa5815 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.Biz/Lab.Biz.csproj @@ -0,0 +1,7 @@ + + + + net5.0 + + + diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj new file mode 100644 index 00000000..e94f8450 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj @@ -0,0 +1,33 @@ + + + + net5.0 + bin + bin\Lab.DAL.TestProject.xml + false + + + + + + + + + + + + + + + + + + + + + + Always + + + + diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs new file mode 100644 index 00000000..8787ad2c --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs @@ -0,0 +1,36 @@ +using Lab.DAL.DomainModel.Employee; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.DAL.UnitTest +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void 操作真實資料庫() + { + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(services => { services.AddSingleton(); }); + var host = builder.Build(); + var repository = host.Services.GetService(); + var count = repository.InsertAsync(new InsertRequest(), "").Result; + Assert.AreEqual(1, count); + } + + [TestMethod] + public void 操作記憶體() + { + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(services => { services.AddSingleton(); }); + var host = builder.Build(); + + DefaultDbContextFactory.SetUseMemoryDatabase(); + + var repository = host.Services.GetService(); + var count = repository.InsertAsync(new InsertRequest(), "").Result; + Assert.AreEqual(1, count); + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/appsettings.json b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/appsettings.json new file mode 100644 index 00000000..6e379ce7 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL.UnitTest;Trusted_Connection=True;MultipleActiveResultSets=true" + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextFactory.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextFactory.cs new file mode 100644 index 00000000..05388ede --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextFactory.cs @@ -0,0 +1,142 @@ +using System; +using System.IO; +using Lab.DAL.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.DAL +{ + internal static class DefaultDbContextFactory + { + private static readonly Lazy s_serviceProviderLazy; + private static readonly Lazy s_configurationLazy; + private static ServiceProvider s_serviceProvider; + private static IConfiguration s_configuration; + + public static ServiceProvider ServiceProvider + { + get + { + if (s_serviceProvider == null) + { + s_serviceProvider = s_serviceProviderLazy.Value; + } + + return s_serviceProvider; + } + set => s_serviceProvider = value; + } + + public static IConfiguration Configuration + { + get + { + if (s_configuration == null) + { + s_configuration = s_configurationLazy.Value; + } + + return s_configuration; + } + set => s_configuration = value; + } + + static DefaultDbContextFactory() + { + s_serviceProviderLazy = + new Lazy(() => + { + var services = new ServiceCollection(); + services.AddDbContextFactory(ApplyConfigurePhysical); + return services.BuildServiceProvider(); + }); + s_configurationLazy + = new Lazy(() => + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + return configBuilder.Build(); + }); + } + + public static void ApplyConfigureMemory(IServiceProvider provider, + DbContextOptionsBuilder optionsBuilder) + { + var loggerFactory = LoggerFactory.Create(builder => + { + builder + + //.AddFilter("Microsoft", LogLevel.Warning) + //.AddFilter("System", LogLevel.Warning) + .AddFilter("Lab.DAL", LogLevel.Debug) + .AddConsole() + ; + }); + optionsBuilder.UseInMemoryDatabase("Demo") + .UseLoggerFactory(loggerFactory) + ; + } + + public static void ApplyConfigurePhysical(IServiceProvider provider, + DbContextOptionsBuilder optionsBuilder) + { + var configuration = provider.GetService(); + if (configuration == null) + { + configuration = Configuration; + } + + var connectionString = configuration.GetConnectionString("DefaultConnection"); + var loggerFactory = LoggerFactory.Create(builder => + { + builder + + //.AddFilter("Microsoft", LogLevel.Warning) + //.AddFilter("System", LogLevel.Warning) + .AddFilter("Lab.DAL", LogLevel.Debug) + .AddConsole() + ; + }); + optionsBuilder.UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + ; + } + + public static DbContextOptionsBuilder CreateEmployeeDbContextOptionsBuilder() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .Build(); + var connectionString = configuration.GetConnectionString("DefaultConnection"); + var loggerFactory = LoggerFactory.Create(builder => + { + builder + + //.AddFilter("Microsoft", LogLevel.Warning) + //.AddFilter("System", LogLevel.Warning) + .AddFilter("Lab.DAL", LogLevel.Debug) + .AddConsole() + ; + }); + return new DbContextOptionsBuilder() + .UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + ; + } + + public static T GetInstance() + { + return ServiceProvider.GetService(); + } + + public static void SetUseMemoryDatabase() + { + var services = new ServiceCollection(); + services.AddDbContextFactory(ApplyConfigureMemory); + ServiceProvider = services.BuildServiceProvider(); + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs new file mode 100644 index 00000000..dee69ae8 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Lab.DAL.DomainModel.Employee +{ + public class InsertRequest + { + [Key] + public Guid Id { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + public string Remark { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs new file mode 100644 index 00000000..42e8066d --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Lab.DAL.DomainModel.Employee; +using Lab.DAL.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Lab.DAL +{ + public class EmployeeRepository + { + internal IDbContextFactory DbContextFactory + { + get + { + if (this._dbContextFactory == null) + { + this._dbContextFactory = DefaultDbContextFactory.GetInstance>(); + } + + return this._dbContextFactory; + } + set => this._dbContextFactory = value; + } + + private IDbContextFactory _dbContextFactory; + + public async Task InsertAsync(InsertRequest request, + string accessId, + CancellationToken cancel = default) + { + using var dbContext = this.DbContextFactory.CreateDbContext(); + var id = Guid.NewGuid(); + var toDb = new Employee + { + Id = id, + Name = "yao", + Age = 18, + }; + await dbContext.Employees.AddAsync(toDb, cancel); + return await dbContext.SaveChangesAsync(cancel); + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs new file mode 100644 index 00000000..9561c908 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.DAL.EntityModel +{ + [Table("Employee")] + public class Employee + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + public virtual Identity Identity { get; set; } + + // public virtual ICollection Order { get; set; } + + public Employee() + { + // this.Order = new HashSet(); + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs new file mode 100644 index 00000000..4660cb3f --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs @@ -0,0 +1,52 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; + +namespace Lab.DAL.EntityModel +{ + public class EmployeeContext : DbContext + { + private static readonly bool[] s_migrated = {false}; + + public virtual DbSet Employees { get; set; } + + public virtual DbSet Identities { get; set; } + + public virtual DbSet Orders { get; set; } + + public string ConnectionString { get; } + + public EmployeeContext(DbContextOptions options) + : base(options) + { + if (s_migrated[0] == false) + { + lock (s_migrated) + { + if (s_migrated[0] == false) + { + var memoryOptions = options.FindExtension(); + if (memoryOptions == null) + { + this.Database.Migrate(); + } + + s_migrated[0] = true; + } + } + } + } + + // 建構函數配置失敗才需要以下處理 + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + // var connectionString = + // "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL.UnitTest;Trusted_Connection=True;MultipleActiveResultSets=true"; + // + // // var connectionString = this._connectionString; + // if (optionsBuilder.IsConfigured == false) + // { + // optionsBuilder.UseSqlServer(connectionString); + // } + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs new file mode 100644 index 00000000..fcd1d3cb --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs @@ -0,0 +1,27 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.DAL.EntityModel +{ + [Table("Identity")] + public class Identity + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid EmployeeId { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + public virtual Employee Employee { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Order.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Order.cs new file mode 100644 index 00000000..aca86633 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Order.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.DAL.EntityModel +{ + [Table("Order")] + public class Order + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Id { get; set; } + + public Guid? EmployeeId { get; set; } + + public DateTime? OrderTime { get; set; } + + public string Remark { get; set; } + + public long SequenceId { get; set; } + + public virtual Employee Employee { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj new file mode 100644 index 00000000..29ca09ce --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj @@ -0,0 +1,25 @@ + + + + net5.0 + bin + bin\Lab.DAL.xml + + + + + + + + + + + Always + + + + + <_Parameter1>Lab.DAL.TestProject + + + diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/appsettings.json b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/appsettings.json new file mode 100644 index 00000000..79b166fe --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL;Trusted_Connection=True;MultipleActiveResultSets=true" + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln b/ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln new file mode 100644 index 00000000..f401e0a4 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.DAL", "Lab.DAL\Lab.DAL.csproj", "{0258C7C6-B9DE-4050-BE38-D3BE0750BA9D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.DAL.TestProject", "Lab.DAL.TestProject\Lab.DAL.TestProject.csproj", "{3DAC8D3D-E494-459B-BEAD-AD306034E441}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Biz", "Lab.Biz\Lab.Biz.csproj", "{0056BEF7-8B47-4387-9110-788A3B73E452}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0258C7C6-B9DE-4050-BE38-D3BE0750BA9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0258C7C6-B9DE-4050-BE38-D3BE0750BA9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0258C7C6-B9DE-4050-BE38-D3BE0750BA9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0258C7C6-B9DE-4050-BE38-D3BE0750BA9D}.Release|Any CPU.Build.0 = Release|Any CPU + {3DAC8D3D-E494-459B-BEAD-AD306034E441}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DAC8D3D-E494-459B-BEAD-AD306034E441}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DAC8D3D-E494-459B-BEAD-AD306034E441}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DAC8D3D-E494-459B-BEAD-AD306034E441}.Release|Any CPU.Build.0 = Release|Any CPU + {0056BEF7-8B47-4387-9110-788A3B73E452}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0056BEF7-8B47-4387-9110-788A3B73E452}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0056BEF7-8B47-4387-9110-788A3B73E452}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0056BEF7-8B47-4387-9110-788A3B73E452}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From 98cf02c4c9ac1de5e4edf19f6a147e9b4585ecf0 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Mon, 12 Apr 2021 09:23:18 +0800 Subject: [PATCH 056/424] refactor --- .../Lab.DAL.TestProject/UnitTest1.cs | 70 +------------------ .../Lab.DAL/DefaultDbContextFactory.cs | 41 ----------- .../DomainModel/Employee/InsertRequest.cs | 9 +-- .../Lab.DAL/EntityModel/Employee.cs | 24 ------- .../Lab.DAL/EntityModel/Identity.cs | 11 --- 5 files changed, 2 insertions(+), 153 deletions(-) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs index 7fef7ff6..0ff6538c 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs @@ -1,13 +1,4 @@ -<<<<<<< HEAD using Lab.DAL.DomainModel.Employee; -======= -using System; -using System.Linq; -using Lab.DAL.DomainModel.Employee; -using Lab.DAL.EntityModel; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Internal; ->>>>>>> origin/master using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -17,62 +8,17 @@ namespace Lab.DAL.UnitTest [TestClass] public class UnitTest1 { - [TestMethod] -<<<<<<< HEAD public void 操作真實資料庫() { var builder = Host.CreateDefaultBuilder() .ConfigureServices(services => { services.AddSingleton(); }); -======= - public void TestMethod1() - { - var options = DefaultDbContextFactory.CreateEmployeeDbContextOptions(); - using (var dbContext = new EmployeeContext(options)) - { - var employees = dbContext.Employees.AsNoTracking().ToList(); - } - } - - [TestMethod] - public void TestMethod2() - { - var options = DefaultDbContextFactory.CreateEmployeeDbContextOptions(); - - using (var dbContext = new EmployeeContext(options)) - { - var id = Guid.NewGuid(); - var toDb = new Employee - { - Id = id, - Name = "yao", - Age = 18, - }; - dbContext.Employees.Add(toDb); - var count = dbContext.SaveChanges(); - Assert.AreEqual(true, count != 0); - Assert.AreEqual(true, toDb.SequenceId != 0); - } - } - - - - [TestMethod] - public void 注入DbContextFactory操作真實資料庫() - { - var builder = Host.CreateDefaultBuilder() - .ConfigureServices(services => - { - services.AddSingleton(); - }); ->>>>>>> origin/master - var host = builder.Build(); + var host = builder.Build(); var repository = host.Services.GetService(); var count = repository.InsertAsync(new InsertRequest(), "").Result; Assert.AreEqual(1, count); } [TestMethod] -<<<<<<< HEAD public void 操作記憶體() { var builder = Host.CreateDefaultBuilder() @@ -80,20 +26,6 @@ public void 操作記憶體() var host = builder.Build(); DefaultDbContextFactory.SetUseMemoryDatabase(); - -======= - public void 注入DbContextFactory操作記憶體() - { - var builder = Host.CreateDefaultBuilder() - .ConfigureServices(services => - { - services.AddSingleton(); - }); - var host = builder.Build(); - - DefaultDbContextFactory.UseMemory(); - ->>>>>>> origin/master var repository = host.Services.GetService(); var count = repository.InsertAsync(new InsertRequest(), "").Result; Assert.AreEqual(1, count); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextFactory.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextFactory.cs index 51e12efa..80dea349 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextFactory.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextFactory.cs @@ -8,11 +8,7 @@ namespace Lab.DAL { -<<<<<<< HEAD internal static class DefaultDbContextFactory -======= - public static class DefaultDbContextFactory ->>>>>>> origin/master { private static readonly Lazy s_serviceProviderLazy; private static readonly Lazy s_configurationLazy; @@ -54,10 +50,7 @@ static DefaultDbContextFactory() { var services = new ServiceCollection(); services.AddDbContextFactory(ApplyConfigurePhysical); -<<<<<<< HEAD -======= ->>>>>>> origin/master return services.BuildServiceProvider(); }); s_configurationLazy @@ -113,46 +106,12 @@ public static void ApplyConfigurePhysical(IServiceProvider provider, ; } -<<<<<<< HEAD -======= - public static DbContextOptions CreateEmployeeDbContextOptions() - { - return CreateEmployeeDbContextOptionsBuilder().Options; - } - ->>>>>>> origin/master - public static DbContextOptionsBuilder CreateEmployeeDbContextOptionsBuilder() - { - var configuration = new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .Build(); - var connectionString = configuration.GetConnectionString("DefaultConnection"); - var loggerFactory = LoggerFactory.Create(builder => - { - builder - - //.AddFilter("Microsoft", LogLevel.Warning) - //.AddFilter("System", LogLevel.Warning) - .AddFilter("Lab.DAL", LogLevel.Debug) - .AddConsole() - ; - }); - return new DbContextOptionsBuilder() - .UseSqlServer(connectionString) - .UseLoggerFactory(loggerFactory) - ; - } - public static T GetInstance() { return ServiceProvider.GetService(); } -<<<<<<< HEAD public static void SetUseMemoryDatabase() -======= - public static void UseMemory() ->>>>>>> origin/master { var services = new ServiceCollection(); services.AddDbContextFactory(ApplyConfigureMemory); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs index 265527ed..861980e9 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs @@ -1,5 +1,4 @@ -<<<<<<< HEAD -using System; +using System; using System.ComponentModel.DataAnnotations; namespace Lab.DAL.DomainModel.Employee @@ -21,11 +20,5 @@ public class InsertRequest public string Password { get; set; } public string Remark { get; set; } -======= -namespace Lab.DAL.DomainModel.Employee -{ - public class InsertRequest - { ->>>>>>> origin/master } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs index 64ac474b..01950480 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs @@ -5,24 +5,12 @@ namespace Lab.DAL.EntityModel { - [Table("Employee")] -<<<<<<< HEAD public class Employee { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid Id { get; set; } - [Required] -======= - - public class Employee - { - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.None)] - public Guid Id { get; set; } - ->>>>>>> origin/master public string Name { get; set; } public int? Age { get; set; } @@ -34,19 +22,7 @@ public class Employee public virtual Identity Identity { get; set; } -<<<<<<< HEAD // public virtual ICollection Order { get; set; } - public Employee() - { - // this.Order = new HashSet(); -======= - public virtual ICollection Order { get; set; } - - public Employee() - { - this.Order = new HashSet(); ->>>>>>> origin/master - } } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs index 6d73c256..053412c2 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs @@ -8,7 +8,6 @@ namespace Lab.DAL.EntityModel public class Identity { [Key] -<<<<<<< HEAD [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid EmployeeId { get; set; } @@ -18,16 +17,6 @@ public class Identity [Required] public string Password { get; set; } - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] -======= - [DatabaseGenerated(DatabaseGeneratedOption.None)] - public Guid EmployeeId { get; set; } - - public string Account { get; set; } - - public string Password { get; set; } - ->>>>>>> origin/master public long SequenceId { get; set; } public string Remark { get; set; } From 69c6b41e69e10cc0dc85d5c0d7e0452ec4e5c68b Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Mon, 12 Apr 2021 10:31:06 +0800 Subject: [PATCH 057/424] refactor --- .../Lab.DAL.TestProject/UnitTest1.cs | 21 ++- ...tFactory.cs => DefaultDbContextManager.cs} | 43 +++--- .../Lab.DAL/EmployeeContextFactory.cs | 29 ++++ .../Lab.DAL/EmployeeRepository.cs | 37 +++-- .../Lab.DAL/EntityModel/EmployeeContext.cs | 3 +- .../Lab.DAL/EntityModel/Identity.cs | 9 +- .../Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj | 1 + .../20210412020922_InitialCreate.Designer.cs | 126 ++++++++++++++++++ .../20210412020922_InitialCreate.cs | 87 ++++++++++++ .../EmployeeContextModelSnapshot.cs | 124 +++++++++++++++++ 10 files changed, 433 insertions(+), 47 deletions(-) rename ORM/EFCore/Lab.VirtualDb/Lab.DAL/{DefaultDbContextFactory.cs => DefaultDbContextManager.cs} (65%) create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeContextFactory.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412020922_InitialCreate.Designer.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412020922_InitialCreate.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs index 0ff6538c..a78a01d2 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs @@ -1,4 +1,6 @@ +using System; using Lab.DAL.DomainModel.Employee; +using Lab.DAL.EntityModel; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -8,14 +10,23 @@ namespace Lab.DAL.UnitTest [TestClass] public class UnitTest1 { + [TestMethod] public void 操作真實資料庫() { var builder = Host.CreateDefaultBuilder() .ConfigureServices(services => { services.AddSingleton(); }); - var host = builder.Build(); + var host = builder.Build(); var repository = host.Services.GetService(); - var count = repository.InsertAsync(new InsertRequest(), "").Result; - Assert.AreEqual(1, count); + var count = repository.InsertAsync(new InsertRequest + { + Id = Guid.NewGuid(), + Account = "yao", + Password = "123456", + Name = "余小章", + Age = 18, + Remark = "測試案例,持續航向偉大航道" + }, "").Result; + Assert.AreEqual(2, count); } [TestMethod] @@ -25,10 +36,10 @@ public void 操作記憶體() .ConfigureServices(services => { services.AddSingleton(); }); var host = builder.Build(); - DefaultDbContextFactory.SetUseMemoryDatabase(); + DefaultDbContextManager.SetUseMemoryDatabase(); var repository = host.Services.GetService(); var count = repository.InsertAsync(new InsertRequest(), "").Result; - Assert.AreEqual(1, count); + Assert.AreEqual(2, count); } } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextFactory.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs similarity index 65% rename from ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextFactory.cs rename to ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs index 80dea349..fc32fa3a 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextFactory.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs @@ -8,12 +8,13 @@ namespace Lab.DAL { - internal static class DefaultDbContextFactory + internal static class DefaultDbContextManager { private static readonly Lazy s_serviceProviderLazy; private static readonly Lazy s_configurationLazy; private static ServiceProvider s_serviceProvider; private static IConfiguration s_configuration; + private static readonly ILoggerFactory s_loggerFactory; public static ServiceProvider ServiceProvider { @@ -43,7 +44,7 @@ public static IConfiguration Configuration set => s_configuration = value; } - static DefaultDbContextFactory() + static DefaultDbContextManager() { s_serviceProviderLazy = new Lazy(() => @@ -61,23 +62,23 @@ static DefaultDbContextFactory() .AddJsonFile("appsettings.json"); return configBuilder.Build(); }); + s_loggerFactory = LoggerFactory.Create(builder => + { + builder + + //.AddFilter("Microsoft", LogLevel.Warning) + //.AddFilter("System", LogLevel.Warning) + .AddFilter("Lab.DAL", LogLevel.Debug) + .AddConsole() + ; + }); } public static void ApplyConfigureMemory(IServiceProvider provider, DbContextOptionsBuilder optionsBuilder) { - var loggerFactory = LoggerFactory.Create(builder => - { - builder - - //.AddFilter("Microsoft", LogLevel.Warning) - //.AddFilter("System", LogLevel.Warning) - .AddFilter("Lab.DAL", LogLevel.Debug) - .AddConsole() - ; - }); optionsBuilder.UseInMemoryDatabase("Demo") - .UseLoggerFactory(loggerFactory) + .UseLoggerFactory(s_loggerFactory) ; } @@ -91,18 +92,8 @@ public static void ApplyConfigurePhysical(IServiceProvider provider, } var connectionString = configuration.GetConnectionString("DefaultConnection"); - var loggerFactory = LoggerFactory.Create(builder => - { - builder - - //.AddFilter("Microsoft", LogLevel.Warning) - //.AddFilter("System", LogLevel.Warning) - .AddFilter("Lab.DAL", LogLevel.Debug) - .AddConsole() - ; - }); optionsBuilder.UseSqlServer(connectionString) - .UseLoggerFactory(loggerFactory) + .UseLoggerFactory(s_loggerFactory) ; } @@ -111,10 +102,10 @@ public static T GetInstance() return ServiceProvider.GetService(); } - public static void SetUseMemoryDatabase() + public static void SetUseMemoryDatabase() where TContext : DbContext { var services = new ServiceCollection(); - services.AddDbContextFactory(ApplyConfigureMemory); + services.AddDbContextFactory(ApplyConfigureMemory); ServiceProvider = services.BuildServiceProvider(); } } diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeContextFactory.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeContextFactory.cs new file mode 100644 index 00000000..4609fcd5 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeContextFactory.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; +using Lab.DAL.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Lab.DAL +{ + public class EmployeeContextFactory : IDesignTimeDbContextFactory + { + public EmployeeContext CreateDbContext(string[] args) + { + Console.WriteLine("由設計工具產生 Database,初始化 DbContextOptionsBuilder"); + + var config = DefaultDbContextManager.Configuration; + var connectionString = config.GetConnectionString("DefaultConnection"); + + Console.WriteLine($"由 appsettings.json 讀取連線字串為:{connectionString}"); + + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlServer(connectionString); + Console.WriteLine($"DbContextOptionsBuilder 設定完成"); + + return new EmployeeContext(optionsBuilder.Options); + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs index 42e8066d..a4ff2405 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs @@ -4,7 +4,6 @@ using Lab.DAL.DomainModel.Employee; using Lab.DAL.EntityModel; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; namespace Lab.DAL { @@ -16,7 +15,7 @@ internal IDbContextFactory DbContextFactory { if (this._dbContextFactory == null) { - this._dbContextFactory = DefaultDbContextFactory.GetInstance>(); + this._dbContextFactory = DefaultDbContextManager.GetInstance>(); } return this._dbContextFactory; @@ -30,16 +29,34 @@ public async Task InsertAsync(InsertRequest request, string accessId, CancellationToken cancel = default) { - using var dbContext = this.DbContextFactory.CreateDbContext(); - var id = Guid.NewGuid(); - var toDb = new Employee + await using var dbContext = this.DbContextFactory.CreateDbContext(); + + var id = Guid.NewGuid(); + var toDbEmployee = new Employee + { + Id = id, + Name = request.Name, + Age = request.Age, + Remark = request.Remark, + }; + var toDbIdentity = new Identity { - Id = id, - Name = "yao", - Age = 18, + Account = request.Account, + Password = request.Password, + Remark = request.Remark, + Employee = toDbEmployee }; - await dbContext.Employees.AddAsync(toDb, cancel); - return await dbContext.SaveChangesAsync(cancel); + toDbEmployee.Identity = toDbIdentity; + await dbContext.Employees.AddAsync(toDbEmployee, cancel); + try + { + return await dbContext.SaveChangesAsync(cancel); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } } } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs index 4660cb3f..688a16cc 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs @@ -13,8 +13,6 @@ public class EmployeeContext : DbContext public virtual DbSet Orders { get; set; } - public string ConnectionString { get; } - public EmployeeContext(DbContextOptions options) : base(options) { @@ -36,6 +34,7 @@ public EmployeeContext(DbContextOptions options) } } + // 給 Migration CLI 使用 // 建構函數配置失敗才需要以下處理 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs index 053412c2..a38a1890 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs @@ -8,8 +8,8 @@ namespace Lab.DAL.EntityModel public class Identity { [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public Guid EmployeeId { get; set; } + // [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Employee_Id { get; set; } [Required] public string Account { get; set; } @@ -17,10 +17,11 @@ public class Identity [Required] public string Password { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long SequenceId { get; set; } + public string Remark { get; set; } - public string Remark { get; set; } - + [ForeignKey("Employee_Id")] public virtual Employee Employee { get; set; } } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj index 29ca09ce..dbdf1ac8 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj @@ -9,6 +9,7 @@ + diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412020922_InitialCreate.Designer.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412020922_InitialCreate.Designer.cs new file mode 100644 index 00000000..42d61c8b --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412020922_InitialCreate.Designer.cs @@ -0,0 +1,126 @@ +// +using System; +using Lab.DAL.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Lab.DAL.Migrations +{ + [DbContext(typeof(EmployeeContext))] + [Migration("20210412020922_InitialCreate")] + partial class InitialCreate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Age") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Remark") + .HasColumnType("nvarchar(max)"); + + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .UseIdentityColumn(); + + b.HasKey("Id"); + + b.ToTable("Employees"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => + { + b.Property("Employee_Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Account") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Remark") + .HasColumnType("nvarchar(max)"); + + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .UseIdentityColumn(); + + b.HasKey("Employee_Id"); + + b.ToTable("Identity"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Order", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("EmployeeId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrderTime") + .HasColumnType("datetime2"); + + b.Property("Remark") + .HasColumnType("nvarchar(max)"); + + b.Property("SequenceId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Order"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => + { + b.HasOne("Lab.DAL.EntityModel.Employee", "Employee") + .WithOne("Identity") + .HasForeignKey("Lab.DAL.EntityModel.Identity", "Employee_Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Order", b => + { + b.HasOne("Lab.DAL.EntityModel.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => + { + b.Navigation("Identity"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412020922_InitialCreate.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412020922_InitialCreate.cs new file mode 100644 index 00000000..41e8f2cb --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412020922_InitialCreate.cs @@ -0,0 +1,87 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Lab.DAL.Migrations +{ + public partial class InitialCreate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Employees", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(max)", nullable: true), + Age = table.Column(type: "int", nullable: true), + SequenceId = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Remark = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Employees", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Identity", + columns: table => new + { + Employee_Id = table.Column(type: "uniqueidentifier", nullable: false), + Account = table.Column(type: "nvarchar(max)", nullable: false), + Password = table.Column(type: "nvarchar(max)", nullable: false), + SequenceId = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Remark = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Identity", x => x.Employee_Id); + table.ForeignKey( + name: "FK_Identity_Employees_Employee_Id", + column: x => x.Employee_Id, + principalTable: "Employees", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Order", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + EmployeeId = table.Column(type: "uniqueidentifier", nullable: true), + OrderTime = table.Column(type: "datetime2", nullable: true), + Remark = table.Column(type: "nvarchar(max)", nullable: true), + SequenceId = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Order", x => x.Id); + table.ForeignKey( + name: "FK_Order_Employees_EmployeeId", + column: x => x.EmployeeId, + principalTable: "Employees", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Order_EmployeeId", + table: "Order", + column: "EmployeeId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Identity"); + + migrationBuilder.DropTable( + name: "Order"); + + migrationBuilder.DropTable( + name: "Employees"); + } + } +} diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs new file mode 100644 index 00000000..4fc5046b --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs @@ -0,0 +1,124 @@ +// +using System; +using Lab.DAL.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Lab.DAL.Migrations +{ + [DbContext(typeof(EmployeeContext))] + partial class EmployeeContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Age") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Remark") + .HasColumnType("nvarchar(max)"); + + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .UseIdentityColumn(); + + b.HasKey("Id"); + + b.ToTable("Employees"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => + { + b.Property("Employee_Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Account") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Remark") + .HasColumnType("nvarchar(max)"); + + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .UseIdentityColumn(); + + b.HasKey("Employee_Id"); + + b.ToTable("Identity"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Order", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("EmployeeId") + .HasColumnType("uniqueidentifier"); + + b.Property("OrderTime") + .HasColumnType("datetime2"); + + b.Property("Remark") + .HasColumnType("nvarchar(max)"); + + b.Property("SequenceId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Order"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => + { + b.HasOne("Lab.DAL.EntityModel.Employee", "Employee") + .WithOne("Identity") + .HasForeignKey("Lab.DAL.EntityModel.Identity", "Employee_Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Order", b => + { + b.HasOne("Lab.DAL.EntityModel.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => + { + b.Navigation("Identity"); + }); +#pragma warning restore 612, 618 + } + } +} From c8b846c597ef9b83e56172a498ede34806959021 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Mon, 12 Apr 2021 10:49:30 +0800 Subject: [PATCH 058/424] refactory --- .../Lab.DAL/EntityModel/Employee.cs | 5 ++- .../Lab.DAL/EntityModel/EmployeeContext.cs | 27 ++++++++++++++++ .../Lab.DAL/EntityModel/Identity.cs | 2 +- ... 20210412024817_InitialCreate.Designer.cs} | 19 ++++++++--- ...ate.cs => 20210412024817_InitialCreate.cs} | 32 ++++++++++++++----- .../EmployeeContextModelSnapshot.cs | 17 +++++++--- .../Lab.VirtualDb/Lab.DAL/appsettings.json | 2 +- 7 files changed, 82 insertions(+), 22 deletions(-) rename ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/{20210412020922_InitialCreate.Designer.cs => 20210412024817_InitialCreate.Designer.cs} (88%) rename ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/{20210412020922_InitialCreate.cs => 20210412024817_InitialCreate.cs} (76%) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs index 01950480..dde26937 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs @@ -1,14 +1,14 @@ using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Lab.DAL.EntityModel { + [Table("Employee")] public class Employee { [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [DatabaseGenerated(DatabaseGeneratedOption.None)] public Guid Id { get; set; } public string Name { get; set; } @@ -23,6 +23,5 @@ public class Employee public virtual Identity Identity { get; set; } // public virtual ICollection Order { get; set; } - } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs index 688a16cc..355cf085 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs @@ -13,6 +13,33 @@ public class EmployeeContext : DbContext public virtual DbSet Orders { get; set; } + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(p => + { + p.HasKey(e => e.Id) + .IsClustered(false); + }); + + modelBuilder.Entity(p => + { + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + }); + modelBuilder.Entity(p => + { + p.HasKey(e => e.Employee_Id) + .IsClustered(false); + }); + modelBuilder.Entity(p => + { + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + }); + } + public EmployeeContext(DbContextOptions options) : base(options) { diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs index a38a1890..d1c1fa9f 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs @@ -8,7 +8,7 @@ namespace Lab.DAL.EntityModel public class Identity { [Key] - // [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [DatabaseGenerated(DatabaseGeneratedOption.None)] public Guid Employee_Id { get; set; } [Required] diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412020922_InitialCreate.Designer.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412024817_InitialCreate.Designer.cs similarity index 88% rename from ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412020922_InitialCreate.Designer.cs rename to ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412024817_InitialCreate.Designer.cs index 42d61c8b..438f07ea 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412020922_InitialCreate.Designer.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412024817_InitialCreate.Designer.cs @@ -10,7 +10,7 @@ namespace Lab.DAL.Migrations { [DbContext(typeof(EmployeeContext))] - [Migration("20210412020922_InitialCreate")] + [Migration("20210412024817_InitialCreate")] partial class InitialCreate { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -24,7 +24,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("Age") @@ -41,9 +40,14 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("bigint") .UseIdentityColumn(); - b.HasKey("Id"); + b.HasKey("Id") + .IsClustered(false); - b.ToTable("Employees"); + b.HasIndex("SequenceId") + .IsUnique() + .IsClustered(); + + b.ToTable("Employee"); }); modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => @@ -67,7 +71,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("bigint") .UseIdentityColumn(); - b.HasKey("Employee_Id"); + b.HasKey("Employee_Id") + .IsClustered(false); + + b.HasIndex("SequenceId") + .IsUnique() + .IsClustered(); b.ToTable("Identity"); }); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412020922_InitialCreate.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412024817_InitialCreate.cs similarity index 76% rename from ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412020922_InitialCreate.cs rename to ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412024817_InitialCreate.cs index 41e8f2cb..d4a12d8d 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412020922_InitialCreate.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412024817_InitialCreate.cs @@ -8,7 +8,7 @@ public partial class InitialCreate : Migration protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "Employees", + name: "Employee", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), @@ -20,7 +20,8 @@ protected override void Up(MigrationBuilder migrationBuilder) }, constraints: table => { - table.PrimaryKey("PK_Employees", x => x.Id); + table.PrimaryKey("PK_Employee", x => x.Id) + .Annotation("SqlServer:Clustered", false); }); migrationBuilder.CreateTable( @@ -36,11 +37,12 @@ protected override void Up(MigrationBuilder migrationBuilder) }, constraints: table => { - table.PrimaryKey("PK_Identity", x => x.Employee_Id); + table.PrimaryKey("PK_Identity", x => x.Employee_Id) + .Annotation("SqlServer:Clustered", false); table.ForeignKey( - name: "FK_Identity_Employees_Employee_Id", + name: "FK_Identity_Employee_Employee_Id", column: x => x.Employee_Id, - principalTable: "Employees", + principalTable: "Employee", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); @@ -59,13 +61,27 @@ protected override void Up(MigrationBuilder migrationBuilder) { table.PrimaryKey("PK_Order", x => x.Id); table.ForeignKey( - name: "FK_Order_Employees_EmployeeId", + name: "FK_Order_Employee_EmployeeId", column: x => x.EmployeeId, - principalTable: "Employees", + principalTable: "Employee", principalColumn: "Id", onDelete: ReferentialAction.Restrict); }); + migrationBuilder.CreateIndex( + name: "IX_Employee_SequenceId", + table: "Employee", + column: "SequenceId", + unique: true) + .Annotation("SqlServer:Clustered", true); + + migrationBuilder.CreateIndex( + name: "IX_Identity_SequenceId", + table: "Identity", + column: "SequenceId", + unique: true) + .Annotation("SqlServer:Clustered", true); + migrationBuilder.CreateIndex( name: "IX_Order_EmployeeId", table: "Order", @@ -81,7 +97,7 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "Order"); migrationBuilder.DropTable( - name: "Employees"); + name: "Employee"); } } } diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs index 4fc5046b..226c6cb2 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs @@ -22,7 +22,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); b.Property("Age") @@ -39,9 +38,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("bigint") .UseIdentityColumn(); - b.HasKey("Id"); + b.HasKey("Id") + .IsClustered(false); - b.ToTable("Employees"); + b.HasIndex("SequenceId") + .IsUnique() + .IsClustered(); + + b.ToTable("Employee"); }); modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => @@ -65,7 +69,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("bigint") .UseIdentityColumn(); - b.HasKey("Employee_Id"); + b.HasKey("Employee_Id") + .IsClustered(false); + + b.HasIndex("SequenceId") + .IsUnique() + .IsClustered(); b.ToTable("Identity"); }); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/appsettings.json b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/appsettings.json index 79b166fe..0e917009 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/appsettings.json +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/appsettings.json @@ -1,5 +1,5 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL;Trusted_Connection=True;MultipleActiveResultSets=true" + "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL.Test;Trusted_Connection=True;MultipleActiveResultSets=true" } } \ No newline at end of file From 154ba5dcfec101cc30f66dddb2400d857b7afd11 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Mon, 12 Apr 2021 12:09:41 +0800 Subject: [PATCH 059/424] refactor --- .../Lab.DAL.TestProject/UnitTest1.cs | 47 +++++++++++- .../Lab.DAL/DefaultDbContextManager.cs | 34 +++++++-- .../Employee/InsertOrderRequest.cs | 15 ++++ .../{InsertRequest.cs => NewRequest.cs} | 2 +- .../Lab.DAL/EmployeeRepository.cs | 73 +++++++++++++------ .../Lab.DAL/EntityModel/Employee.cs | 9 ++- .../Lab.DAL/EntityModel/EmployeeContext.cs | 2 +- .../Lab.DAL/EntityModel/Identity.cs | 9 ++- .../Lab.DAL/EntityModel/Order.cs | 24 ------ .../Lab.DAL/EntityModel/OrderHistory.cs | 31 ++++++++ ... 20210412040708_InitialCreate.Designer.cs} | 53 +++++++++----- ...ate.cs => 20210412040708_InitialCreate.cs} | 57 +++++++-------- .../EmployeeContextModelSnapshot.cs | 51 ++++++++----- .../Lab.VirtualDb/Lab.DAL/appsettings.json | 2 +- 14 files changed, 283 insertions(+), 126 deletions(-) create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertOrderRequest.cs rename ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/{InsertRequest.cs => NewRequest.cs} (93%) delete mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Order.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/OrderHistory.cs rename ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/{20210412024817_InitialCreate.Designer.cs => 20210412040708_InitialCreate.Designer.cs} (73%) rename ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/{20210412024817_InitialCreate.cs => 20210412040708_InitialCreate.cs} (75%) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs index a78a01d2..f43a6313 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs @@ -10,14 +10,17 @@ namespace Lab.DAL.UnitTest [TestClass] public class UnitTest1 { + [TestMethod] public void 操作真實資料庫() { + DefaultDbContextManager.Now = new DateTime(1900, 1, 1); + var builder = Host.CreateDefaultBuilder() .ConfigureServices(services => { services.AddSingleton(); }); var host = builder.Build(); var repository = host.Services.GetService(); - var count = repository.InsertAsync(new InsertRequest + var count = repository.NewAsync(new NewRequest { Id = Guid.NewGuid(), Account = "yao", @@ -25,20 +28,56 @@ public void 操作真實資料庫() Name = "余小章", Age = 18, Remark = "測試案例,持續航向偉大航道" - }, "").Result; + }, "TestUser").Result; Assert.AreEqual(2, count); } + [TestMethod] + public void 操作真實資料庫1() + { + //arrange + DefaultDbContextManager.Now = new DateTime(1900, 1, 1); + + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(services => { services.AddSingleton(); }); + var host = builder.Build(); + var repository = host.Services.GetService(); + var id = Guid.NewGuid(); + repository.NewAsync(new NewRequest + { + Id = id, + Account = "yao", + Password = "123456", + Name = "余小章", + Age = 18, + Remark = "測試案例,持續航向偉大航道" + }, "TestUser").Wait(); + + //act + var count = repository.InsertLogAsync(new InsertOrderRequest + { + Employee_Id = id, + Product_Id = "A001", + Product_Name = "羅技滑鼠", + Remark = "測試案例,持續航向偉大航道" + }, "TestUser").Result; + + //assert + Assert.AreEqual(1, count); + } + [TestMethod] public void 操作記憶體() { + DefaultDbContextManager.Now = new DateTime(1900, 1, 1); + DefaultDbContextManager.SetUseMemoryDatabase(); + var builder = Host.CreateDefaultBuilder() .ConfigureServices(services => { services.AddSingleton(); }); var host = builder.Build(); - DefaultDbContextManager.SetUseMemoryDatabase(); var repository = host.Services.GetService(); - var count = repository.InsertAsync(new InsertRequest(), "").Result; + var count = repository.NewAsync(new NewRequest(), "TestUser").Result; Assert.AreEqual(2, count); } } diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs index fc32fa3a..006e4d88 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs @@ -8,13 +8,28 @@ namespace Lab.DAL { - internal static class DefaultDbContextManager + internal class DefaultDbContextManager { private static readonly Lazy s_serviceProviderLazy; private static readonly Lazy s_configurationLazy; + private static readonly ILoggerFactory s_loggerFactory; private static ServiceProvider s_serviceProvider; private static IConfiguration s_configuration; - private static readonly ILoggerFactory s_loggerFactory; + private static DateTime? s_now; + + public static DateTime Now + { + get + { + if (s_now == null) + { + return DateTime.UtcNow; + } + + return s_now.Value; + } + set => s_now = value; + } public static ServiceProvider ServiceProvider { @@ -85,13 +100,13 @@ public static void ApplyConfigureMemory(IServiceProvider provider, public static void ApplyConfigurePhysical(IServiceProvider provider, DbContextOptionsBuilder optionsBuilder) { - var configuration = provider.GetService(); - if (configuration == null) + var config = provider.GetService(); + if (config == null) { - configuration = Configuration; + config = Configuration; } - var connectionString = configuration.GetConnectionString("DefaultConnection"); + var connectionString = config.GetConnectionString("DefaultConnection"); optionsBuilder.UseSqlServer(connectionString) .UseLoggerFactory(s_loggerFactory) ; @@ -102,6 +117,13 @@ public static T GetInstance() return ServiceProvider.GetService(); } + public static void SetUseDefaultDatabase() where TContext : DbContext + { + var services = new ServiceCollection(); + services.AddDbContextFactory(ApplyConfigurePhysical); + ServiceProvider = services.BuildServiceProvider(); + } + public static void SetUseMemoryDatabase() where TContext : DbContext { var services = new ServiceCollection(); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertOrderRequest.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertOrderRequest.cs new file mode 100644 index 00000000..76eb01d6 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertOrderRequest.cs @@ -0,0 +1,15 @@ +using System; + +namespace Lab.DAL.DomainModel.Employee +{ + public class InsertOrderRequest + { + public Guid? Employee_Id { get; set; } + + public string Product_Id { get; set; } + + public string Product_Name { get; set; } + + public string Remark { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/NewRequest.cs similarity index 93% rename from ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs rename to ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/NewRequest.cs index 861980e9..3288a612 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/InsertRequest.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/NewRequest.cs @@ -3,7 +3,7 @@ namespace Lab.DAL.DomainModel.Employee { - public class InsertRequest + public class NewRequest { [Key] public Guid Id { get; set; } diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs index a4ff2405..cbea1ae7 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs @@ -23,40 +23,71 @@ internal IDbContextFactory DbContextFactory set => this._dbContextFactory = value; } + internal DateTime Now + { + get + { + if (this._now == null) + { + return DefaultDbContextManager.Now; + } + + return this._now.Value; + } + set => this._now = value; + } + private IDbContextFactory _dbContextFactory; + private DateTime? _now; + + public async Task InsertLogAsync(InsertOrderRequest request, + string accessId, + CancellationToken cancel = default) + { + await using var dbContext = this.DbContextFactory.CreateDbContext(); + + var toDbOrderHistory = new OrderHistory + { + Employee_Id = request.Employee_Id, + Product_Id = request.Product_Id, + Product_Name = request.Product_Id, + CreateAt = this.Now, + CreateBy = accessId, + Remark = request.Remark, + }; + + await dbContext.OrderHistories.AddAsync(toDbOrderHistory, cancel); + return await dbContext.SaveChangesAsync(cancel); + } - public async Task InsertAsync(InsertRequest request, - string accessId, - CancellationToken cancel = default) + public async Task NewAsync(NewRequest request, + string accessId, + CancellationToken cancel = default) { await using var dbContext = this.DbContextFactory.CreateDbContext(); var id = Guid.NewGuid(); - var toDbEmployee = new Employee + var employeeToDb = new Employee { - Id = id, - Name = request.Name, - Age = request.Age, - Remark = request.Remark, + Id = id, + Name = request.Name, + Age = request.Age, + Remark = request.Remark, + CreateAt = this.Now, + CreateBy = accessId }; - var toDbIdentity = new Identity + var identityToDb = new Identity { Account = request.Account, Password = request.Password, Remark = request.Remark, - Employee = toDbEmployee + Employee = employeeToDb, + CreateAt = this.Now, + CreateBy = accessId }; - toDbEmployee.Identity = toDbIdentity; - await dbContext.Employees.AddAsync(toDbEmployee, cancel); - try - { - return await dbContext.SaveChangesAsync(cancel); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } + employeeToDb.Identity = identityToDb; + await dbContext.Employees.AddAsync(employeeToDb, cancel); + return await dbContext.SaveChangesAsync(cancel); } } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs index dde26937..2c375566 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Employee.cs @@ -11,6 +11,7 @@ public class Employee [DatabaseGenerated(DatabaseGeneratedOption.None)] public Guid Id { get; set; } + [Required] public string Name { get; set; } public int? Age { get; set; } @@ -20,8 +21,12 @@ public class Employee public string Remark { get; set; } - public virtual Identity Identity { get; set; } + [Required] + public DateTime CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } - // public virtual ICollection Order { get; set; } + public virtual Identity Identity { get; set; } } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs index 355cf085..11f97a07 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs @@ -11,7 +11,7 @@ public class EmployeeContext : DbContext public virtual DbSet Identities { get; set; } - public virtual DbSet Orders { get; set; } + public virtual DbSet OrderHistories {get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs index d1c1fa9f..472f9bcb 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Identity.cs @@ -19,7 +19,14 @@ public class Identity [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long SequenceId { get; set; } - public string Remark { get; set; } + + public string Remark { get; set; } + + [Required] + public DateTime CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } [ForeignKey("Employee_Id")] public virtual Employee Employee { get; set; } diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Order.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Order.cs deleted file mode 100644 index aca86633..00000000 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/Order.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Lab.DAL.EntityModel -{ - [Table("Order")] - public class Order - { - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.None)] - public Guid Id { get; set; } - - public Guid? EmployeeId { get; set; } - - public DateTime? OrderTime { get; set; } - - public string Remark { get; set; } - - public long SequenceId { get; set; } - - public virtual Employee Employee { get; set; } - } -} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/OrderHistory.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/OrderHistory.cs new file mode 100644 index 00000000..ca49d134 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/OrderHistory.cs @@ -0,0 +1,31 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.DAL.EntityModel +{ + [Table("OrderHistory")] + public class OrderHistory + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + public Guid? Employee_Id { get; set; } + + public string Remark { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Product_Id { get; set; } + + public string Product_Name { get; set; } + + [Required] + public DateTime CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412024817_InitialCreate.Designer.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412040708_InitialCreate.Designer.cs similarity index 73% rename from ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412024817_InitialCreate.Designer.cs rename to ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412040708_InitialCreate.Designer.cs index 438f07ea..ef3fc502 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412024817_InitialCreate.Designer.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412040708_InitialCreate.Designer.cs @@ -10,7 +10,7 @@ namespace Lab.DAL.Migrations { [DbContext(typeof(EmployeeContext))] - [Migration("20210412024817_InitialCreate")] + [Migration("20210412040708_InitialCreate")] partial class InitialCreate { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -29,7 +29,15 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Age") .HasColumnType("int"); + b.Property("CreateAt") + .HasColumnType("datetime2"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b.Property("Name") + .IsRequired() .HasColumnType("nvarchar(max)"); b.Property("Remark") @@ -59,6 +67,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(max)"); + b.Property("CreateAt") + .HasColumnType("datetime2"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b.Property("Password") .IsRequired() .HasColumnType("nvarchar(max)"); @@ -81,28 +96,39 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Identity"); }); - modelBuilder.Entity("Lab.DAL.EntityModel.Order", b => + modelBuilder.Entity("Lab.DAL.EntityModel.OrderHistory", b => { b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("EmployeeId") + b.Property("CreateAt") + .HasColumnType("datetime2"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Employee_Id") .HasColumnType("uniqueidentifier"); - b.Property("OrderTime") - .HasColumnType("datetime2"); + b.Property("Product_Id") + .HasColumnType("nvarchar(max)"); + + b.Property("Product_Name") + .HasColumnType("nvarchar(max)"); b.Property("Remark") .HasColumnType("nvarchar(max)"); b.Property("SequenceId") - .HasColumnType("bigint"); + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .UseIdentityColumn(); b.HasKey("Id"); - b.HasIndex("EmployeeId"); - - b.ToTable("Order"); + b.ToTable("OrderHistory"); }); modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => @@ -116,15 +142,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Employee"); }); - modelBuilder.Entity("Lab.DAL.EntityModel.Order", b => - { - b.HasOne("Lab.DAL.EntityModel.Employee", "Employee") - .WithMany() - .HasForeignKey("EmployeeId"); - - b.Navigation("Employee"); - }); - modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => { b.Navigation("Identity"); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412024817_InitialCreate.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412040708_InitialCreate.cs similarity index 75% rename from ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412024817_InitialCreate.cs rename to ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412040708_InitialCreate.cs index d4a12d8d..44edf0c5 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412024817_InitialCreate.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412040708_InitialCreate.cs @@ -12,11 +12,13 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: true), + Name = table.Column(type: "nvarchar(max)", nullable: false), Age = table.Column(type: "int", nullable: true), SequenceId = table.Column(type: "bigint", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), - Remark = table.Column(type: "nvarchar(max)", nullable: true) + Remark = table.Column(type: "nvarchar(max)", nullable: true), + CreateAt = table.Column(type: "datetime2", nullable: false), + CreateBy = table.Column(type: "nvarchar(max)", nullable: false) }, constraints: table => { @@ -25,47 +27,47 @@ protected override void Up(MigrationBuilder migrationBuilder) }); migrationBuilder.CreateTable( - name: "Identity", + name: "OrderHistory", columns: table => new { - Employee_Id = table.Column(type: "uniqueidentifier", nullable: false), - Account = table.Column(type: "nvarchar(max)", nullable: false), - Password = table.Column(type: "nvarchar(max)", nullable: false), + Id = table.Column(type: "uniqueidentifier", nullable: false), + Employee_Id = table.Column(type: "uniqueidentifier", nullable: true), + Remark = table.Column(type: "nvarchar(max)", nullable: true), SequenceId = table.Column(type: "bigint", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), - Remark = table.Column(type: "nvarchar(max)", nullable: true) + Product_Id = table.Column(type: "nvarchar(max)", nullable: true), + Product_Name = table.Column(type: "nvarchar(max)", nullable: true), + CreateAt = table.Column(type: "datetime2", nullable: false), + CreateBy = table.Column(type: "nvarchar(max)", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Identity", x => x.Employee_Id) - .Annotation("SqlServer:Clustered", false); - table.ForeignKey( - name: "FK_Identity_Employee_Employee_Id", - column: x => x.Employee_Id, - principalTable: "Employee", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + table.PrimaryKey("PK_OrderHistory", x => x.Id); }); migrationBuilder.CreateTable( - name: "Order", + name: "Identity", columns: table => new { - Id = table.Column(type: "uniqueidentifier", nullable: false), - EmployeeId = table.Column(type: "uniqueidentifier", nullable: true), - OrderTime = table.Column(type: "datetime2", nullable: true), - Remark = table.Column(type: "nvarchar(max)", nullable: true), + Employee_Id = table.Column(type: "uniqueidentifier", nullable: false), + Account = table.Column(type: "nvarchar(max)", nullable: false), + Password = table.Column(type: "nvarchar(max)", nullable: false), SequenceId = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Remark = table.Column(type: "nvarchar(max)", nullable: true), + CreateAt = table.Column(type: "datetime2", nullable: false), + CreateBy = table.Column(type: "nvarchar(max)", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Order", x => x.Id); + table.PrimaryKey("PK_Identity", x => x.Employee_Id) + .Annotation("SqlServer:Clustered", false); table.ForeignKey( - name: "FK_Order_Employee_EmployeeId", - column: x => x.EmployeeId, + name: "FK_Identity_Employee_Employee_Id", + column: x => x.Employee_Id, principalTable: "Employee", principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateIndex( @@ -81,11 +83,6 @@ protected override void Up(MigrationBuilder migrationBuilder) column: "SequenceId", unique: true) .Annotation("SqlServer:Clustered", true); - - migrationBuilder.CreateIndex( - name: "IX_Order_EmployeeId", - table: "Order", - column: "EmployeeId"); } protected override void Down(MigrationBuilder migrationBuilder) @@ -94,7 +91,7 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "Identity"); migrationBuilder.DropTable( - name: "Order"); + name: "OrderHistory"); migrationBuilder.DropTable( name: "Employee"); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs index 226c6cb2..995a5f12 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs @@ -27,7 +27,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Age") .HasColumnType("int"); + b.Property("CreateAt") + .HasColumnType("datetime2"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b.Property("Name") + .IsRequired() .HasColumnType("nvarchar(max)"); b.Property("Remark") @@ -57,6 +65,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(max)"); + b.Property("CreateAt") + .HasColumnType("datetime2"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b.Property("Password") .IsRequired() .HasColumnType("nvarchar(max)"); @@ -79,28 +94,39 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Identity"); }); - modelBuilder.Entity("Lab.DAL.EntityModel.Order", b => + modelBuilder.Entity("Lab.DAL.EntityModel.OrderHistory", b => { b.Property("Id") + .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("EmployeeId") + b.Property("CreateAt") + .HasColumnType("datetime2"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Employee_Id") .HasColumnType("uniqueidentifier"); - b.Property("OrderTime") - .HasColumnType("datetime2"); + b.Property("Product_Id") + .HasColumnType("nvarchar(max)"); + + b.Property("Product_Name") + .HasColumnType("nvarchar(max)"); b.Property("Remark") .HasColumnType("nvarchar(max)"); b.Property("SequenceId") - .HasColumnType("bigint"); + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .UseIdentityColumn(); b.HasKey("Id"); - b.HasIndex("EmployeeId"); - - b.ToTable("Order"); + b.ToTable("OrderHistory"); }); modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => @@ -114,15 +140,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Employee"); }); - modelBuilder.Entity("Lab.DAL.EntityModel.Order", b => - { - b.HasOne("Lab.DAL.EntityModel.Employee", "Employee") - .WithMany() - .HasForeignKey("EmployeeId"); - - b.Navigation("Employee"); - }); - modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => { b.Navigation("Identity"); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/appsettings.json b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/appsettings.json index 0e917009..79b166fe 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/appsettings.json +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/appsettings.json @@ -1,5 +1,5 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL.Test;Trusted_Connection=True;MultipleActiveResultSets=true" + "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL;Trusted_Connection=True;MultipleActiveResultSets=true" } } \ No newline at end of file From d35db10ace2811312d511676f7af85077aef122b Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 14 Apr 2021 11:47:35 +0800 Subject: [PATCH 060/424] refactor --- .../EmployeeRepositoryUnitTests.cs | 197 ++++++++++++++++++ .../Lab.DAL.TestProject.csproj | 1 + .../Lab.DAL.TestProject/UnitTest1.cs | 84 -------- .../Lab.DAL/DefaultDbContextManager.cs | 3 +- .../Lab.DAL/EmployeeRepository.cs | 2 + .../Lab.DAL/EntityModel/EmployeeContext.cs | 76 +++---- 6 files changed, 240 insertions(+), 123 deletions(-) create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs delete mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs new file mode 100644 index 00000000..7134e185 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -0,0 +1,197 @@ +using System; +using System.IO; +using System.Linq; +using Lab.DAL.DomainModel.Employee; +using Lab.DAL.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.DAL.UnitTest +{ + [TestClass] + public class EmployeeRepositoryUnitTests + { + private static readonly DbContextOptions s_employeeContextOptions; + + static EmployeeRepositoryUnitTests() + { + s_employeeContextOptions = CreateDbContextOptions(); + } + + [AssemblyCleanup] + public static void AssemblyCleanup() + { + //刪除測試資料庫 + Console.WriteLine("AssemblyCleanup"); + + using var db = new EmployeeContext(s_employeeContextOptions); + db.Database.EnsureDeleted(); + } + + [AssemblyInitialize] + public static void AssemblyInitialize(TestContext context) + { + //刪除測試資料庫 + Console.WriteLine("AssemblyInitialize"); + using var db = new EmployeeContext(s_employeeContextOptions); + db.Database.EnsureDeleted(); + + //建立測試資料庫 + db.Database.EnsureCreated(); + } + + [ClassCleanup] + public static void ClassCleanup() + { + //刪除測試資料表 + Console.WriteLine("ClassCleanup"); + DeleteTestDataRow(); + } + + [ClassInitialize] + public static void ClassInitialize(TestContext context) + { + //刪除測試資料表 + Console.WriteLine("ClassInitialize"); + DeleteTestDataRow(); + } + + [TestMethod] + public void 操作真實資料庫_手動取得執行個體() + { + //arrange + DefaultDbContextManager.Now = new DateTime(1900, 1, 1); + var repository = new EmployeeRepository(); + var id = Guid.NewGuid(); + repository.NewAsync(new NewRequest + { + Id = id, + Account = "yao", + Password = "123456", + Name = "余小章", + Age = 18, + Remark = "測試案例,持續航向偉大航道" + }, "TestUser").Wait(); + + //act + var count = repository.InsertLogAsync(new InsertOrderRequest + { + Employee_Id = id, + Product_Id = "A001", + Product_Name = "羅技滑鼠", + Remark = "測試案例,持續航向偉大航道" + }, "TestUser").Result; + + //assert + Assert.AreEqual(1, count); + } + + [TestMethod] + public void 操作真實資料庫_從容器取得執行個體() + { + //arrange + DefaultDbContextManager.Now = new DateTime(1900, 1, 1); + + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(services => { services.AddSingleton(); }); + var host = builder.Build(); + + var repository = host.Services.GetService(); + var id = Guid.NewGuid(); + + //act + var count = repository.NewAsync(new NewRequest + { + Id = id, + Account = "yao", + Password = "123456", + Name = "余小章", + Age = 18, + }, "TestUser").Result; + + //assert + Assert.AreEqual(2, count); + var db = new EmployeeContext(s_employeeContextOptions); + + // var actual = db.Employees.FirstOrDefault(); + var actual = db.Employees + .Include(p => p.Identity) + .AsNoTracking() + .FirstOrDefault(); + + Assert.AreEqual(actual.Name, "余小章"); + Assert.AreEqual(actual.Age, 18); + Assert.AreEqual(actual.Identity.Account, "yao"); + Assert.AreEqual(actual.Identity.Password, "123456"); + } + + [TestMethod] + public void 操作記憶體() + { + DefaultDbContextManager.Now = new DateTime(1900, 1, 1); + DefaultDbContextManager.SetUseMemoryDatabase(); + + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(services => { services.AddSingleton(); }); + var host = builder.Build(); + + var repository = host.Services.GetService(); + var count = repository.NewAsync(new NewRequest(), "TestUser").Result; + Assert.AreEqual(2, count); + } + + private static DbContextOptions CreateDbContextOptions() + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + + var configRoot = configBuilder.Build(); + var connectionString = configRoot.GetConnectionString("DefaultConnection"); + + var loggerFactory = LoggerFactory.Create(builder => + { + builder + + //.AddFilter("Microsoft", LogLevel.Warning) + //.AddFilter("System", LogLevel.Warning) + .AddFilter("Lab.DAL", LogLevel.Debug) + .AddConsole() + ; + }); + return new DbContextOptionsBuilder() + .UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + .Options; + } + + private static void DeleteTestDataRow() + { + var dbContextOptions = s_employeeContextOptions; + using var db = new EmployeeContext(dbContextOptions); + var deleteCommand = GetDeleteAllRecordCommand(); + db.Database.ExecuteSqlRaw(deleteCommand); + } + + private static string GetDeleteAllRecordCommand() + { + var sql = @" +-- disable referential integrity +EXEC sp_MSForEachTable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL' + + +EXEC sp_MSForEachTable 'DELETE FROM ?' + + +-- enable referential integrity again +EXEC sp_MSForEachTable 'ALTER TABLE ? WITH CHECK CHECK CONSTRAINT ALL' +"; + + return sql; + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj index e94f8450..04554d62 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj @@ -8,6 +8,7 @@ + diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs deleted file mode 100644 index f43a6313..00000000 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/UnitTest1.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using Lab.DAL.DomainModel.Employee; -using Lab.DAL.EntityModel; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Lab.DAL.UnitTest -{ - [TestClass] - public class UnitTest1 - { - - [TestMethod] - public void 操作真實資料庫() - { - DefaultDbContextManager.Now = new DateTime(1900, 1, 1); - - var builder = Host.CreateDefaultBuilder() - .ConfigureServices(services => { services.AddSingleton(); }); - var host = builder.Build(); - var repository = host.Services.GetService(); - var count = repository.NewAsync(new NewRequest - { - Id = Guid.NewGuid(), - Account = "yao", - Password = "123456", - Name = "余小章", - Age = 18, - Remark = "測試案例,持續航向偉大航道" - }, "TestUser").Result; - Assert.AreEqual(2, count); - } - - [TestMethod] - public void 操作真實資料庫1() - { - //arrange - DefaultDbContextManager.Now = new DateTime(1900, 1, 1); - - var builder = Host.CreateDefaultBuilder() - .ConfigureServices(services => { services.AddSingleton(); }); - var host = builder.Build(); - var repository = host.Services.GetService(); - var id = Guid.NewGuid(); - repository.NewAsync(new NewRequest - { - Id = id, - Account = "yao", - Password = "123456", - Name = "余小章", - Age = 18, - Remark = "測試案例,持續航向偉大航道" - }, "TestUser").Wait(); - - //act - var count = repository.InsertLogAsync(new InsertOrderRequest - { - Employee_Id = id, - Product_Id = "A001", - Product_Name = "羅技滑鼠", - Remark = "測試案例,持續航向偉大航道" - }, "TestUser").Result; - - //assert - Assert.AreEqual(1, count); - } - - [TestMethod] - public void 操作記憶體() - { - DefaultDbContextManager.Now = new DateTime(1900, 1, 1); - DefaultDbContextManager.SetUseMemoryDatabase(); - - var builder = Host.CreateDefaultBuilder() - .ConfigureServices(services => { services.AddSingleton(); }); - var host = builder.Build(); - - var repository = host.Services.GetService(); - var count = repository.NewAsync(new NewRequest(), "TestUser").Result; - Assert.AreEqual(2, count); - } - } -} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs index 006e4d88..82237866 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs @@ -10,6 +10,7 @@ namespace Lab.DAL { internal class DefaultDbContextManager { + public static readonly bool[] Migrated = {false}; private static readonly Lazy s_serviceProviderLazy; private static readonly Lazy s_configurationLazy; private static readonly ILoggerFactory s_loggerFactory; @@ -116,7 +117,7 @@ public static T GetInstance() { return ServiceProvider.GetService(); } - + public static void SetUseDefaultDatabase() where TContext : DbContext { var services = new ServiceCollection(); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs index cbea1ae7..bb49074f 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs @@ -76,6 +76,7 @@ public async Task NewAsync(NewRequest request, CreateAt = this.Now, CreateBy = accessId }; + var identityToDb = new Identity { Account = request.Account, @@ -85,6 +86,7 @@ public async Task NewAsync(NewRequest request, CreateAt = this.Now, CreateBy = accessId }; + employeeToDb.Identity = identityToDb; await dbContext.Employees.AddAsync(employeeToDb, cancel); return await dbContext.SaveChangesAsync(cancel); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs index 11f97a07..88cf3a78 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs @@ -5,13 +5,48 @@ namespace Lab.DAL.EntityModel { public class EmployeeContext : DbContext { - private static readonly bool[] s_migrated = {false}; - public virtual DbSet Employees { get; set; } public virtual DbSet Identities { get; set; } - public virtual DbSet OrderHistories {get; set; } + public virtual DbSet OrderHistories { get; set; } + + public EmployeeContext(DbContextOptions options) + : base(options) + { + if (DefaultDbContextManager.Migrated[0]) + { + return; + } + + lock (DefaultDbContextManager.Migrated) + { + if (DefaultDbContextManager.Migrated[0] == false) + { + var memoryOptions = options.FindExtension(); + if (memoryOptions == null) + { + this.Database.Migrate(); + } + + DefaultDbContextManager.Migrated[0] = true; + } + } + } + + // 給 Migration CLI 使用 + // 建構函數配置失敗才需要以下處理 + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + // var connectionString = + // "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL.UnitTest;Trusted_Connection=True;MultipleActiveResultSets=true"; + // + // // var connectionString = this._connectionString; + // if (optionsBuilder.IsConfigured == false) + // { + // optionsBuilder.UseSqlServer(connectionString); + // } + } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -39,40 +74,5 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .IsClustered(); }); } - - public EmployeeContext(DbContextOptions options) - : base(options) - { - if (s_migrated[0] == false) - { - lock (s_migrated) - { - if (s_migrated[0] == false) - { - var memoryOptions = options.FindExtension(); - if (memoryOptions == null) - { - this.Database.Migrate(); - } - - s_migrated[0] = true; - } - } - } - } - - // 給 Migration CLI 使用 - // 建構函數配置失敗才需要以下處理 - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - // var connectionString = - // "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL.UnitTest;Trusted_Connection=True;MultipleActiveResultSets=true"; - // - // // var connectionString = this._connectionString; - // if (optionsBuilder.IsConfigured == false) - // { - // optionsBuilder.UseSqlServer(connectionString); - // } - } } } \ No newline at end of file From 2123ba57c71e05436027b210c55acf5319cce830 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Wed, 14 Apr 2021 13:08:21 +0800 Subject: [PATCH 061/424] refactor --- .../Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj | 1 - .../20210412040708_InitialCreate.Designer.cs | 152 ------------------ ...ate.cs => 20210414050400_InitialCreate.cs} | 0 3 files changed, 153 deletions(-) delete mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412040708_InitialCreate.Designer.cs rename ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/{20210412040708_InitialCreate.cs => 20210414050400_InitialCreate.cs} (100%) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj index dbdf1ac8..bdc54140 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj @@ -8,7 +8,6 @@ - diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412040708_InitialCreate.Designer.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412040708_InitialCreate.Designer.cs deleted file mode 100644 index ef3fc502..00000000 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412040708_InitialCreate.Designer.cs +++ /dev/null @@ -1,152 +0,0 @@ -// -using System; -using Lab.DAL.EntityModel; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Lab.DAL.Migrations -{ - [DbContext(typeof(EmployeeContext))] - [Migration("20210412040708_InitialCreate")] - partial class InitialCreate - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .UseIdentityColumns() - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.0"); - - modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.Property("Age") - .HasColumnType("int"); - - b.Property("CreateAt") - .HasColumnType("datetime2"); - - b.Property("CreateBy") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Remark") - .HasColumnType("nvarchar(max)"); - - b.Property("SequenceId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .UseIdentityColumn(); - - b.HasKey("Id") - .IsClustered(false); - - b.HasIndex("SequenceId") - .IsUnique() - .IsClustered(); - - b.ToTable("Employee"); - }); - - modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => - { - b.Property("Employee_Id") - .HasColumnType("uniqueidentifier"); - - b.Property("Account") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("CreateAt") - .HasColumnType("datetime2"); - - b.Property("CreateBy") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Password") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Remark") - .HasColumnType("nvarchar(max)"); - - b.Property("SequenceId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .UseIdentityColumn(); - - b.HasKey("Employee_Id") - .IsClustered(false); - - b.HasIndex("SequenceId") - .IsUnique() - .IsClustered(); - - b.ToTable("Identity"); - }); - - modelBuilder.Entity("Lab.DAL.EntityModel.OrderHistory", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CreateAt") - .HasColumnType("datetime2"); - - b.Property("CreateBy") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Employee_Id") - .HasColumnType("uniqueidentifier"); - - b.Property("Product_Id") - .HasColumnType("nvarchar(max)"); - - b.Property("Product_Name") - .HasColumnType("nvarchar(max)"); - - b.Property("Remark") - .HasColumnType("nvarchar(max)"); - - b.Property("SequenceId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .UseIdentityColumn(); - - b.HasKey("Id"); - - b.ToTable("OrderHistory"); - }); - - modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => - { - b.HasOne("Lab.DAL.EntityModel.Employee", "Employee") - .WithOne("Identity") - .HasForeignKey("Lab.DAL.EntityModel.Identity", "Employee_Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Employee"); - }); - - modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => - { - b.Navigation("Identity"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412040708_InitialCreate.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210414050400_InitialCreate.cs similarity index 100% rename from ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210412040708_InitialCreate.cs rename to ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210414050400_InitialCreate.cs From 7b055dcf089ccb1947317e776b20426927206b32 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Apr 2021 08:57:47 +0800 Subject: [PATCH 062/424] refactor --- .../EmployeeRepositoryUnitTests.cs | 16 ++++++++-------- .../Lab.DAL/DefaultDbContextManager.cs | 2 +- .../Lab.DAL/EmployeeContextFactory.cs | 8 ++++---- .../Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs | 6 +++--- .../{EmployeeContext.cs => EmployeeDbContext.cs} | 4 ++-- .../Migrations/EmployeeContextModelSnapshot.cs | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) rename ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/{EmployeeContext.cs => EmployeeDbContext.cs} (95%) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs index 7134e185..16808115 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -15,7 +15,7 @@ namespace Lab.DAL.UnitTest [TestClass] public class EmployeeRepositoryUnitTests { - private static readonly DbContextOptions s_employeeContextOptions; + private static readonly DbContextOptions s_employeeContextOptions; static EmployeeRepositoryUnitTests() { @@ -28,7 +28,7 @@ public static void AssemblyCleanup() //刪除測試資料庫 Console.WriteLine("AssemblyCleanup"); - using var db = new EmployeeContext(s_employeeContextOptions); + using var db = new EmployeeDbContext(s_employeeContextOptions); db.Database.EnsureDeleted(); } @@ -37,7 +37,7 @@ public static void AssemblyInitialize(TestContext context) { //刪除測試資料庫 Console.WriteLine("AssemblyInitialize"); - using var db = new EmployeeContext(s_employeeContextOptions); + using var db = new EmployeeDbContext(s_employeeContextOptions); db.Database.EnsureDeleted(); //建立測試資料庫 @@ -115,7 +115,7 @@ public void 操作真實資料庫_從容器取得執行個體() //assert Assert.AreEqual(2, count); - var db = new EmployeeContext(s_employeeContextOptions); + var db = new EmployeeDbContext(s_employeeContextOptions); // var actual = db.Employees.FirstOrDefault(); var actual = db.Employees @@ -133,7 +133,7 @@ public void 操作真實資料庫_從容器取得執行個體() public void 操作記憶體() { DefaultDbContextManager.Now = new DateTime(1900, 1, 1); - DefaultDbContextManager.SetUseMemoryDatabase(); + DefaultDbContextManager.SetUseMemoryDatabase(); var builder = Host.CreateDefaultBuilder() .ConfigureServices(services => { services.AddSingleton(); }); @@ -144,7 +144,7 @@ public void 操作記憶體() Assert.AreEqual(2, count); } - private static DbContextOptions CreateDbContextOptions() + private static DbContextOptions CreateDbContextOptions() { var configBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) @@ -163,7 +163,7 @@ private static DbContextOptions CreateDbContextOptions() .AddConsole() ; }); - return new DbContextOptionsBuilder() + return new DbContextOptionsBuilder() .UseSqlServer(connectionString) .UseLoggerFactory(loggerFactory) .Options; @@ -172,7 +172,7 @@ private static DbContextOptions CreateDbContextOptions() private static void DeleteTestDataRow() { var dbContextOptions = s_employeeContextOptions; - using var db = new EmployeeContext(dbContextOptions); + using var db = new EmployeeDbContext(dbContextOptions); var deleteCommand = GetDeleteAllRecordCommand(); db.Database.ExecuteSqlRaw(deleteCommand); } diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs index 82237866..38c2be1c 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs @@ -66,7 +66,7 @@ static DefaultDbContextManager() new Lazy(() => { var services = new ServiceCollection(); - services.AddDbContextFactory(ApplyConfigurePhysical); + services.AddDbContextFactory(ApplyConfigurePhysical); return services.BuildServiceProvider(); }); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeContextFactory.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeContextFactory.cs index 4609fcd5..58195dd9 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeContextFactory.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeContextFactory.cs @@ -8,9 +8,9 @@ namespace Lab.DAL { - public class EmployeeContextFactory : IDesignTimeDbContextFactory + public class EmployeeContextFactory : IDesignTimeDbContextFactory { - public EmployeeContext CreateDbContext(string[] args) + public EmployeeDbContext CreateDbContext(string[] args) { Console.WriteLine("由設計工具產生 Database,初始化 DbContextOptionsBuilder"); @@ -19,11 +19,11 @@ public EmployeeContext CreateDbContext(string[] args) Console.WriteLine($"由 appsettings.json 讀取連線字串為:{connectionString}"); - var optionsBuilder = new DbContextOptionsBuilder(); + var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlServer(connectionString); Console.WriteLine($"DbContextOptionsBuilder 設定完成"); - return new EmployeeContext(optionsBuilder.Options); + return new EmployeeDbContext(optionsBuilder.Options); } } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs index bb49074f..e0fef74c 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs @@ -9,13 +9,13 @@ namespace Lab.DAL { public class EmployeeRepository { - internal IDbContextFactory DbContextFactory + internal IDbContextFactory DbContextFactory { get { if (this._dbContextFactory == null) { - this._dbContextFactory = DefaultDbContextManager.GetInstance>(); + this._dbContextFactory = DefaultDbContextManager.GetInstance>(); } return this._dbContextFactory; @@ -37,7 +37,7 @@ internal DateTime Now set => this._now = value; } - private IDbContextFactory _dbContextFactory; + private IDbContextFactory _dbContextFactory; private DateTime? _now; public async Task InsertLogAsync(InsertOrderRequest request, diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs similarity index 95% rename from ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs rename to ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs index 88cf3a78..8dfebd40 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeContext.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs @@ -3,7 +3,7 @@ namespace Lab.DAL.EntityModel { - public class EmployeeContext : DbContext + public class EmployeeDbContext : DbContext { public virtual DbSet Employees { get; set; } @@ -11,7 +11,7 @@ public class EmployeeContext : DbContext public virtual DbSet OrderHistories { get; set; } - public EmployeeContext(DbContextOptions options) + public EmployeeDbContext(DbContextOptions options) : base(options) { if (DefaultDbContextManager.Migrated[0]) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs index 995a5f12..23f95f8e 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs @@ -8,7 +8,7 @@ namespace Lab.DAL.Migrations { - [DbContext(typeof(EmployeeContext))] + [DbContext(typeof(EmployeeDbContext))] partial class EmployeeContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) From 6fa0e35838b00870435af18fe393b041b0d5422f Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Apr 2021 09:41:21 +0800 Subject: [PATCH 063/424] refactor --- .../Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs index 8dfebd40..32c415b6 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs @@ -48,6 +48,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) // } } + //管理索引 protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(p => From 56a8c7bd22604005555cd0b36a4f5dfe22a96e56 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Apr 2021 09:56:30 +0800 Subject: [PATCH 064/424] refactor --- .../20210415015614_InitialCreate.Designer.cs | 152 ++++++++++++++++++ ...ate.cs => 20210415015614_InitialCreate.cs} | 0 ...t.cs => EmployeeDbContextModelSnapshot.cs} | 2 +- 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210415015614_InitialCreate.Designer.cs rename ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/{20210414050400_InitialCreate.cs => 20210415015614_InitialCreate.cs} (100%) rename ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/{EmployeeContextModelSnapshot.cs => EmployeeDbContextModelSnapshot.cs} (98%) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210415015614_InitialCreate.Designer.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210415015614_InitialCreate.Designer.cs new file mode 100644 index 00000000..da6dc416 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210415015614_InitialCreate.Designer.cs @@ -0,0 +1,152 @@ +// +using System; +using Lab.DAL.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Lab.DAL.Migrations +{ + [DbContext(typeof(EmployeeDbContext))] + [Migration("20210415015614_InitialCreate")] + partial class InitialCreate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseIdentityColumns() + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Age") + .HasColumnType("int"); + + b.Property("CreateAt") + .HasColumnType("datetime2"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Remark") + .HasColumnType("nvarchar(max)"); + + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .UseIdentityColumn(); + + b.HasKey("Id") + .IsClustered(false); + + b.HasIndex("SequenceId") + .IsUnique() + .IsClustered(); + + b.ToTable("Employee"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => + { + b.Property("Employee_Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Account") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreateAt") + .HasColumnType("datetime2"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Remark") + .HasColumnType("nvarchar(max)"); + + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .UseIdentityColumn(); + + b.HasKey("Employee_Id") + .IsClustered(false); + + b.HasIndex("SequenceId") + .IsUnique() + .IsClustered(); + + b.ToTable("Identity"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.OrderHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreateAt") + .HasColumnType("datetime2"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Employee_Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Product_Id") + .HasColumnType("nvarchar(max)"); + + b.Property("Product_Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Remark") + .HasColumnType("nvarchar(max)"); + + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .UseIdentityColumn(); + + b.HasKey("Id"); + + b.ToTable("OrderHistory"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => + { + b.HasOne("Lab.DAL.EntityModel.Employee", "Employee") + .WithOne("Identity") + .HasForeignKey("Lab.DAL.EntityModel.Identity", "Employee_Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => + { + b.Navigation("Identity"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210414050400_InitialCreate.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210415015614_InitialCreate.cs similarity index 100% rename from ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210414050400_InitialCreate.cs rename to ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/20210415015614_InitialCreate.cs diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeDbContextModelSnapshot.cs similarity index 98% rename from ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs rename to ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeDbContextModelSnapshot.cs index 23f95f8e..d9b6dd49 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeContextModelSnapshot.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Migrations/EmployeeDbContextModelSnapshot.cs @@ -9,7 +9,7 @@ namespace Lab.DAL.Migrations { [DbContext(typeof(EmployeeDbContext))] - partial class EmployeeContextModelSnapshot : ModelSnapshot + partial class EmployeeDbContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { From 2f3bf4b1046668fdf2b11936a9ebadd05ccf62a9 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Apr 2021 11:23:02 +0800 Subject: [PATCH 065/424] refactor --- .../Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs | 4 ++-- .../Lab.DAL.TestProject/Lab.DAL.TestProject.csproj | 8 +++----- ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj | 6 +++--- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs index 16808115..f9a2f333 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -61,7 +61,7 @@ public static void ClassInitialize(TestContext context) } [TestMethod] - public void 操作真實資料庫_手動取得執行個體() + public void 操作真實資料庫_手動取得Repository執行個體() { //arrange DefaultDbContextManager.Now = new DateTime(1900, 1, 1); @@ -91,7 +91,7 @@ public void 操作真實資料庫_手動取得執行個體() } [TestMethod] - public void 操作真實資料庫_從容器取得執行個體() + public void 操作真實資料庫_從容器取得Repository執行個體() { //arrange DefaultDbContextManager.Now = new DateTime(1900, 1, 1); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj index 04554d62..d1e58815 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj @@ -14,11 +14,9 @@ - - - - - + + + diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj index bdc54140..d5ddf873 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj @@ -6,9 +6,9 @@ bin\Lab.DAL.xml - - - + + + From a531fee809587d071dc009e8f8e219df76b3516f Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Apr 2021 11:55:50 +0800 Subject: [PATCH 066/424] refactor --- .../EmployeeRepositoryUnitTests.cs | 3 ++- .../Lab.DAL/DefaultDbContextManager.cs | 22 ++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs index f9a2f333..b079931d 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -95,6 +95,7 @@ public void 操作真實資料庫_從容器取得Repository執行個體() { //arrange DefaultDbContextManager.Now = new DateTime(1900, 1, 1); + DefaultDbContextManager.SetPhysicalDatabase(); var builder = Host.CreateDefaultBuilder() .ConfigureServices(services => { services.AddSingleton(); }); @@ -133,7 +134,7 @@ public void 操作真實資料庫_從容器取得Repository執行個體() public void 操作記憶體() { DefaultDbContextManager.Now = new DateTime(1900, 1, 1); - DefaultDbContextManager.SetUseMemoryDatabase(); + DefaultDbContextManager.SetMemoryDatabase(); var builder = Host.CreateDefaultBuilder() .ConfigureServices(services => { services.AddSingleton(); }); diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs index 38c2be1c..a2bc9b3e 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs @@ -60,14 +60,16 @@ public static IConfiguration Configuration set => s_configuration = value; } + private static ServiceCollection s_services; static DefaultDbContextManager() { + s_services = new ServiceCollection(); + s_serviceProviderLazy = new Lazy(() => { - var services = new ServiceCollection(); + var services = s_services; services.AddDbContextFactory(ApplyConfigurePhysical); - return services.BuildServiceProvider(); }); s_configurationLazy @@ -90,16 +92,16 @@ static DefaultDbContextManager() }); } - public static void ApplyConfigureMemory(IServiceProvider provider, - DbContextOptionsBuilder optionsBuilder) + private static void ApplyConfigureMemory(IServiceProvider provider, + DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseInMemoryDatabase("Demo") .UseLoggerFactory(s_loggerFactory) ; } - public static void ApplyConfigurePhysical(IServiceProvider provider, - DbContextOptionsBuilder optionsBuilder) + private static void ApplyConfigurePhysical(IServiceProvider provider, + DbContextOptionsBuilder optionsBuilder) { var config = provider.GetService(); if (config == null) @@ -118,16 +120,16 @@ public static T GetInstance() return ServiceProvider.GetService(); } - public static void SetUseDefaultDatabase() where TContext : DbContext + public static void SetPhysicalDatabase() where TContext : DbContext { - var services = new ServiceCollection(); + var services = s_services; services.AddDbContextFactory(ApplyConfigurePhysical); ServiceProvider = services.BuildServiceProvider(); } - public static void SetUseMemoryDatabase() where TContext : DbContext + public static void SetMemoryDatabase() where TContext : DbContext { - var services = new ServiceCollection(); + var services = s_services; services.AddDbContextFactory(ApplyConfigureMemory); ServiceProvider = services.BuildServiceProvider(); } From 4a88ffaa26d5378016cb8829f3ef4c24a5e4070b Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Apr 2021 15:44:08 +0800 Subject: [PATCH 067/424] refactor --- .../Lab.DAL.InMemoryTestProject.csproj | 16 +++++ .../Lab.DAL.InMemoryTestProject/UnitTest1.cs | 13 +++++ .../EmployeeRepositoryUnitTests.cs | 2 + .../Lab.DAL/DefaultDbContextManager.cs | 58 +++++++++++-------- .../Lab.DAL/EmployeeRepository.cs | 8 +-- 5 files changed, 68 insertions(+), 29 deletions(-) create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/Lab.DAL.InMemoryTestProject.csproj create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/UnitTest1.cs diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/Lab.DAL.InMemoryTestProject.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/Lab.DAL.InMemoryTestProject.csproj new file mode 100644 index 00000000..5fb667f3 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/Lab.DAL.InMemoryTestProject.csproj @@ -0,0 +1,16 @@ + + + + net5.0 + + false + + + + + + + + + + diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/UnitTest1.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/UnitTest1.cs new file mode 100644 index 00000000..69366529 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/UnitTest1.cs @@ -0,0 +1,13 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.DAL.InMemoryTestProject +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs index b079931d..f30e2198 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -65,6 +65,8 @@ public void 操作真實資料庫_手動取得Repository執行個體() { //arrange DefaultDbContextManager.Now = new DateTime(1900, 1, 1); + DefaultDbContextManager.SetPhysicalDatabase(); + var repository = new EmployeeRepository(); var id = Guid.NewGuid(); repository.NewAsync(new NewRequest diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs index a2bc9b3e..4a1c16d2 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs @@ -14,9 +14,11 @@ internal class DefaultDbContextManager private static readonly Lazy s_serviceProviderLazy; private static readonly Lazy s_configurationLazy; private static readonly ILoggerFactory s_loggerFactory; - private static ServiceProvider s_serviceProvider; - private static IConfiguration s_configuration; - private static DateTime? s_now; + + private static readonly ServiceCollection s_services; + private static ServiceProvider s_serviceProvider; + private static IConfiguration s_configuration; + private static DateTime? s_now; public static DateTime Now { @@ -60,16 +62,18 @@ public static IConfiguration Configuration set => s_configuration = value; } - private static ServiceCollection s_services; static DefaultDbContextManager() { s_services = new ServiceCollection(); - + s_serviceProviderLazy = new Lazy(() => { var services = s_services; services.AddDbContextFactory(ApplyConfigurePhysical); + + //services.AddDbContextFactory(ApplyConfigurePhysical); + // services.AddDbContextFactory(ApplyConfigureMemory); return services.BuildServiceProvider(); }); s_configurationLazy @@ -92,10 +96,33 @@ static DefaultDbContextManager() }); } + public static T GetInstance() + { + return ServiceProvider.GetService(); + } + + public static void SetMemoryDatabase() where TContext : DbContext + { + var services = s_services; + + services.Clear(); + services.AddDbContextFactory(ApplyConfigureMemory); + ServiceProvider = services.BuildServiceProvider(); + } + + public static void SetPhysicalDatabase() where TContext : DbContext + { + var services = s_services; + + services.Clear(); + services.AddDbContextFactory(ApplyConfigurePhysical); + ServiceProvider = services.BuildServiceProvider(); + } + private static void ApplyConfigureMemory(IServiceProvider provider, DbContextOptionsBuilder optionsBuilder) { - optionsBuilder.UseInMemoryDatabase("Demo") + optionsBuilder.UseInMemoryDatabase("Lab.DAL") .UseLoggerFactory(s_loggerFactory) ; } @@ -114,24 +141,5 @@ private static void ApplyConfigurePhysical(IServiceProvider provider, .UseLoggerFactory(s_loggerFactory) ; } - - public static T GetInstance() - { - return ServiceProvider.GetService(); - } - - public static void SetPhysicalDatabase() where TContext : DbContext - { - var services = s_services; - services.AddDbContextFactory(ApplyConfigurePhysical); - ServiceProvider = services.BuildServiceProvider(); - } - - public static void SetMemoryDatabase() where TContext : DbContext - { - var services = s_services; - services.AddDbContextFactory(ApplyConfigureMemory); - ServiceProvider = services.BuildServiceProvider(); - } } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs index e0fef74c..09d00827 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs @@ -15,7 +15,7 @@ internal IDbContextFactory DbContextFactory { if (this._dbContextFactory == null) { - this._dbContextFactory = DefaultDbContextManager.GetInstance>(); + return DefaultDbContextManager.GetInstance>(); } return this._dbContextFactory; @@ -38,7 +38,7 @@ internal DateTime Now } private IDbContextFactory _dbContextFactory; - private DateTime? _now; + private DateTime? _now; public async Task InsertLogAsync(InsertOrderRequest request, string accessId, @@ -76,7 +76,7 @@ public async Task NewAsync(NewRequest request, CreateAt = this.Now, CreateBy = accessId }; - + var identityToDb = new Identity { Account = request.Account, @@ -86,7 +86,7 @@ public async Task NewAsync(NewRequest request, CreateAt = this.Now, CreateBy = accessId }; - + employeeToDb.Identity = identityToDb; await dbContext.Employees.AddAsync(employeeToDb, cancel); return await dbContext.SaveChangesAsync(cancel); From eb494beaff5445b7fa8d739e42f866c2d1f9f597 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Apr 2021 15:45:08 +0800 Subject: [PATCH 068/424] refactor --- .../Lab.DAL.InMemoryTestProject.csproj | 16 ---------------- .../Lab.DAL.InMemoryTestProject/UnitTest1.cs | 13 ------------- 2 files changed, 29 deletions(-) delete mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/Lab.DAL.InMemoryTestProject.csproj delete mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/UnitTest1.cs diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/Lab.DAL.InMemoryTestProject.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/Lab.DAL.InMemoryTestProject.csproj deleted file mode 100644 index 5fb667f3..00000000 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/Lab.DAL.InMemoryTestProject.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - net5.0 - - false - - - - - - - - - - diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/UnitTest1.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/UnitTest1.cs deleted file mode 100644 index 69366529..00000000 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.InMemoryTestProject/UnitTest1.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Lab.DAL.InMemoryTestProject -{ - [TestClass] - public class UnitTest1 - { - [TestMethod] - public void TestMethod1() - { - } - } -} \ No newline at end of file From e8d9477e8463ffc3d6534fcf416c0f011b668ea2 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Apr 2021 15:56:33 +0800 Subject: [PATCH 069/424] re --- .../EmployeeRepositoryUnitTests.cs | 56 +++++++++++++++++++ .../Lab.DAL/EmployeeRepository.cs | 19 ++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs index f30e2198..264634b8 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -92,6 +92,62 @@ public void 操作真實資料庫_手動取得Repository執行個體() Assert.AreEqual(1, count); } + [TestMethod] + public void 操作真實資料庫_從容器取得Repository和EmployeeDbContext執行個體() + { + //arrange + DefaultDbContextManager.Now = new DateTime(1900, 1, 1); + + var connectionString = + "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL.Injection;Trusted_Connection=True;MultipleActiveResultSets=true"; + + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(services => + { + services.AddSingleton(); + services.AddDbContext(builder => + { + builder.UseSqlServer(connectionString); + }); + }); + var host = builder.Build(); + + var dbContextOptions = host.Services.GetService>(); + var repository = host.Services.GetService(); + var employeeDbContext = host.Services.GetService(); + employeeDbContext.Database.EnsureCreated(); + + repository.EmployeeDbContext = employeeDbContext; + + var id = Guid.NewGuid(); + + //act + var count = repository.NewAsync(new NewRequest + { + Id = id, + Account = "yao", + Password = "123456", + Name = "余小章", + Age = 18, + }, "TestUser").Result; + + //assert + Assert.AreEqual(2, count); + var db = new EmployeeDbContext(dbContextOptions); + + // var actual = db.Employees.FirstOrDefault(); + var actual = db.Employees + .Include(p => p.Identity) + .AsNoTracking() + .FirstOrDefault(); + + Assert.AreEqual(actual.Name, "余小章"); + Assert.AreEqual(actual.Age, 18); + Assert.AreEqual(actual.Identity.Account, "yao"); + Assert.AreEqual(actual.Identity.Password, "123456"); + db.Database.EnsureDeleted(); + } + [TestMethod] public void 操作真實資料庫_從容器取得Repository執行個體() { diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs index 09d00827..d480ba0b 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs @@ -23,6 +23,20 @@ internal IDbContextFactory DbContextFactory set => this._dbContextFactory = value; } + internal EmployeeDbContext EmployeeDbContext + { + get + { + if (this._employeeDbContext == null) + { + return this.DbContextFactory.CreateDbContext(); + } + + return this._employeeDbContext; + } + set => this._employeeDbContext = value; + } + internal DateTime Now { get @@ -38,13 +52,14 @@ internal DateTime Now } private IDbContextFactory _dbContextFactory; + private EmployeeDbContext _employeeDbContext; private DateTime? _now; public async Task InsertLogAsync(InsertOrderRequest request, string accessId, CancellationToken cancel = default) { - await using var dbContext = this.DbContextFactory.CreateDbContext(); + await using var dbContext = this.EmployeeDbContext; var toDbOrderHistory = new OrderHistory { @@ -64,7 +79,7 @@ public async Task NewAsync(NewRequest request, string accessId, CancellationToken cancel = default) { - await using var dbContext = this.DbContextFactory.CreateDbContext(); + await using var dbContext = this.EmployeeDbContext; var id = Guid.NewGuid(); var employeeToDb = new Employee From 8aafabc38283e4163a631185a25740fbe8f7e50e Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Apr 2021 17:12:52 +0800 Subject: [PATCH 070/424] refactor --- .../Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs index d480ba0b..3a063e94 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs @@ -7,7 +7,18 @@ namespace Lab.DAL { - public class EmployeeRepository + public interface IEmployeeRepository + { + Task InsertLogAsync(InsertOrderRequest request, + string accessId, + CancellationToken cancel = default); + + Task NewAsync(NewRequest request, + string accessId, + CancellationToken cancel = default); + } + + public class EmployeeRepository : IEmployeeRepository { internal IDbContextFactory DbContextFactory { From cd9d27fffc7a88a435a5936f0add249b4b3c72ed Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Apr 2021 21:18:44 +0800 Subject: [PATCH 071/424] refactor --- .../Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs | 1 - .../Lab.DAL/EntityModel/EmployeeDbContext.cs | 10 ++++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs index 4a1c16d2..94015507 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs @@ -10,7 +10,6 @@ namespace Lab.DAL { internal class DefaultDbContextManager { - public static readonly bool[] Migrated = {false}; private static readonly Lazy s_serviceProviderLazy; private static readonly Lazy s_configurationLazy; private static readonly ILoggerFactory s_loggerFactory; diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs index 32c415b6..1e2b3860 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs @@ -11,17 +11,19 @@ public class EmployeeDbContext : DbContext public virtual DbSet OrderHistories { get; set; } + private static bool[] s_migrated; + public EmployeeDbContext(DbContextOptions options) : base(options) { - if (DefaultDbContextManager.Migrated[0]) + if (s_migrated[0]) { return; } - lock (DefaultDbContextManager.Migrated) + lock (s_migrated) { - if (DefaultDbContextManager.Migrated[0] == false) + if (s_migrated[0] == false) { var memoryOptions = options.FindExtension(); if (memoryOptions == null) @@ -29,7 +31,7 @@ public EmployeeDbContext(DbContextOptions options) this.Database.Migrate(); } - DefaultDbContextManager.Migrated[0] = true; + s_migrated[0] = true; } } } From 1ef21fc280957bb4bb6b202bf4a70f348aa2d585 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Apr 2021 21:22:38 +0800 Subject: [PATCH 072/424] fix bug --- .../Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs index 1e2b3860..3a04203a 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs @@ -5,14 +5,14 @@ namespace Lab.DAL.EntityModel { public class EmployeeDbContext : DbContext { + private static readonly bool[] s_migrated = {false}; + public virtual DbSet Employees { get; set; } public virtual DbSet Identities { get; set; } public virtual DbSet OrderHistories { get; set; } - private static bool[] s_migrated; - public EmployeeDbContext(DbContextOptions options) : base(options) { From 19ef31f4cd243f6073274c6aeda3b08ab709867f Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Apr 2021 23:55:44 +0800 Subject: [PATCH 073/424] first add --- .../Lab.SQLite.TestProject.csproj | 24 +++ ORM/EFCore/Lab.SQLite/Lab.SQLite.sln | 22 +++ .../Lab.SQLite/DefaultDbContextManager.cs | 144 ++++++++++++++++++ .../Employee/InsertOrderRequest.cs | 15 ++ .../DomainModel/Employee/NewRequest.cs | 24 +++ .../Lab.SQLite/EmployeeContextFactory.cs | 27 ++++ .../Lab.SQLite/EmployeeRepository.cs | 121 +++++++++++++++ .../Lab.SQLite/EntityModel/Employee.cs | 32 ++++ .../EntityModel/EmployeeDbContext.cs | 81 ++++++++++ .../Lab.SQLite/EntityModel/Identity.cs | 34 +++++ .../Lab.SQLite/EntityModel/OrderHistory.cs | 31 ++++ .../Lab.SQLite/Lab.SQLite/Lab.SQLite.csproj | 23 +++ .../Lab.SQLite/Lab.SQLite/appsettings.json | 5 + 13 files changed, 583 insertions(+) create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/Lab.SQLite.TestProject.csproj create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite.sln create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite/DefaultDbContextManager.cs create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/InsertOrderRequest.cs create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/NewRequest.cs create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeContextFactory.cs create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeRepository.cs create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Employee.cs create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/EmployeeDbContext.cs create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Identity.cs create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/OrderHistory.cs create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite/Lab.SQLite.csproj create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite/appsettings.json diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/Lab.SQLite.TestProject.csproj b/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/Lab.SQLite.TestProject.csproj new file mode 100644 index 00000000..01eb2648 --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/Lab.SQLite.TestProject.csproj @@ -0,0 +1,24 @@ + + + + net5.0 + bin + false + + + + + + + + + + + + + + + + + + diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite.sln b/ORM/EFCore/Lab.SQLite/Lab.SQLite.sln new file mode 100644 index 00000000..1bb14120 --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SQLite", "Lab.SQLite\Lab.SQLite.csproj", "{ECC70BD1-FB1E-442C-B676-386C11510B43}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SQLite.TestProject", "Lab.SQLite.TestProject\Lab.SQLite.TestProject.csproj", "{C3821F71-3F5F-4480-8D84-782A55772101}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ECC70BD1-FB1E-442C-B676-386C11510B43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ECC70BD1-FB1E-442C-B676-386C11510B43}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECC70BD1-FB1E-442C-B676-386C11510B43}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ECC70BD1-FB1E-442C-B676-386C11510B43}.Release|Any CPU.Build.0 = Release|Any CPU + {C3821F71-3F5F-4480-8D84-782A55772101}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3821F71-3F5F-4480-8D84-782A55772101}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3821F71-3F5F-4480-8D84-782A55772101}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3821F71-3F5F-4480-8D84-782A55772101}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DefaultDbContextManager.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/DefaultDbContextManager.cs new file mode 100644 index 00000000..08bdb7c2 --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/DefaultDbContextManager.cs @@ -0,0 +1,144 @@ +using System; +using System.IO; +using Lab.SQLite.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.SQLite +{ + internal class DefaultDbContextManager + { + private static readonly Lazy s_serviceProviderLazy; + private static readonly Lazy s_configurationLazy; + private static readonly ILoggerFactory s_loggerFactory; + + private static readonly ServiceCollection s_services; + private static ServiceProvider s_serviceProvider; + private static IConfiguration s_configuration; + private static DateTime? s_now; + + public static DateTime Now + { + get + { + if (s_now == null) + { + return DateTime.UtcNow; + } + + return s_now.Value; + } + set => s_now = value; + } + + public static ServiceProvider ServiceProvider + { + get + { + if (s_serviceProvider == null) + { + s_serviceProvider = s_serviceProviderLazy.Value; + } + + return s_serviceProvider; + } + set => s_serviceProvider = value; + } + + public static IConfiguration Configuration + { + get + { + if (s_configuration == null) + { + s_configuration = s_configurationLazy.Value; + } + + return s_configuration; + } + set => s_configuration = value; + } + + static DefaultDbContextManager() + { + s_services = new ServiceCollection(); + + s_serviceProviderLazy = + new Lazy(() => + { + var services = s_services; + services.AddDbContextFactory(ApplyConfigurePhysical); + + //services.AddDbContextFactory(ApplyConfigurePhysical); + // services.AddDbContextFactory(ApplyConfigureMemory); + return services.BuildServiceProvider(); + }); + s_configurationLazy + = new Lazy(() => + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + return configBuilder.Build(); + }); + s_loggerFactory = LoggerFactory.Create(builder => + { + builder + + //.AddFilter("Microsoft", LogLevel.Warning) + //.AddFilter("System", LogLevel.Warning) + .AddFilter("Lab.DAL", LogLevel.Debug) + .AddConsole() + ; + }); + } + + public static T GetInstance() + { + return ServiceProvider.GetService(); + } + + public static void SetMemoryDatabase() where TContext : DbContext + { + var services = s_services; + + services.Clear(); + services.AddDbContextFactory(ApplyConfigureMemory); + ServiceProvider = services.BuildServiceProvider(); + } + + public static void SetPhysicalDatabase() where TContext : DbContext + { + var services = s_services; + + services.Clear(); + services.AddDbContextFactory(ApplyConfigurePhysical); + ServiceProvider = services.BuildServiceProvider(); + } + + private static void ApplyConfigureMemory(IServiceProvider provider, + DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseInMemoryDatabase("Lab.DAL") + .UseLoggerFactory(s_loggerFactory) + ; + } + + private static void ApplyConfigurePhysical(IServiceProvider provider, + DbContextOptionsBuilder optionsBuilder) + { + var config = provider.GetService(); + if (config == null) + { + config = Configuration; + } + + var connectionString = config.GetConnectionString("DefaultConnection"); + optionsBuilder.UseSqlite(connectionString) + .UseLoggerFactory(s_loggerFactory) + ; + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/InsertOrderRequest.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/InsertOrderRequest.cs new file mode 100644 index 00000000..bb552170 --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/InsertOrderRequest.cs @@ -0,0 +1,15 @@ +using System; + +namespace Lab.SQLite.DomainModel.Employee +{ + public class InsertOrderRequest + { + public Guid? Employee_Id { get; set; } + + public string Product_Id { get; set; } + + public string Product_Name { get; set; } + + public string Remark { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/NewRequest.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/NewRequest.cs new file mode 100644 index 00000000..7719a278 --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/NewRequest.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Lab.SQLite.DomainModel.Employee +{ + public class NewRequest + { + [Key] + public Guid Id { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + public string Remark { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeContextFactory.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeContextFactory.cs new file mode 100644 index 00000000..1a3a45c4 --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeContextFactory.cs @@ -0,0 +1,27 @@ +using System; +using Lab.SQLite.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; + +namespace Lab.SQLite +{ + public class EmployeeContextFactory : IDesignTimeDbContextFactory + { + public EmployeeDbContext CreateDbContext(string[] args) + { + Console.WriteLine("由設計工具產生 Database,初始化 DbContextOptionsBuilder"); + + var config = DefaultDbContextManager.Configuration; + var connectionString = config.GetConnectionString("DefaultConnection"); + + Console.WriteLine($"由 appsettings.json 讀取連線字串為:{connectionString}"); + + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlite(connectionString); + Console.WriteLine($"DbContextOptionsBuilder 設定完成"); + + return new EmployeeDbContext(optionsBuilder.Options); + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeRepository.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeRepository.cs new file mode 100644 index 00000000..1e932f4e --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeRepository.cs @@ -0,0 +1,121 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Lab.SQLite.DomainModel.Employee; +using Lab.SQLite.EntityModel; +using Microsoft.EntityFrameworkCore; + +namespace Lab.SQLite +{ + public interface IEmployeeRepository + { + Task InsertLogAsync(InsertOrderRequest request, + string accessId, + CancellationToken cancel = default); + + Task NewAsync(NewRequest request, + string accessId, + CancellationToken cancel = default); + } + + public class EmployeeRepository : IEmployeeRepository + { + internal IDbContextFactory DbContextFactory + { + get + { + if (this._dbContextFactory == null) + { + return DefaultDbContextManager.GetInstance>(); + } + + return this._dbContextFactory; + } + set => this._dbContextFactory = value; + } + + internal EmployeeDbContext EmployeeDbContext + { + get + { + if (this._employeeDbContext == null) + { + return this.DbContextFactory.CreateDbContext(); + } + + return this._employeeDbContext; + } + set => this._employeeDbContext = value; + } + + internal DateTime Now + { + get + { + if (this._now == null) + { + return DefaultDbContextManager.Now; + } + + return this._now.Value; + } + set => this._now = value; + } + + private IDbContextFactory _dbContextFactory; + private EmployeeDbContext _employeeDbContext; + private DateTime? _now; + + public async Task InsertLogAsync(InsertOrderRequest request, + string accessId, + CancellationToken cancel = default) + { + await using var dbContext = this.EmployeeDbContext; + + var toDbOrderHistory = new OrderHistory + { + Employee_Id = request.Employee_Id, + Product_Id = request.Product_Id, + Product_Name = request.Product_Id, + CreateAt = this.Now, + CreateBy = accessId, + Remark = request.Remark, + }; + + await dbContext.OrderHistories.AddAsync(toDbOrderHistory, cancel); + return await dbContext.SaveChangesAsync(cancel); + } + + public async Task NewAsync(NewRequest request, + string accessId, + CancellationToken cancel = default) + { + await using var dbContext = this.EmployeeDbContext; + + var id = Guid.NewGuid(); + var employeeToDb = new Employee + { + Id = id, + Name = request.Name, + Age = request.Age, + Remark = request.Remark, + CreateAt = this.Now, + CreateBy = accessId + }; + + var identityToDb = new Identity + { + Account = request.Account, + Password = request.Password, + Remark = request.Remark, + Employee = employeeToDb, + CreateAt = this.Now, + CreateBy = accessId + }; + + employeeToDb.Identity = identityToDb; + await dbContext.Employees.AddAsync(employeeToDb, cancel); + return await dbContext.SaveChangesAsync(cancel); + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Employee.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Employee.cs new file mode 100644 index 00000000..5f9596f1 --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Employee.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.SQLite.EntityModel +{ + [Table("Employee")] + public class Employee + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Id { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + [Required] + public DateTime CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + public virtual Identity Identity { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/EmployeeDbContext.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/EmployeeDbContext.cs new file mode 100644 index 00000000..30d07caa --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/EmployeeDbContext.cs @@ -0,0 +1,81 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; + +namespace Lab.SQLite.EntityModel +{ + public class EmployeeDbContext : DbContext + { + private static readonly bool[] s_migrated = {false}; + + public virtual DbSet Employees { get; set; } + + public virtual DbSet Identities { get; set; } + + public virtual DbSet OrderHistories { get; set; } + + public EmployeeDbContext(DbContextOptions options) + : base(options) + { + if (s_migrated[0]) + { + return; + } + + lock (s_migrated) + { + if (s_migrated[0] == false) + { + var memoryOptions = options.FindExtension(); + if (memoryOptions == null) + { + this.Database.Migrate(); + } + + s_migrated[0] = true; + } + } + } + + // 給 Migration CLI 使用 + // 建構函數配置失敗才需要以下處理 + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + // var connectionString = + // "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL.UnitTest;Trusted_Connection=True;MultipleActiveResultSets=true"; + // + // // var connectionString = this._connectionString; + // if (optionsBuilder.IsConfigured == false) + // { + // optionsBuilder.UseSqlServer(connectionString); + // } + } + + //管理索引 + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + // modelBuilder.Entity(p => + // { + // p.HasKey(e => e.Id) + // .IsClustered(false); + // }); + // + // modelBuilder.Entity(p => + // { + // p.HasIndex(e => e.SequenceId) + // .IsUnique() + // .IsClustered(); + // }); + // modelBuilder.Entity(p => + // { + // p.HasKey(e => e.Employee_Id) + // .IsClustered(false); + // }); + // modelBuilder.Entity(p => + // { + // p.HasIndex(e => e.SequenceId) + // .IsUnique() + // .IsClustered(); + // }); + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Identity.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Identity.cs new file mode 100644 index 00000000..1f7947c1 --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Identity.cs @@ -0,0 +1,34 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.SQLite.EntityModel +{ + [Table("Identity")] + public class Identity + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Employee_Id { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + [Required] + public DateTime CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + [ForeignKey("Employee_Id")] + public virtual Employee Employee { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/OrderHistory.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/OrderHistory.cs new file mode 100644 index 00000000..12404287 --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/OrderHistory.cs @@ -0,0 +1,31 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.SQLite.EntityModel +{ + [Table("OrderHistory")] + public class OrderHistory + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + public Guid? Employee_Id { get; set; } + + public string Remark { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Product_Id { get; set; } + + public string Product_Name { get; set; } + + [Required] + public DateTime CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/Lab.SQLite.csproj b/ORM/EFCore/Lab.SQLite/Lab.SQLite/Lab.SQLite.csproj new file mode 100644 index 00000000..afd9f79a --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/Lab.SQLite.csproj @@ -0,0 +1,23 @@ + + + + net5.0 + + + + + + + + + + + + + + + + <_Parameter1>Lab.SQLite.TestProject + + + diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/appsettings.json b/ORM/EFCore/Lab.SQLite/Lab.SQLite/appsettings.json new file mode 100644 index 00000000..be206a85 --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Data Source=bin\\Lab.DAL.db" + } +} \ No newline at end of file From 35f7904b55ac484d9c6d255be98618bce2b3db9b Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 16 Apr 2021 11:21:15 +0800 Subject: [PATCH 074/424] refactor --- .../EmployeeRepositoryUnitTests.cs | 19 ++++++++----------- .../DomainModel/Employee/NewRequest.cs | 3 --- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs index 264634b8..206064d0 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -68,16 +68,18 @@ public void 操作真實資料庫_手動取得Repository執行個體() DefaultDbContextManager.SetPhysicalDatabase(); var repository = new EmployeeRepository(); - var id = Guid.NewGuid(); + + // var id = Guid.NewGuid(); repository.NewAsync(new NewRequest { - Id = id, Account = "yao", Password = "123456", Name = "余小章", Age = 18, Remark = "測試案例,持續航向偉大航道" }, "TestUser").Wait(); + using var db = new EmployeeDbContext(s_employeeContextOptions); + var id = db.Employees.FirstOrDefault(p => p.Name == "余小章").Id; //act var count = repository.InsertLogAsync(new InsertOrderRequest @@ -93,7 +95,7 @@ public void 操作真實資料庫_手動取得Repository執行個體() } [TestMethod] - public void 操作真實資料庫_從容器取得Repository和EmployeeDbContext執行個體() + public void 操作真實資料庫_注入EmployeeDbContext() { //arrange DefaultDbContextManager.Now = new DateTime(1900, 1, 1); @@ -124,7 +126,6 @@ public void 操作真實資料庫_從容器取得Repository和EmployeeDbContext //act var count = repository.NewAsync(new NewRequest { - Id = id, Account = "yao", Password = "123456", Name = "余小章", @@ -133,9 +134,8 @@ public void 操作真實資料庫_從容器取得Repository和EmployeeDbContext //assert Assert.AreEqual(2, count); - var db = new EmployeeDbContext(dbContextOptions); + using var db = new EmployeeDbContext(dbContextOptions); - // var actual = db.Employees.FirstOrDefault(); var actual = db.Employees .Include(p => p.Identity) .AsNoTracking() @@ -149,7 +149,7 @@ public void 操作真實資料庫_從容器取得Repository和EmployeeDbContext } [TestMethod] - public void 操作真實資料庫_從容器取得Repository執行個體() + public void 操作真實資料庫_預設EmployeeDbContext() { //arrange DefaultDbContextManager.Now = new DateTime(1900, 1, 1); @@ -160,12 +160,10 @@ public void 操作真實資料庫_從容器取得Repository執行個體() var host = builder.Build(); var repository = host.Services.GetService(); - var id = Guid.NewGuid(); //act var count = repository.NewAsync(new NewRequest { - Id = id, Account = "yao", Password = "123456", Name = "余小章", @@ -174,9 +172,8 @@ public void 操作真實資料庫_從容器取得Repository執行個體() //assert Assert.AreEqual(2, count); - var db = new EmployeeDbContext(s_employeeContextOptions); + using var db = new EmployeeDbContext(s_employeeContextOptions); - // var actual = db.Employees.FirstOrDefault(); var actual = db.Employees .Include(p => p.Identity) .AsNoTracking() diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/NewRequest.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/NewRequest.cs index 3288a612..ed8418dc 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/NewRequest.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/NewRequest.cs @@ -5,9 +5,6 @@ namespace Lab.DAL.DomainModel.Employee { public class NewRequest { - [Key] - public Guid Id { get; set; } - [Required] public string Name { get; set; } From 3be7ed326fb4873254997ed464df630d4ee29d96 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 16 Apr 2021 12:00:27 +0800 Subject: [PATCH 075/424] refactor --- .../Lab.DAL.TestProject/EmployeeDbContext.cs | 61 +++++++++++++++++++ .../EmployeeRepositoryUnitTests.cs | 41 +++++++++---- .../Lab.DAL/EmployeeRepository.cs | 2 +- 3 files changed, 91 insertions(+), 13 deletions(-) create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeDbContext.cs diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeDbContext.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeDbContext.cs new file mode 100644 index 00000000..1888c309 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeDbContext.cs @@ -0,0 +1,61 @@ +using Lab.DAL.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; + +namespace Lab.DAL.TestProject +{ + public class TestEmployeeDbContext : DbContext + { + private readonly string _connectionString; + + public virtual DbSet Employees { get; set; } + + public virtual DbSet Identities { get; set; } + + public virtual DbSet OrderHistories { get; set; } + + public TestEmployeeDbContext(string connectionString) + { + this._connectionString = connectionString; + } + + // 給 Migration CLI 使用 + // 建構函數配置失敗才需要以下處理 + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + var connectionString = this._connectionString; + if (optionsBuilder.IsConfigured == false) + { + optionsBuilder.UseSqlServer(connectionString); + } + } + + //管理索引 + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(p => + { + p.HasKey(e => e.Id) + .IsClustered(false); + }); + + modelBuilder.Entity(p => + { + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + }); + modelBuilder.Entity(p => + { + p.HasKey(e => e.Employee_Id) + .IsClustered(false); + }); + modelBuilder.Entity(p => + { + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + }); + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs index 206064d0..860a0a25 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -10,16 +10,24 @@ using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Lab.DAL.UnitTest +namespace Lab.DAL.TestProject { [TestClass] public class EmployeeRepositoryUnitTests { private static readonly DbContextOptions s_employeeContextOptions; + public static string TestDbConnectionString; static EmployeeRepositoryUnitTests() { s_employeeContextOptions = CreateDbContextOptions(); + + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + + var configRoot = configBuilder.Build(); + TestDbConnectionString = configRoot.GetConnectionString("DefaultConnection"); } [AssemblyCleanup] @@ -28,7 +36,7 @@ public static void AssemblyCleanup() //刪除測試資料庫 Console.WriteLine("AssemblyCleanup"); - using var db = new EmployeeDbContext(s_employeeContextOptions); + using var db = new TestEmployeeDbContext(TestDbConnectionString); db.Database.EnsureDeleted(); } @@ -37,11 +45,8 @@ public static void AssemblyInitialize(TestContext context) { //刪除測試資料庫 Console.WriteLine("AssemblyInitialize"); - using var db = new EmployeeDbContext(s_employeeContextOptions); + using var db = new TestEmployeeDbContext(TestDbConnectionString); db.Database.EnsureDeleted(); - - //建立測試資料庫 - db.Database.EnsureCreated(); } [ClassCleanup] @@ -92,6 +97,15 @@ public void 操作真實資料庫_手動取得Repository執行個體() //assert Assert.AreEqual(1, count); + + using var db1 = new TestEmployeeDbContext(TestDbConnectionString); + + var actual = db1.OrderHistories + .AsNoTracking() + .FirstOrDefault(); + + Assert.AreEqual("A001", actual.Product_Id); + Assert.AreEqual("羅技滑鼠", actual.Product_Name); } [TestMethod] @@ -121,8 +135,6 @@ public void 操作真實資料庫_注入EmployeeDbContext() repository.EmployeeDbContext = employeeDbContext; - var id = Guid.NewGuid(); - //act var count = repository.NewAsync(new NewRequest { @@ -172,7 +184,8 @@ public void 操作真實資料庫_預設EmployeeDbContext() //assert Assert.AreEqual(2, count); - using var db = new EmployeeDbContext(s_employeeContextOptions); + + using var db = new TestEmployeeDbContext(TestDbConnectionString); var actual = db.Employees .Include(p => p.Identity) @@ -227,9 +240,13 @@ private static DbContextOptions CreateDbContextOptions() private static void DeleteTestDataRow() { - var dbContextOptions = s_employeeContextOptions; - using var db = new EmployeeDbContext(dbContextOptions); - var deleteCommand = GetDeleteAllRecordCommand(); + using var db = new TestEmployeeDbContext(TestDbConnectionString); + if (db.Database.CanConnect() == false) + { + return; + } + + var deleteCommand = GetDeleteAllRecordCommand(); db.Database.ExecuteSqlRaw(deleteCommand); } diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs index 3a063e94..f6703c41 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs @@ -76,7 +76,7 @@ public async Task InsertLogAsync(InsertOrderRequest request, { Employee_Id = request.Employee_Id, Product_Id = request.Product_Id, - Product_Name = request.Product_Id, + Product_Name = request.Product_Name, CreateAt = this.Now, CreateBy = accessId, Remark = request.Remark, From 4ed7925158b7a4829fc48a388339bc732b3738f3 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 16 Apr 2021 12:01:38 +0800 Subject: [PATCH 076/424] refactor --- .../{EmployeeDbContext.cs => TestEmployeeDbContext.cs} | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) rename ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/{EmployeeDbContext.cs => TestEmployeeDbContext.cs} (90%) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeDbContext.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/TestEmployeeDbContext.cs similarity index 90% rename from ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeDbContext.cs rename to ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/TestEmployeeDbContext.cs index 1888c309..202ad138 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/EmployeeDbContext.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/TestEmployeeDbContext.cs @@ -1,26 +1,23 @@ using Lab.DAL.EntityModel; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; namespace Lab.DAL.TestProject { public class TestEmployeeDbContext : DbContext { - private readonly string _connectionString; - public virtual DbSet Employees { get; set; } public virtual DbSet Identities { get; set; } public virtual DbSet OrderHistories { get; set; } + private readonly string _connectionString; + public TestEmployeeDbContext(string connectionString) { this._connectionString = connectionString; } - // 給 Migration CLI 使用 - // 建構函數配置失敗才需要以下處理 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { var connectionString = this._connectionString; From 168afe7dc467fd65e340ba1d21c4e2aa7e3f66cd Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 16 Apr 2021 13:05:57 +0800 Subject: [PATCH 077/424] refactor --- ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs index 94015507..999d62af 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DefaultDbContextManager.cs @@ -70,9 +70,6 @@ static DefaultDbContextManager() { var services = s_services; services.AddDbContextFactory(ApplyConfigurePhysical); - - //services.AddDbContextFactory(ApplyConfigurePhysical); - // services.AddDbContextFactory(ApplyConfigureMemory); return services.BuildServiceProvider(); }); s_configurationLazy From 51632f9e5eb7a261bf82b41f13c817309a40061e Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 16 Apr 2021 14:43:19 +0800 Subject: [PATCH 078/424] refactor --- .../Lab.DAL/EntityModel/EmployeeDbContext.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs index 3a04203a..78b0f738 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EntityModel/EmployeeDbContext.cs @@ -1,5 +1,7 @@ -using Microsoft.EntityFrameworkCore; +using System; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; namespace Lab.DAL.EntityModel { @@ -26,9 +28,15 @@ public EmployeeDbContext(DbContextOptions options) if (s_migrated[0] == false) { var memoryOptions = options.FindExtension(); + if (memoryOptions == null) { - this.Database.Migrate(); + var sqlOptions = options.FindExtension(); + if (sqlOptions != null) + { + Console.WriteLine($"EmployeeDbContext of connection string be '{sqlOptions.ConnectionString}'"); + this.Database.Migrate(); + } } s_migrated[0] = true; From ab8fbbb347cb80c70501145b6f13df8ccc5d0096 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 16 Apr 2021 15:44:56 +0800 Subject: [PATCH 079/424] add web api project --- .../DomainModel/Employee/FilterResponse.cs | 15 +++++ .../Lab.DAL/EmployeeRepository.cs | 19 +++++++ ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln | 6 ++ .../Controllers/DefaultController.cs | 57 +++++++++++++++++++ .../Lab.WebApi/Lab.WebApi.csproj | 15 +++++ .../Lab.VirtualDb/Lab.WebApi/Program.cs | 23 ++++++++ .../Lab.WebApi/Properties/launchSettings.json | 31 ++++++++++ .../ServiceModel/Employee/FilterResponse.cs | 16 ++++++ .../ServiceModel/Employee/NewRequest.cs | 20 +++++++ .../Lab.VirtualDb/Lab.WebApi/Startup.cs | 57 +++++++++++++++++++ .../Lab.WebApi/WeatherForecast.cs | 15 +++++ .../Lab.WebApi/appsettings.Development.json | 9 +++ .../Lab.VirtualDb/Lab.WebApi/appsettings.json | 13 +++++ 13 files changed, 296 insertions(+) create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/FilterResponse.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Controllers/DefaultController.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Lab.WebApi.csproj create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Program.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Properties/launchSettings.json create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.WebApi/ServiceModel/Employee/FilterResponse.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.WebApi/ServiceModel/Employee/NewRequest.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Startup.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.WebApi/WeatherForecast.cs create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.WebApi/appsettings.Development.json create mode 100644 ORM/EFCore/Lab.VirtualDb/Lab.WebApi/appsettings.json diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/FilterResponse.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/FilterResponse.cs new file mode 100644 index 00000000..8aa958a4 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/DomainModel/Employee/FilterResponse.cs @@ -0,0 +1,15 @@ +namespace Lab.DAL.DomainModel.Employee +{ + public class FilterResponse + { + public string Name { get; set; } + + public int? Age { get; set; } + + public string Account { get; set; } + + public string Password { get; set; } + + public string Remark { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs index f6703c41..68c05d52 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/EmployeeRepository.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Lab.DAL.DomainModel.Employee; @@ -117,5 +119,22 @@ public async Task NewAsync(NewRequest request, await dbContext.Employees.AddAsync(employeeToDb, cancel); return await dbContext.SaveChangesAsync(cancel); } + + public async Task> GetAllAsync(CancellationToken cancel) + { + await using var db = this.EmployeeDbContext; + return await db.Employees + .Include(p => p.Identity) + .Select(p => new FilterResponse() + { + Account = p.Identity.Account, + Age = p.Age, + Name = p.Name, + Password = p.Identity.Password, + Remark = p.Remark + }) + .AsNoTracking() + .ToListAsync(cancel); + } } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln b/ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln index f401e0a4..fa88dca5 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln +++ b/ORM/EFCore/Lab.VirtualDb/Lab.VirtualDb.sln @@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.DAL.TestProject", "Lab. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Biz", "Lab.Biz\Lab.Biz.csproj", "{0056BEF7-8B47-4387-9110-788A3B73E452}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.WebApi", "Lab.WebApi\Lab.WebApi.csproj", "{7C5880FB-4D25-4188-A04C-CE58C6D592A0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,5 +26,9 @@ Global {0056BEF7-8B47-4387-9110-788A3B73E452}.Debug|Any CPU.Build.0 = Debug|Any CPU {0056BEF7-8B47-4387-9110-788A3B73E452}.Release|Any CPU.ActiveCfg = Release|Any CPU {0056BEF7-8B47-4387-9110-788A3B73E452}.Release|Any CPU.Build.0 = Release|Any CPU + {7C5880FB-4D25-4188-A04C-CE58C6D592A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C5880FB-4D25-4188-A04C-CE58C6D592A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C5880FB-4D25-4188-A04C-CE58C6D592A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C5880FB-4D25-4188-A04C-CE58C6D592A0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Controllers/DefaultController.cs b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Controllers/DefaultController.cs new file mode 100644 index 00000000..385cb904 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Controllers/DefaultController.cs @@ -0,0 +1,57 @@ +using System.Threading; +using System.Threading.Tasks; +using Lab.DAL; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using ServiceModel = Lab.WebApi.ServiceModel; +using DomainModel = Lab.DAL.DomainModel; + +namespace Lab.WebApi.Controllers +{ + [ApiController] + [Route("[controller]")] + public class DefaultController : ControllerBase + { + private readonly ILogger _logger; + private readonly EmployeeRepository _repository; + + public DefaultController(ILogger logger, EmployeeRepository repository) + { + this._logger = logger; + this._repository = repository; + } + + [HttpGet] + [Produces(typeof(ServiceModel.Employee.FilterResponse))] + public async Task Get(CancellationToken cancel = default) + { + var repository = this._repository; + var record = await repository.GetAllAsync(cancel); + return this.Ok(record); + } + + [HttpPost] + public async Task Post(ServiceModel.Employee.NewRequest request, + string accessId, + CancellationToken cancel = default) + { + var repository = this._repository; + var count = await repository.NewAsync(new DomainModel.Employee.NewRequest() + { + Account = request.Account, + Age = request.Age, + Name = request.Name, + Password = request.Password, + Remark = request.Remark + }, accessId, cancel); + if (count == 2) + { + return this.Ok(); + } + else + { + return this.NoContent(); + } + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Lab.WebApi.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Lab.WebApi.csproj new file mode 100644 index 00000000..8b969acc --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Lab.WebApi.csproj @@ -0,0 +1,15 @@ + + + + net5.0 + + + + + + + + + + + diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Program.cs b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Program.cs new file mode 100644 index 00000000..3fd195ba --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Program.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Lab.WebApi +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Properties/launchSettings.json b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Properties/launchSettings.json new file mode 100644 index 00000000..292d93d5 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:24269", + "sslPort": 44335 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Lab.WebApi": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/ServiceModel/Employee/FilterResponse.cs b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/ServiceModel/Employee/FilterResponse.cs new file mode 100644 index 00000000..257ab643 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/ServiceModel/Employee/FilterResponse.cs @@ -0,0 +1,16 @@ +namespace Lab.WebApi.ServiceModel.Employee +{ + //for doc + public class FilterResponse + { + public string Name { get; set; } + + public int? Age { get; set; } + + public string Account { get; set; } + + public string Password { get; set; } + + public string Remark { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/ServiceModel/Employee/NewRequest.cs b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/ServiceModel/Employee/NewRequest.cs new file mode 100644 index 00000000..e26362fb --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/ServiceModel/Employee/NewRequest.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; + +namespace Lab.WebApi.ServiceModel.Employee +{ + public class NewRequest + { + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + public string Remark { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Startup.cs b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Startup.cs new file mode 100644 index 00000000..4430ede3 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/Startup.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Lab.DAL; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; + +namespace Lab.WebApi +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo {Title = "Lab.WebApi", Version = "v1"}); + }); + services.AddSingleton(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Lab.WebApi v1")); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/WeatherForecast.cs b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/WeatherForecast.cs new file mode 100644 index 00000000..1c8dfc31 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/WeatherForecast.cs @@ -0,0 +1,15 @@ +using System; + +namespace Lab.WebApi +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int) (TemperatureC / 0.5556); + + public string Summary { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/appsettings.Development.json b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/appsettings.Development.json new file mode 100644 index 00000000..8983e0fc --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/appsettings.json b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/appsettings.json new file mode 100644 index 00000000..64195df2 --- /dev/null +++ b/ORM/EFCore/Lab.VirtualDb/Lab.WebApi/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL.WebApi;Trusted_Connection=True;MultipleActiveResultSets=true" + } +} From 97a892c7c757589e44a31a7f0b827dfe9cdf5e73 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 16 Apr 2021 15:45:26 +0800 Subject: [PATCH 080/424] add sqlite project --- .../EmployeeRepositoryUnitTests.cs | 253 ++++++++++++++++++ .../Lab.SQLite.TestProject.csproj | 19 +- .../TestEmployeeDbContext.cs | 39 +++ .../Lab.SQLite.TestProject/appsettings.json | 5 + .../Lab.SQLite/DefaultDbContextManager.cs | 3 - .../Employee/InsertOrderRequest.cs | 2 +- .../DomainModel/Employee/NewRequest.cs | 3 - .../Lab.SQLite/EmployeeRepository.cs | 3 +- .../Lab.SQLite/EntityModel/Employee.cs | 5 +- .../EntityModel/EmployeeDbContext.cs | 41 ++- .../Lab.SQLite/EntityModel/Identity.cs | 7 +- .../Lab.SQLite/EntityModel/OrderHistory.cs | 11 +- .../Lab.SQLite/Lab.SQLite/Lab.SQLite.csproj | 10 +- 13 files changed, 345 insertions(+), 56 deletions(-) create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/EmployeeRepositoryUnitTests.cs create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/TestEmployeeDbContext.cs create mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/appsettings.json diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/EmployeeRepositoryUnitTests.cs new file mode 100644 index 00000000..2bc478ee --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/EmployeeRepositoryUnitTests.cs @@ -0,0 +1,253 @@ +using System; +using System.IO; +using System.Linq; +using Lab.SQLite.DomainModel.Employee; +using Lab.SQLite.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.SQLite.TestProject +{ + [TestClass] + public class EmployeeRepositoryUnitTests + { + private static readonly DbContextOptions s_employeeContextOptions; + private static readonly string TestDbConnectionString = "Data Source=Lab.DAL.TestProject.db"; + + static EmployeeRepositoryUnitTests() + { + s_employeeContextOptions = CreateDbContextOptions(); + } + + [AssemblyCleanup] + public static void AssemblyCleanup() + { + //刪除測試資料庫 + Console.WriteLine("AssemblyCleanup"); + + using var db = new TestEmployeeDbContext(TestDbConnectionString); + db.Database.EnsureDeleted(); + } + + [AssemblyInitialize] + public static void AssemblyInitialize(TestContext context) + { + //刪除測試資料庫 + Console.WriteLine("AssemblyInitialize"); + using var db = new TestEmployeeDbContext(TestDbConnectionString); + db.Database.EnsureDeleted(); + + // //建立測試資料庫 + // db.Database.Migrate(); + } + + [ClassCleanup] + public static void ClassCleanup() + { + //刪除測試資料表 + Console.WriteLine("ClassCleanup"); + + // DeleteTestDataRow(); + } + + [ClassInitialize] + public static void ClassInitialize(TestContext context) + { + //刪除測試資料表 + Console.WriteLine("ClassInitialize"); + + // DeleteTestDataRow(); + } + + [TestMethod] + public void 操作真實資料庫_手動取得Repository執行個體() + { + //arrange + DefaultDbContextManager.Now = new DateTime(1900, 1, 1); + DefaultDbContextManager.SetPhysicalDatabase(); + + var repository = new EmployeeRepository(); + + // var id = Guid.NewGuid(); + repository.NewAsync(new NewRequest + { + Account = "yao", + Password = "123456", + Name = "余小章", + Age = 18, + Remark = "測試案例,持續航向偉大航道" + }, "TestUser").Wait(); + using var db = new TestEmployeeDbContext(TestDbConnectionString); + var id = db.Employees.FirstOrDefault(p => p.Name == "余小章").Id; + + //act + var count = repository.InsertLogAsync(new InsertOrderRequest + { + Employee_Id = id, + Product_Id = "A001", + Product_Name = "羅技滑鼠", + Remark = "測試案例,持續航向偉大航道" + }, "TestUser").Result; + + //assert + Assert.AreEqual(1, count); + } + + [TestMethod] + public void 操作真實資料庫_注入EmployeeDbContext() + { + //arrange + DefaultDbContextManager.Now = new DateTime(1900, 1, 1); + + var connectionString = "Data Source=Lab.DAL.Injection.db"; + + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(services => + { + services.AddSingleton(); + services.AddDbContext(builder => + { + builder.UseSqlite(connectionString); + }); + }); + var host = builder.Build(); + + var dbContextOptions = host.Services.GetService>(); + var repository = host.Services.GetService(); + var employeeDbContext = host.Services.GetService(); + // employeeDbContext.Database.Migrate(); + + repository.EmployeeDbContext = employeeDbContext; + + //act + var count = repository.NewAsync(new NewRequest + { + Account = "yao", + Password = "123456", + Name = "余小章", + Age = 18, + }, "TestUser").Result; + + //assert + Assert.AreEqual(2, count); + using var db = new EmployeeDbContext(dbContextOptions); + + var actual = db.Employees + .Include(p => p.Identity) + .AsNoTracking() + .FirstOrDefault(); + + Assert.AreEqual(actual.Name, "余小章"); + Assert.AreEqual(actual.Age, 18); + Assert.AreEqual(actual.Identity.Account, "yao"); + Assert.AreEqual(actual.Identity.Password, "123456"); + db.Database.EnsureDeleted(); + } + + [TestMethod] + public void 操作真實資料庫_預設EmployeeDbContext() + { + //arrange + DefaultDbContextManager.Now = new DateTime(1900, 1, 1); + DefaultDbContextManager.SetPhysicalDatabase(); + + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(services => { services.AddSingleton(); }); + var host = builder.Build(); + + var repository = host.Services.GetService(); + + //act + var count = repository.NewAsync(new NewRequest + { + Account = "yao", + Password = "123456", + Name = "余小章", + Age = 18, + }, "TestUser").Result; + + //assert + Assert.AreEqual(2, count); + using var db = new TestEmployeeDbContext(TestDbConnectionString); + + var actual = db.Employees + .Include(p => p.Identity) + .AsNoTracking() + .FirstOrDefault(); + + Assert.AreEqual(actual.Name, "余小章"); + Assert.AreEqual(actual.Age, 18); + Assert.AreEqual(actual.Identity.Account, "yao"); + Assert.AreEqual(actual.Identity.Password, "123456"); + } + + [TestMethod] + public void 操作記憶體() + { + DefaultDbContextManager.Now = new DateTime(1900, 1, 1); + DefaultDbContextManager.SetMemoryDatabase(); + + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(services => { services.AddSingleton(); }); + var host = builder.Build(); + + var repository = host.Services.GetService(); + var count = repository.NewAsync(new NewRequest(), "TestUser").Result; + Assert.AreEqual(2, count); + } + + private static DbContextOptions CreateDbContextOptions() + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + + var configRoot = configBuilder.Build(); + var connectionString = configRoot.GetConnectionString("DefaultConnection"); + + var loggerFactory = LoggerFactory.Create(builder => + { + builder + + //.AddFilter("Microsoft", LogLevel.Warning) + //.AddFilter("System", LogLevel.Warning) + .AddFilter("Lab.DAL", LogLevel.Debug) + .AddConsole() + ; + }); + return new DbContextOptionsBuilder() + .UseSqlite(connectionString) + .UseLoggerFactory(loggerFactory) + .Options; + } + + private static void DeleteTestDataRow() + { + var dbContextOptions = s_employeeContextOptions; + using var db = new EmployeeDbContext(dbContextOptions); + var deleteCommand = GetDeleteAllRecordCommand(); + db.Database.ExecuteSqlRaw(deleteCommand); + } + + private static string GetDeleteAllRecordCommand() + { + var sql = @" +-- disable referential integrity +EXEC sp_MSForEachTable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL' + + +EXEC sp_MSForEachTable 'DELETE FROM ?' + + +-- enable referential integrity again +EXEC sp_MSForEachTable 'ALTER TABLE ? WITH CHECK CHECK CONSTRAINT ALL' +"; + + return sql; + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/Lab.SQLite.TestProject.csproj b/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/Lab.SQLite.TestProject.csproj index 01eb2648..43a4d622 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/Lab.SQLite.TestProject.csproj +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/Lab.SQLite.TestProject.csproj @@ -12,8 +12,9 @@ - + + @@ -21,4 +22,20 @@ + + + Always + + + + + + + + + + + + + diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/TestEmployeeDbContext.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/TestEmployeeDbContext.cs new file mode 100644 index 00000000..ffdf16eb --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/TestEmployeeDbContext.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.EntityFrameworkCore; + +namespace Lab.SQLite.EntityModel +{ + public class TestEmployeeDbContext : DbContext + { + private static readonly bool[] s_migrated = {false}; + + public virtual DbSet Employees { get; set; } + + public virtual DbSet Identities { get; set; } + + public virtual DbSet OrderHistories { get; set; } + + private readonly string _connectionString; + + public TestEmployeeDbContext(string connectionString) + { + this._connectionString = connectionString; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + var connectionString = this._connectionString; + if (optionsBuilder.IsConfigured == false) + { + Console.WriteLine($"設定連線字串:{connectionString}"); + optionsBuilder.UseSqlite(connectionString); + } + } + + //管理索引 + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/appsettings.json b/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/appsettings.json new file mode 100644 index 00000000..a83c326c --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Data Source=Lab.DAL.TestProject.db" + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DefaultDbContextManager.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/DefaultDbContextManager.cs index 08bdb7c2..bc0a0261 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DefaultDbContextManager.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/DefaultDbContextManager.cs @@ -70,9 +70,6 @@ static DefaultDbContextManager() { var services = s_services; services.AddDbContextFactory(ApplyConfigurePhysical); - - //services.AddDbContextFactory(ApplyConfigurePhysical); - // services.AddDbContextFactory(ApplyConfigureMemory); return services.BuildServiceProvider(); }); s_configurationLazy diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/InsertOrderRequest.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/InsertOrderRequest.cs index bb552170..0c53a98b 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/InsertOrderRequest.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/InsertOrderRequest.cs @@ -4,7 +4,7 @@ namespace Lab.SQLite.DomainModel.Employee { public class InsertOrderRequest { - public Guid? Employee_Id { get; set; } + public string Employee_Id { get; set; } public string Product_Id { get; set; } diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/NewRequest.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/NewRequest.cs index 7719a278..03987557 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/NewRequest.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/NewRequest.cs @@ -5,9 +5,6 @@ namespace Lab.SQLite.DomainModel.Employee { public class NewRequest { - [Key] - public Guid Id { get; set; } - [Required] public string Name { get; set; } diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeRepository.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeRepository.cs index 1e932f4e..a968face 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeRepository.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeRepository.cs @@ -74,6 +74,7 @@ public async Task InsertLogAsync(InsertOrderRequest request, var toDbOrderHistory = new OrderHistory { + Id = Guid.NewGuid().ToString(), Employee_Id = request.Employee_Id, Product_Id = request.Product_Id, Product_Name = request.Product_Id, @@ -92,7 +93,7 @@ public async Task NewAsync(NewRequest request, { await using var dbContext = this.EmployeeDbContext; - var id = Guid.NewGuid(); + var id = Guid.NewGuid().ToString(); var employeeToDb = new Employee { Id = id, diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Employee.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Employee.cs index 5f9596f1..371948bd 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Employee.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Employee.cs @@ -9,16 +9,13 @@ public class Employee { [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] - public Guid Id { get; set; } + public string Id { get; set; } [Required] public string Name { get; set; } public int? Age { get; set; } - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long SequenceId { get; set; } - public string Remark { get; set; } [Required] diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/EmployeeDbContext.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/EmployeeDbContext.cs index 30d07caa..842b7602 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/EmployeeDbContext.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/EmployeeDbContext.cs @@ -1,5 +1,8 @@ -using Microsoft.EntityFrameworkCore; +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal; namespace Lab.SQLite.EntityModel { @@ -25,7 +28,14 @@ public EmployeeDbContext(DbContextOptions options) { if (s_migrated[0] == false) { - var memoryOptions = options.FindExtension(); + var memoryOptions = options.FindExtension(); + var sqliteOptionsExtension = options.FindExtension(); + + if (sqliteOptionsExtension != null) + { + Console.WriteLine($"EmployeeDbContext 的連線字串為:{sqliteOptionsExtension.ConnectionString},執行 Migration"); + } + if (memoryOptions == null) { this.Database.Migrate(); @@ -46,36 +56,15 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) // // var connectionString = this._connectionString; // if (optionsBuilder.IsConfigured == false) // { - // optionsBuilder.UseSqlServer(connectionString); + // Console.WriteLine("OnConfiguring"); + // optionsBuilder.UseSqlite(connectionString); // } } //管理索引 protected override void OnModelCreating(ModelBuilder modelBuilder) { - // modelBuilder.Entity(p => - // { - // p.HasKey(e => e.Id) - // .IsClustered(false); - // }); - // - // modelBuilder.Entity(p => - // { - // p.HasIndex(e => e.SequenceId) - // .IsUnique() - // .IsClustered(); - // }); - // modelBuilder.Entity(p => - // { - // p.HasKey(e => e.Employee_Id) - // .IsClustered(false); - // }); - // modelBuilder.Entity(p => - // { - // p.HasIndex(e => e.SequenceId) - // .IsUnique() - // .IsClustered(); - // }); + Console.WriteLine("設定資料表定義"); } } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Identity.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Identity.cs index 1f7947c1..e5c9949d 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Identity.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Identity.cs @@ -9,17 +9,14 @@ public class Identity { [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] - public Guid Employee_Id { get; set; } + public string Employee_Id { get; set; } [Required] public string Account { get; set; } [Required] public string Password { get; set; } - - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long SequenceId { get; set; } - + public string Remark { get; set; } [Required] diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/OrderHistory.cs b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/OrderHistory.cs index 12404287..9b015c57 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/OrderHistory.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/OrderHistory.cs @@ -8,16 +8,13 @@ namespace Lab.SQLite.EntityModel public class OrderHistory { [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public Guid Id { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public string Id { get; set; } - public Guid? Employee_Id { get; set; } + public string Employee_Id { get; set; } public string Remark { get; set; } - - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long SequenceId { get; set; } - + public string Product_Id { get; set; } public string Product_Name { get; set; } diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/Lab.SQLite.csproj b/ORM/EFCore/Lab.SQLite/Lab.SQLite/Lab.SQLite.csproj index afd9f79a..71f18786 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/Lab.SQLite.csproj +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite/Lab.SQLite.csproj @@ -2,19 +2,19 @@ net5.0 - - + bin + bin\Lab.DAL.xml - + - + - + <_Parameter1>Lab.SQLite.TestProject From 59ad406276db2eef49186dd4cd0d1efa0a8b2922 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 16 Apr 2021 17:54:11 +0800 Subject: [PATCH 081/424] refactor --- .../EmployeeRepositoryUnitTests.cs | 6 +-- .../Lab.DAL.TestProject.csproj} | 39 +++++++------------ .../TestEmployeeDbContext.cs | 5 +-- .../Lab.DAL.TestProject/appsettings.json | 5 +++ .../DefaultDbContextManager.cs | 4 +- .../Employee/InsertOrderRequest.cs | 4 +- .../DomainModel/Employee/NewRequest.cs | 3 +- .../EmployeeContextFactory.cs | 10 ++--- .../EmployeeRepository.cs | 6 +-- .../EntityModel/Employee.cs | 2 +- .../EntityModel/EmployeeDbContext.cs | 18 ++++----- .../EntityModel/Identity.cs | 2 +- .../EntityModel/OrderHistory.cs | 2 +- .../Lab.DAL.csproj} | 8 +++- .../{Lab.SQLite => Lab.DAL}/appsettings.json | 0 .../Lab.SQLite.TestProject/appsettings.json | 5 --- ORM/EFCore/Lab.SQLite/Lab.SQLite.sln | 20 +++++----- 17 files changed, 65 insertions(+), 74 deletions(-) rename ORM/EFCore/Lab.SQLite/{Lab.SQLite.TestProject => Lab.DAL.TestProject}/EmployeeRepositoryUnitTests.cs (98%) rename ORM/EFCore/Lab.SQLite/{Lab.SQLite.TestProject/Lab.SQLite.TestProject.csproj => Lab.DAL.TestProject/Lab.DAL.TestProject.csproj} (57%) rename ORM/EFCore/Lab.SQLite/{Lab.SQLite.TestProject => Lab.DAL.TestProject}/TestEmployeeDbContext.cs (91%) create mode 100644 ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/appsettings.json rename ORM/EFCore/Lab.SQLite/{Lab.SQLite => Lab.DAL}/DefaultDbContextManager.cs (99%) rename ORM/EFCore/Lab.SQLite/{Lab.SQLite => Lab.DAL}/DomainModel/Employee/InsertOrderRequest.cs (80%) rename ORM/EFCore/Lab.SQLite/{Lab.SQLite => Lab.DAL}/DomainModel/Employee/NewRequest.cs (86%) rename ORM/EFCore/Lab.SQLite/{Lab.SQLite => Lab.DAL}/EmployeeContextFactory.cs (63%) rename ORM/EFCore/Lab.SQLite/{Lab.SQLite => Lab.DAL}/EmployeeRepository.cs (97%) rename ORM/EFCore/Lab.SQLite/{Lab.SQLite => Lab.DAL}/EntityModel/Employee.cs (94%) rename ORM/EFCore/Lab.SQLite/{Lab.SQLite => Lab.DAL}/EntityModel/EmployeeDbContext.cs (78%) rename ORM/EFCore/Lab.SQLite/{Lab.SQLite => Lab.DAL}/EntityModel/Identity.cs (95%) rename ORM/EFCore/Lab.SQLite/{Lab.SQLite => Lab.DAL}/EntityModel/OrderHistory.cs (95%) rename ORM/EFCore/Lab.SQLite/{Lab.SQLite/Lab.SQLite.csproj => Lab.DAL/Lab.DAL.csproj} (82%) rename ORM/EFCore/Lab.SQLite/{Lab.SQLite => Lab.DAL}/appsettings.json (100%) delete mode 100644 ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/appsettings.json diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs similarity index 98% rename from ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/EmployeeRepositoryUnitTests.cs rename to ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs index 2bc478ee..9d9aaaf1 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/EmployeeRepositoryUnitTests.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -1,8 +1,8 @@ using System; using System.IO; using System.Linq; -using Lab.SQLite.DomainModel.Employee; -using Lab.SQLite.EntityModel; +using Lab.DAL.DomainModel.Employee; +using Lab.DAL.EntityModel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Lab.SQLite.TestProject +namespace Lab.DAL.TestProject { [TestClass] public class EmployeeRepositoryUnitTests diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/Lab.SQLite.TestProject.csproj b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj similarity index 57% rename from ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/Lab.SQLite.TestProject.csproj rename to ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj index 43a4d622..d5c3965e 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/Lab.SQLite.TestProject.csproj +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj @@ -2,40 +2,31 @@ net5.0 - bin + bin false - - - - - - - - - + + + + + + + + + + - + - - Always - - - - - - - - - - - + + Always + diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/TestEmployeeDbContext.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/TestEmployeeDbContext.cs similarity index 91% rename from ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/TestEmployeeDbContext.cs rename to ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/TestEmployeeDbContext.cs index ffdf16eb..74a2e32d 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/TestEmployeeDbContext.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/TestEmployeeDbContext.cs @@ -1,12 +1,11 @@ using System; +using Lab.DAL.EntityModel; using Microsoft.EntityFrameworkCore; -namespace Lab.SQLite.EntityModel +namespace Lab.DAL.TestProject { public class TestEmployeeDbContext : DbContext { - private static readonly bool[] s_migrated = {false}; - public virtual DbSet Employees { get; set; } public virtual DbSet Identities { get; set; } diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/appsettings.json b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/appsettings.json new file mode 100644 index 00000000..90427e9f --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Data Source=bin\\Lab.DAL.TestProject.db" + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DefaultDbContextManager.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/DefaultDbContextManager.cs similarity index 99% rename from ORM/EFCore/Lab.SQLite/Lab.SQLite/DefaultDbContextManager.cs rename to ORM/EFCore/Lab.SQLite/Lab.DAL/DefaultDbContextManager.cs index bc0a0261..4a70778a 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DefaultDbContextManager.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/DefaultDbContextManager.cs @@ -1,12 +1,12 @@ using System; using System.IO; -using Lab.SQLite.EntityModel; +using Lab.DAL.EntityModel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Lab.SQLite +namespace Lab.DAL { internal class DefaultDbContextManager { diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/InsertOrderRequest.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/DomainModel/Employee/InsertOrderRequest.cs similarity index 80% rename from ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/InsertOrderRequest.cs rename to ORM/EFCore/Lab.SQLite/Lab.DAL/DomainModel/Employee/InsertOrderRequest.cs index 0c53a98b..52598da3 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/InsertOrderRequest.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/DomainModel/Employee/InsertOrderRequest.cs @@ -1,6 +1,4 @@ -using System; - -namespace Lab.SQLite.DomainModel.Employee +namespace Lab.DAL.DomainModel.Employee { public class InsertOrderRequest { diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/NewRequest.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/DomainModel/Employee/NewRequest.cs similarity index 86% rename from ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/NewRequest.cs rename to ORM/EFCore/Lab.SQLite/Lab.DAL/DomainModel/Employee/NewRequest.cs index 03987557..490f696e 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/DomainModel/Employee/NewRequest.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/DomainModel/Employee/NewRequest.cs @@ -1,7 +1,6 @@ -using System; using System.ComponentModel.DataAnnotations; -namespace Lab.SQLite.DomainModel.Employee +namespace Lab.DAL.DomainModel.Employee { public class NewRequest { diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeContextFactory.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/EmployeeContextFactory.cs similarity index 63% rename from ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeContextFactory.cs rename to ORM/EFCore/Lab.SQLite/Lab.DAL/EmployeeContextFactory.cs index 1a3a45c4..44a7b638 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeContextFactory.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/EmployeeContextFactory.cs @@ -1,25 +1,25 @@ using System; -using Lab.SQLite.EntityModel; +using Lab.DAL.EntityModel; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.Extensions.Configuration; -namespace Lab.SQLite +namespace Lab.DAL { public class EmployeeContextFactory : IDesignTimeDbContextFactory { public EmployeeDbContext CreateDbContext(string[] args) { - Console.WriteLine("由設計工具產生 Database,初始化 DbContextOptionsBuilder"); + Console.WriteLine("EmployeeContextFactory - 由設計工具產生 Database,初始化 DbContextOptionsBuilder"); var config = DefaultDbContextManager.Configuration; var connectionString = config.GetConnectionString("DefaultConnection"); - Console.WriteLine($"由 appsettings.json 讀取連線字串為:{connectionString}"); + Console.WriteLine($"EmployeeContextFactory - 讀取 appsettings.json 檔案的讀取連線字串為:{connectionString}"); var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite(connectionString); - Console.WriteLine($"DbContextOptionsBuilder 設定完成"); + Console.WriteLine($"EmployeeContextFactory - DbContextOptionsBuilder 設定完成"); return new EmployeeDbContext(optionsBuilder.Options); } diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeRepository.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/EmployeeRepository.cs similarity index 97% rename from ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeRepository.cs rename to ORM/EFCore/Lab.SQLite/Lab.DAL/EmployeeRepository.cs index a968face..eec3bcfe 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EmployeeRepository.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/EmployeeRepository.cs @@ -1,11 +1,11 @@ using System; using System.Threading; using System.Threading.Tasks; -using Lab.SQLite.DomainModel.Employee; -using Lab.SQLite.EntityModel; +using Lab.DAL.DomainModel.Employee; +using Lab.DAL.EntityModel; using Microsoft.EntityFrameworkCore; -namespace Lab.SQLite +namespace Lab.DAL { public interface IEmployeeRepository { diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Employee.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/EntityModel/Employee.cs similarity index 94% rename from ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Employee.cs rename to ORM/EFCore/Lab.SQLite/Lab.DAL/EntityModel/Employee.cs index 371948bd..ca5252e6 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Employee.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/EntityModel/Employee.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Lab.SQLite.EntityModel +namespace Lab.DAL.EntityModel { [Table("Employee")] public class Employee diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/EmployeeDbContext.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/EntityModel/EmployeeDbContext.cs similarity index 78% rename from ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/EmployeeDbContext.cs rename to ORM/EFCore/Lab.SQLite/Lab.DAL/EntityModel/EmployeeDbContext.cs index 842b7602..c8d3832f 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/EmployeeDbContext.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/EntityModel/EmployeeDbContext.cs @@ -1,10 +1,9 @@ using System; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal; -namespace Lab.SQLite.EntityModel +namespace Lab.DAL.EntityModel { public class EmployeeDbContext : DbContext { @@ -28,16 +27,17 @@ public EmployeeDbContext(DbContextOptions options) { if (s_migrated[0] == false) { - var memoryOptions = options.FindExtension(); - var sqliteOptionsExtension = options.FindExtension(); - - if (sqliteOptionsExtension != null) - { - Console.WriteLine($"EmployeeDbContext 的連線字串為:{sqliteOptionsExtension.ConnectionString},執行 Migration"); - } + var memoryOptions = options.FindExtension(); if (memoryOptions == null) { + var sqliteOptionsExtension = options.FindExtension(); + + if (sqliteOptionsExtension != null) + { + Console.WriteLine($"EmployeeDbContext 的連線字串為:{sqliteOptionsExtension.ConnectionString},執行 Migration"); + } + this.Database.Migrate(); } diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Identity.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/EntityModel/Identity.cs similarity index 95% rename from ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Identity.cs rename to ORM/EFCore/Lab.SQLite/Lab.DAL/EntityModel/Identity.cs index e5c9949d..5ab55f04 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/Identity.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/EntityModel/Identity.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Lab.SQLite.EntityModel +namespace Lab.DAL.EntityModel { [Table("Identity")] public class Identity diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/OrderHistory.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/EntityModel/OrderHistory.cs similarity index 95% rename from ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/OrderHistory.cs rename to ORM/EFCore/Lab.SQLite/Lab.DAL/EntityModel/OrderHistory.cs index 9b015c57..7a77ca2e 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/EntityModel/OrderHistory.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/EntityModel/OrderHistory.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Lab.SQLite.EntityModel +namespace Lab.DAL.EntityModel { [Table("OrderHistory")] public class OrderHistory diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/Lab.SQLite.csproj b/ORM/EFCore/Lab.SQLite/Lab.DAL/Lab.DAL.csproj similarity index 82% rename from ORM/EFCore/Lab.SQLite/Lab.SQLite/Lab.SQLite.csproj rename to ORM/EFCore/Lab.SQLite/Lab.DAL/Lab.DAL.csproj index 71f18786..2ad7fe1f 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite/Lab.SQLite.csproj +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/Lab.DAL.csproj @@ -14,10 +14,14 @@ - - <_Parameter1>Lab.SQLite.TestProject + <_Parameter1>Lab.DAL.TestProject + + + Always + + diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite/appsettings.json b/ORM/EFCore/Lab.SQLite/Lab.DAL/appsettings.json similarity index 100% rename from ORM/EFCore/Lab.SQLite/Lab.SQLite/appsettings.json rename to ORM/EFCore/Lab.SQLite/Lab.DAL/appsettings.json diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/appsettings.json b/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/appsettings.json deleted file mode 100644 index a83c326c..00000000 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite.TestProject/appsettings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "ConnectionStrings": { - "DefaultConnection": "Data Source=Lab.DAL.TestProject.db" - } -} \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.SQLite.sln b/ORM/EFCore/Lab.SQLite/Lab.SQLite.sln index 1bb14120..049ab624 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.SQLite.sln +++ b/ORM/EFCore/Lab.SQLite/Lab.SQLite.sln @@ -1,8 +1,8 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SQLite", "Lab.SQLite\Lab.SQLite.csproj", "{ECC70BD1-FB1E-442C-B676-386C11510B43}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.DAL", "Lab.DAL\Lab.DAL.csproj", "{663838CE-1F72-483A-B981-8CCE685911C3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SQLite.TestProject", "Lab.SQLite.TestProject\Lab.SQLite.TestProject.csproj", "{C3821F71-3F5F-4480-8D84-782A55772101}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.DAL.TestProject", "Lab.DAL.TestProject\Lab.DAL.TestProject.csproj", "{EFF09A2D-3B94-4BCF-8A38-18186E64F25F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -10,13 +10,13 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {ECC70BD1-FB1E-442C-B676-386C11510B43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ECC70BD1-FB1E-442C-B676-386C11510B43}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ECC70BD1-FB1E-442C-B676-386C11510B43}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ECC70BD1-FB1E-442C-B676-386C11510B43}.Release|Any CPU.Build.0 = Release|Any CPU - {C3821F71-3F5F-4480-8D84-782A55772101}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C3821F71-3F5F-4480-8D84-782A55772101}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C3821F71-3F5F-4480-8D84-782A55772101}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C3821F71-3F5F-4480-8D84-782A55772101}.Release|Any CPU.Build.0 = Release|Any CPU + {663838CE-1F72-483A-B981-8CCE685911C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {663838CE-1F72-483A-B981-8CCE685911C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {663838CE-1F72-483A-B981-8CCE685911C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {663838CE-1F72-483A-B981-8CCE685911C3}.Release|Any CPU.Build.0 = Release|Any CPU + {EFF09A2D-3B94-4BCF-8A38-18186E64F25F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EFF09A2D-3B94-4BCF-8A38-18186E64F25F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EFF09A2D-3B94-4BCF-8A38-18186E64F25F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EFF09A2D-3B94-4BCF-8A38-18186E64F25F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 060119b1156d1ea041aaeacbfa54d427ad552888 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 16 Apr 2021 18:59:41 +0800 Subject: [PATCH 082/424] refactor --- .../EmployeeRepositoryUnitTests.cs | 36 +++-- .../Lab.DAL.TestProject/appsettings.json | 2 +- .../Lab.SQLite/Lab.DAL/EmployeeRepository.cs | 3 +- .../20210416101355_InitialCreate.Designer.cs | 123 ++++++++++++++++++ .../20210416101355_InitialCreate.cs | 78 +++++++++++ .../EmployeeDbContextModelSnapshot.cs | 121 +++++++++++++++++ 6 files changed, 349 insertions(+), 14 deletions(-) create mode 100644 ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210416101355_InitialCreate.Designer.cs create mode 100644 ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210416101355_InitialCreate.cs create mode 100644 ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/EmployeeDbContextModelSnapshot.cs diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs index 9d9aaaf1..b99306e9 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -16,7 +16,8 @@ namespace Lab.DAL.TestProject public class EmployeeRepositoryUnitTests { private static readonly DbContextOptions s_employeeContextOptions; - private static readonly string TestDbConnectionString = "Data Source=Lab.DAL.TestProject.db"; + private static readonly string TestDbConnectionString1 = "Data Source=Lab.DAL.TestProject.db"; + private static readonly string TestDbConnectionString2 = "Data Source=Lab.DAL.Injection.db"; static EmployeeRepositoryUnitTests() { @@ -29,8 +30,11 @@ public static void AssemblyCleanup() //刪除測試資料庫 Console.WriteLine("AssemblyCleanup"); - using var db = new TestEmployeeDbContext(TestDbConnectionString); - db.Database.EnsureDeleted(); + using var db1 = new TestEmployeeDbContext(TestDbConnectionString1); + db1.Database.EnsureDeleted(); + + using var db2 = new TestEmployeeDbContext(TestDbConnectionString2); + db2.Database.EnsureDeleted(); } [AssemblyInitialize] @@ -38,8 +42,11 @@ public static void AssemblyInitialize(TestContext context) { //刪除測試資料庫 Console.WriteLine("AssemblyInitialize"); - using var db = new TestEmployeeDbContext(TestDbConnectionString); - db.Database.EnsureDeleted(); + using var db1 = new TestEmployeeDbContext(TestDbConnectionString1); + db1.Database.EnsureDeleted(); + + using var db2 = new TestEmployeeDbContext(TestDbConnectionString2); + db2.Database.EnsureDeleted(); // //建立測試資料庫 // db.Database.Migrate(); @@ -72,7 +79,6 @@ public void 操作真實資料庫_手動取得Repository執行個體() var repository = new EmployeeRepository(); - // var id = Guid.NewGuid(); repository.NewAsync(new NewRequest { Account = "yao", @@ -81,7 +87,8 @@ public void 操作真實資料庫_手動取得Repository執行個體() Age = 18, Remark = "測試案例,持續航向偉大航道" }, "TestUser").Wait(); - using var db = new TestEmployeeDbContext(TestDbConnectionString); + + using var db = new TestEmployeeDbContext(TestDbConnectionString1); var id = db.Employees.FirstOrDefault(p => p.Name == "余小章").Id; //act @@ -119,7 +126,11 @@ public void 操作真實資料庫_注入EmployeeDbContext() var dbContextOptions = host.Services.GetService>(); var repository = host.Services.GetService(); var employeeDbContext = host.Services.GetService(); - // employeeDbContext.Database.Migrate(); + + if (employeeDbContext.Database.CanConnect() == false) + { + employeeDbContext.Database.Migrate(); + } repository.EmployeeDbContext = employeeDbContext; @@ -134,6 +145,7 @@ public void 操作真實資料庫_注入EmployeeDbContext() //assert Assert.AreEqual(2, count); + using var db = new EmployeeDbContext(dbContextOptions); var actual = db.Employees @@ -145,7 +157,6 @@ public void 操作真實資料庫_注入EmployeeDbContext() Assert.AreEqual(actual.Age, 18); Assert.AreEqual(actual.Identity.Account, "yao"); Assert.AreEqual(actual.Identity.Password, "123456"); - db.Database.EnsureDeleted(); } [TestMethod] @@ -156,7 +167,10 @@ public void 操作真實資料庫_預設EmployeeDbContext() DefaultDbContextManager.SetPhysicalDatabase(); var builder = Host.CreateDefaultBuilder() - .ConfigureServices(services => { services.AddSingleton(); }); + .ConfigureServices(services => + { + services.AddSingleton(); + }); var host = builder.Build(); var repository = host.Services.GetService(); @@ -172,7 +186,7 @@ public void 操作真實資料庫_預設EmployeeDbContext() //assert Assert.AreEqual(2, count); - using var db = new TestEmployeeDbContext(TestDbConnectionString); + using var db = new TestEmployeeDbContext(TestDbConnectionString1); var actual = db.Employees .Include(p => p.Identity) diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/appsettings.json b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/appsettings.json index 90427e9f..a83c326c 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/appsettings.json +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/appsettings.json @@ -1,5 +1,5 @@ { "ConnectionStrings": { - "DefaultConnection": "Data Source=bin\\Lab.DAL.TestProject.db" + "DefaultConnection": "Data Source=Lab.DAL.TestProject.db" } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL/EmployeeRepository.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/EmployeeRepository.cs index eec3bcfe..cd607a64 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.DAL/EmployeeRepository.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/EmployeeRepository.cs @@ -91,8 +91,7 @@ public async Task NewAsync(NewRequest request, string accessId, CancellationToken cancel = default) { - await using var dbContext = this.EmployeeDbContext; - + using var dbContext = this.EmployeeDbContext; var id = Guid.NewGuid().ToString(); var employeeToDb = new Employee { diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210416101355_InitialCreate.Designer.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210416101355_InitialCreate.Designer.cs new file mode 100644 index 00000000..213fa1c9 --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210416101355_InitialCreate.Designer.cs @@ -0,0 +1,123 @@ +// +using System; +using Lab.DAL.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Lab.DAL.Migrations +{ + [DbContext(typeof(EmployeeDbContext))] + [Migration("20210416101355_InitialCreate")] + partial class InitialCreate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.5"); + + modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Age") + .HasColumnType("INTEGER"); + + b.Property("CreateAt") + .HasColumnType("TEXT"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Remark") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Employee"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => + { + b.Property("Employee_Id") + .HasColumnType("TEXT"); + + b.Property("Account") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreateAt") + .HasColumnType("TEXT"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Remark") + .HasColumnType("TEXT"); + + b.HasKey("Employee_Id"); + + b.ToTable("Identity"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.OrderHistory", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreateAt") + .HasColumnType("TEXT"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Employee_Id") + .HasColumnType("TEXT"); + + b.Property("Product_Id") + .HasColumnType("TEXT"); + + b.Property("Product_Name") + .HasColumnType("TEXT"); + + b.Property("Remark") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("OrderHistory"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => + { + b.HasOne("Lab.DAL.EntityModel.Employee", "Employee") + .WithOne("Identity") + .HasForeignKey("Lab.DAL.EntityModel.Identity", "Employee_Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => + { + b.Navigation("Identity"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210416101355_InitialCreate.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210416101355_InitialCreate.cs new file mode 100644 index 00000000..1e11c0c3 --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210416101355_InitialCreate.cs @@ -0,0 +1,78 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Lab.DAL.Migrations +{ + public partial class InitialCreate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Employee", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Age = table.Column(type: "INTEGER", nullable: true), + Remark = table.Column(type: "TEXT", nullable: true), + CreateAt = table.Column(type: "TEXT", nullable: false), + CreateBy = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Employee", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "OrderHistory", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Employee_Id = table.Column(type: "TEXT", nullable: true), + Remark = table.Column(type: "TEXT", nullable: true), + Product_Id = table.Column(type: "TEXT", nullable: true), + Product_Name = table.Column(type: "TEXT", nullable: true), + CreateAt = table.Column(type: "TEXT", nullable: false), + CreateBy = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderHistory", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Identity", + columns: table => new + { + Employee_Id = table.Column(type: "TEXT", nullable: false), + Account = table.Column(type: "TEXT", nullable: false), + Password = table.Column(type: "TEXT", nullable: false), + Remark = table.Column(type: "TEXT", nullable: true), + CreateAt = table.Column(type: "TEXT", nullable: false), + CreateBy = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Identity", x => x.Employee_Id); + table.ForeignKey( + name: "FK_Identity_Employee_Employee_Id", + column: x => x.Employee_Id, + principalTable: "Employee", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Identity"); + + migrationBuilder.DropTable( + name: "OrderHistory"); + + migrationBuilder.DropTable( + name: "Employee"); + } + } +} diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/EmployeeDbContextModelSnapshot.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/EmployeeDbContextModelSnapshot.cs new file mode 100644 index 00000000..7d2d01a4 --- /dev/null +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/EmployeeDbContextModelSnapshot.cs @@ -0,0 +1,121 @@ +// +using System; +using Lab.DAL.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Lab.DAL.Migrations +{ + [DbContext(typeof(EmployeeDbContext))] + partial class EmployeeDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.5"); + + modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Age") + .HasColumnType("INTEGER"); + + b.Property("CreateAt") + .HasColumnType("TEXT"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Remark") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Employee"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => + { + b.Property("Employee_Id") + .HasColumnType("TEXT"); + + b.Property("Account") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreateAt") + .HasColumnType("TEXT"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Remark") + .HasColumnType("TEXT"); + + b.HasKey("Employee_Id"); + + b.ToTable("Identity"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.OrderHistory", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreateAt") + .HasColumnType("TEXT"); + + b.Property("CreateBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Employee_Id") + .HasColumnType("TEXT"); + + b.Property("Product_Id") + .HasColumnType("TEXT"); + + b.Property("Product_Name") + .HasColumnType("TEXT"); + + b.Property("Remark") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("OrderHistory"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Identity", b => + { + b.HasOne("Lab.DAL.EntityModel.Employee", "Employee") + .WithOne("Identity") + .HasForeignKey("Lab.DAL.EntityModel.Identity", "Employee_Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Lab.DAL.EntityModel.Employee", b => + { + b.Navigation("Identity"); + }); +#pragma warning restore 612, 618 + } + } +} From 4efc03fcdff4604d0c4aeba7b512867d924f438f Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 16 Apr 2021 22:18:23 +0800 Subject: [PATCH 083/424] refactor --- .../Lab.DAL.TestProject/Lab.DAL.TestProject.csproj | 2 +- ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj index d1e58815..4ac56c73 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj @@ -16,7 +16,7 @@ - + diff --git a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj index d5ddf873..77c9a56d 100644 --- a/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj +++ b/ORM/EFCore/Lab.VirtualDb/Lab.DAL/Lab.DAL.csproj @@ -8,7 +8,7 @@ - + From bc69418adb470ec3d6c5f05d15450c1779edc4ca Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Sat, 17 Apr 2021 12:56:04 +0800 Subject: [PATCH 084/424] refactor --- .../EmployeeRepositoryUnitTests.cs | 28 +++++++++++++++---- .../Lab.DAL.TestProject.csproj | 22 +++++++-------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs index b99306e9..e22ccd85 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -105,7 +105,28 @@ public void 操作真實資料庫_手動取得Repository執行個體() } [TestMethod] - public void 操作真實資料庫_注入EmployeeDbContext() + public void 操作真實資料庫_手動實例化EmployeeDbContext() + { + var contextOptions = CreateDbContextOptions(); + using var dbContext = new EmployeeDbContext(contextOptions); + var id = Guid.NewGuid().ToString(); + dbContext.Employees.Add(new Employee() + { + Age = 18, + Id = id, + CreateAt = DateTime.Now, + CreateBy = "test", + Name = "yao" + }); + dbContext.SaveChanges(); + + var actual = dbContext.Employees.AsNoTracking().FirstOrDefault(p => p.Id ==id); + Assert.AreEqual(18,actual.Age); + Assert.AreEqual("yao",actual.Name); + } + + [TestMethod] + public void 操作真實資料庫_由Host注入EmployeeDbContext() { //arrange DefaultDbContextManager.Now = new DateTime(1900, 1, 1); @@ -167,10 +188,7 @@ public void 操作真實資料庫_預設EmployeeDbContext() DefaultDbContextManager.SetPhysicalDatabase(); var builder = Host.CreateDefaultBuilder() - .ConfigureServices(services => - { - services.AddSingleton(); - }); + .ConfigureServices(services => { services.AddSingleton(); }); var host = builder.Build(); var repository = host.Services.GetService(); diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj index d5c3965e..aebb5b9b 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj @@ -7,20 +7,20 @@ - - - - - - - - - - + + + + + + + + + + - + From b4a96a43042dcfad813cd1aa5736a35892e8ad8a Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Sat, 17 Apr 2021 22:50:39 +0800 Subject: [PATCH 085/424] add sample --- .../EmployeeRepositoryUnitTests.cs | 108 ++++++++++++++---- 1 file changed, 87 insertions(+), 21 deletions(-) diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs index e22ccd85..917fc0ab 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -4,6 +4,7 @@ using Lab.DAL.DomainModel.Employee; using Lab.DAL.EntityModel; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -120,49 +121,114 @@ public void 操作真實資料庫_手動實例化EmployeeDbContext() }); dbContext.SaveChanges(); - var actual = dbContext.Employees.AsNoTracking().FirstOrDefault(p => p.Id ==id); - Assert.AreEqual(18,actual.Age); - Assert.AreEqual("yao",actual.Name); + var actual = dbContext.Employees.AsNoTracking().FirstOrDefault(p => p.Id == id); + Assert.AreEqual(18, actual.Age); + Assert.AreEqual("yao", actual.Name); } [TestMethod] - public void 操作真實資料庫_由Host注入EmployeeDbContext() + public void 操作真實資料庫_由Host註冊EmployeeDbContext() { //arrange - DefaultDbContextManager.Now = new DateTime(1900, 1, 1); - - var connectionString = "Data Source=Lab.DAL.Injection.db"; - var builder = Host.CreateDefaultBuilder() .ConfigureServices(services => { - services.AddSingleton(); - services.AddDbContext(builder => + services.AddDbContext((provider, builder) => { + var config = + provider.GetService(); + var connectionString = + config + .GetConnectionString("DefaultConnection"); builder.UseSqlite(connectionString); }); }); var host = builder.Build(); - var dbContextOptions = host.Services.GetService>(); - var repository = host.Services.GetService(); - var employeeDbContext = host.Services.GetService(); + var dbContextOptions = host.Services.GetService>(); + using var dbContext = host.Services.GetService(); - if (employeeDbContext.Database.CanConnect() == false) + //act + var id = Guid.NewGuid().ToString(); + var now = DateTime.Now; + dbContext.Employees.Add(new Employee() + { + Id = id, + Name = "余小章", + Age = 18, + CreateAt = now, + CreateBy = "test user" + }); + dbContext.Identities.Add(new Identity() { - employeeDbContext.Database.Migrate(); - } + Employee_Id = id, + Account = "yao", + Password = "123456", + CreateAt = now, + CreateBy = "test user" + }); + var count = dbContext.SaveChanges(); - repository.EmployeeDbContext = employeeDbContext; + //assert + Assert.AreEqual(2, count); + + using var db = new EmployeeDbContext(dbContextOptions); + + var actual = db.Employees + .Include(p => p.Identity) + .AsNoTracking() + .FirstOrDefault(); + + Assert.AreEqual(actual.Name, "余小章"); + Assert.AreEqual(actual.Age, 18); + Assert.AreEqual(actual.Identity.Account, "yao"); + Assert.AreEqual(actual.Identity.Password, "123456"); + } + + [TestMethod] + public void 操作真實資料庫_由Host註冊EmployeeDbContextFactory() + { + //arrange + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(services => + { + services + .AddDbContextFactory((provider, builder) => + { + var config = + provider.GetService(); + var connectionString = + config + .GetConnectionString("DefaultConnection"); + builder.UseSqlite(connectionString); + }); + }); + var host = builder.Build(); + + var dbContextOptions = host.Services.GetService>(); + var dbContextFactory = host.Services.GetService>(); + using var dbContext = dbContextFactory.CreateDbContext(); //act - var count = repository.NewAsync(new NewRequest + var id = Guid.NewGuid().ToString(); + var now = DateTime.Now; + dbContext.Employees.Add(new Employee() { - Account = "yao", - Password = "123456", + Id = id, Name = "余小章", Age = 18, - }, "TestUser").Result; + CreateAt = now, + CreateBy = "test user" + }); + dbContext.Identities.Add(new Identity() + { + Employee_Id = id, + Account = "yao", + Password = "123456", + CreateAt = now, + CreateBy = "test user" + }); + var count = dbContext.SaveChanges(); //assert Assert.AreEqual(2, count); From 278dc5963da8b40c5f386010ce22d8666dcecbcb Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Sat, 17 Apr 2021 23:52:47 +0800 Subject: [PATCH 086/424] add tes case --- .../EmployeeRepositoryUnitTests.cs | 167 ++++++++++++++++-- 1 file changed, 148 insertions(+), 19 deletions(-) diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs index 917fc0ab..de11d7dd 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -133,15 +133,18 @@ public void 操作真實資料庫_由Host註冊EmployeeDbContext() var builder = Host.CreateDefaultBuilder() .ConfigureServices(services => { - services.AddDbContext((provider, builder) => - { - var config = - provider.GetService(); - var connectionString = - config - .GetConnectionString("DefaultConnection"); - builder.UseSqlite(connectionString); - }); + services.AddDbContext( + (provider, builder) => + { + var config = + provider.GetService(); + var connectionString = + config.GetConnectionString("DefaultConnection"); + var loggerFactory = provider.GetService(); + builder.UseSqlite(connectionString) + .UseLoggerFactory(loggerFactory) + ; + }); }); var host = builder.Build(); @@ -185,6 +188,67 @@ public void 操作真實資料庫_由Host註冊EmployeeDbContext() Assert.AreEqual(actual.Identity.Password, "123456"); } + [TestMethod] + public void 操作真實資料庫_由Host註冊EmployeeDbContextPool() + { + //arrange + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(services => + { + services.AddDbContextPool( + (provider, builder) => + { + var config = + provider.GetService(); + var connectionString = + config.GetConnectionString("DefaultConnection"); + var loggerFactory = provider.GetService(); + builder.UseSqlite(connectionString) + .UseLoggerFactory(loggerFactory) + ; + }, 64); + }); + var host = builder.Build(); + + var dbContextOptions = host.Services.GetService>(); + using var dbContext = host.Services.GetService(); + + //act + var id = Guid.NewGuid().ToString(); + var now = DateTime.Now; + dbContext.Employees.Add(new Employee() + { + Id = id, + Name = "余小章", + Age = 18, + CreateAt = now, + CreateBy = "test user" + }); + dbContext.Identities.Add(new Identity() + { + Employee_Id = id, + Account = "yao", + Password = "123456", + CreateAt = now, + CreateBy = "test user" + }); + var count = dbContext.SaveChanges(); + + //assert + Assert.AreEqual(2, count); + + using var db = new EmployeeDbContext(dbContextOptions); + + var actual = db.Employees + .Include(p => p.Identity) + .AsNoTracking() + .FirstOrDefault(); + + Assert.AreEqual(actual.Name, "余小章"); + Assert.AreEqual(actual.Age, 18); + Assert.AreEqual(actual.Identity.Account, "yao"); + Assert.AreEqual(actual.Identity.Password, "123456"); + } [TestMethod] public void 操作真實資料庫_由Host註冊EmployeeDbContextFactory() { @@ -192,16 +256,81 @@ public void 操作真實資料庫_由Host註冊EmployeeDbContextFactory() var builder = Host.CreateDefaultBuilder() .ConfigureServices(services => { - services - .AddDbContextFactory((provider, builder) => - { - var config = - provider.GetService(); - var connectionString = - config - .GetConnectionString("DefaultConnection"); - builder.UseSqlite(connectionString); - }); + services.AddDbContextFactory( + (provider, builder) => + { + var config = + provider.GetService(); + var connectionString = + config.GetConnectionString("DefaultConnection"); + var loggerFactory = provider.GetService(); + builder.UseSqlite(connectionString) + .UseLoggerFactory(loggerFactory) + ; + }); + }); + var host = builder.Build(); + + var dbContextOptions = host.Services.GetService>(); + var dbContextFactory = host.Services.GetService>(); + using var dbContext = dbContextFactory.CreateDbContext(); + + //act + var id = Guid.NewGuid().ToString(); + var now = DateTime.Now; + dbContext.Employees.Add(new Employee() + { + Id = id, + Name = "余小章", + Age = 18, + CreateAt = now, + CreateBy = "test user" + }); + dbContext.Identities.Add(new Identity() + { + Employee_Id = id, + Account = "yao", + Password = "123456", + CreateAt = now, + CreateBy = "test user" + }); + var count = dbContext.SaveChanges(); + + //assert + Assert.AreEqual(2, count); + + using var db = new EmployeeDbContext(dbContextOptions); + + var actual = db.Employees + .Include(p => p.Identity) + .AsNoTracking() + .FirstOrDefault(); + + Assert.AreEqual(actual.Name, "余小章"); + Assert.AreEqual(actual.Age, 18); + Assert.AreEqual(actual.Identity.Account, "yao"); + Assert.AreEqual(actual.Identity.Password, "123456"); + } + + [TestMethod] + public void 操作真實資料庫_由Host註冊EmployeeDbContextPoolFactory() + { + //arrange + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(services => + { + services.AddPooledDbContextFactory( + (provider, builder) => + { + var config = + provider.GetService(); + var connectionString = + config.GetConnectionString("DefaultConnection"); + var loggerFactory = provider.GetService(); + builder.UseSqlite(connectionString) + .UseLoggerFactory(loggerFactory) + ; + },64); }); var host = builder.Build(); From d7fd4be8ee59c58c907b66bbd057d87fbc48f8a1 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Sun, 18 Apr 2021 20:18:59 +0800 Subject: [PATCH 087/424] refactor --- .../EmployeeRepositoryUnitTests.cs | 56 ++++++++++++++++++- ... 20210418023303_InitialCreate.Designer.cs} | 2 +- ...ate.cs => 20210418023303_InitialCreate.cs} | 0 3 files changed, 56 insertions(+), 2 deletions(-) rename ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/{20210416101355_InitialCreate.Designer.cs => 20210418023303_InitialCreate.Designer.cs} (98%) rename ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/{20210416101355_InitialCreate.cs => 20210418023303_InitialCreate.cs} (100%) diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs index de11d7dd..fc81c1c9 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Threading.Tasks; using Lab.DAL.DomainModel.Employee; using Lab.DAL.EntityModel; using Microsoft.EntityFrameworkCore; @@ -126,6 +127,58 @@ public void 操作真實資料庫_手動實例化EmployeeDbContext() Assert.AreEqual("yao", actual.Name); } + [TestMethod] + public async Task 操作真實資料庫_() + { + //arrange + var builder = Host.CreateDefaultBuilder() + .ConfigureServices(services => + { + services.AddDbContext( + (provider, builder) => + { + var config = + provider.GetService(); + var connectionString = + config.GetConnectionString("DefaultConnection"); + var loggerFactory = provider.GetService(); + builder.UseSqlite(connectionString) + .UseLoggerFactory(loggerFactory) + ; + }); + }); + var host = builder.Build(); + + var dbContextOptions = host.Services.GetService>(); + await using var dbContext = host.Services.GetService(); + + //act + var id = Guid.NewGuid().ToString(); + var now = DateTime.Now; + dbContext.Employees.Add(new Employee() + { + Id = id, + Name = "余小章", + Age = 18, + CreateAt = now, + CreateBy = "test user" + }); + dbContext.Identities.Add(new Identity() + { + Employee_Id = id, + Account = "yao", + Password = "123456", + CreateAt = now, + CreateBy = "test user" + }); + var count = await dbContext.SaveChangesAsync(); + + // var employeesTask = dbContext.Employees.OrderBy(p=>p.Id).AsNoTracking().LastAsync(); + var employeesTask = dbContext.Employees.AsNoTracking().LastAsync(); + var identitiesTask = dbContext.Identities.OrderBy(p => p.Employee_Id).AsNoTracking().LastAsync(); + await Task.WhenAll(employeesTask, identitiesTask); + } + [TestMethod] public void 操作真實資料庫_由Host註冊EmployeeDbContext() { @@ -249,6 +302,7 @@ public void 操作真實資料庫_由Host註冊EmployeeDbContextPool() Assert.AreEqual(actual.Identity.Account, "yao"); Assert.AreEqual(actual.Identity.Password, "123456"); } + [TestMethod] public void 操作真實資料庫_由Host註冊EmployeeDbContextFactory() { @@ -330,7 +384,7 @@ public void 操作真實資料庫_由Host註冊EmployeeDbContextPoolFactory() builder.UseSqlite(connectionString) .UseLoggerFactory(loggerFactory) ; - },64); + }, 64); }); var host = builder.Build(); diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210416101355_InitialCreate.Designer.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210418023303_InitialCreate.Designer.cs similarity index 98% rename from ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210416101355_InitialCreate.Designer.cs rename to ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210418023303_InitialCreate.Designer.cs index 213fa1c9..55cf0c0e 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210416101355_InitialCreate.Designer.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210418023303_InitialCreate.Designer.cs @@ -9,7 +9,7 @@ namespace Lab.DAL.Migrations { [DbContext(typeof(EmployeeDbContext))] - [Migration("20210416101355_InitialCreate")] + [Migration("20210418023303_InitialCreate")] partial class InitialCreate { protected override void BuildTargetModel(ModelBuilder modelBuilder) diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210416101355_InitialCreate.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210418023303_InitialCreate.cs similarity index 100% rename from ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210416101355_InitialCreate.cs rename to ORM/EFCore/Lab.SQLite/Lab.DAL/Migrations/20210418023303_InitialCreate.cs From 6a362dbbeadd30d6998b67c7e939f35c7c47dfca Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Sun, 18 Apr 2021 20:27:41 +0800 Subject: [PATCH 088/424] refactor --- .../EmployeeRepositoryUnitTests.cs | 52 ------------------- .../Lab.DAL.TestProject.csproj | 19 +++---- 2 files changed, 8 insertions(+), 63 deletions(-) diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs index fc81c1c9..647c94ae 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/EmployeeRepositoryUnitTests.cs @@ -127,58 +127,6 @@ public void 操作真實資料庫_手動實例化EmployeeDbContext() Assert.AreEqual("yao", actual.Name); } - [TestMethod] - public async Task 操作真實資料庫_() - { - //arrange - var builder = Host.CreateDefaultBuilder() - .ConfigureServices(services => - { - services.AddDbContext( - (provider, builder) => - { - var config = - provider.GetService(); - var connectionString = - config.GetConnectionString("DefaultConnection"); - var loggerFactory = provider.GetService(); - builder.UseSqlite(connectionString) - .UseLoggerFactory(loggerFactory) - ; - }); - }); - var host = builder.Build(); - - var dbContextOptions = host.Services.GetService>(); - await using var dbContext = host.Services.GetService(); - - //act - var id = Guid.NewGuid().ToString(); - var now = DateTime.Now; - dbContext.Employees.Add(new Employee() - { - Id = id, - Name = "余小章", - Age = 18, - CreateAt = now, - CreateBy = "test user" - }); - dbContext.Identities.Add(new Identity() - { - Employee_Id = id, - Account = "yao", - Password = "123456", - CreateAt = now, - CreateBy = "test user" - }); - var count = await dbContext.SaveChangesAsync(); - - // var employeesTask = dbContext.Employees.OrderBy(p=>p.Id).AsNoTracking().LastAsync(); - var employeesTask = dbContext.Employees.AsNoTracking().LastAsync(); - var identitiesTask = dbContext.Identities.OrderBy(p => p.Employee_Id).AsNoTracking().LastAsync(); - await Task.WhenAll(employeesTask, identitiesTask); - } - [TestMethod] public void 操作真實資料庫_由Host註冊EmployeeDbContext() { diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj index aebb5b9b..29435055 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL.TestProject/Lab.DAL.TestProject.csproj @@ -7,20 +7,17 @@ - - - - - - - - - - + + + + + + + - + From d0b2392874b880610822995274cdd4ffb5df8181 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 30 Apr 2021 19:03:10 +0800 Subject: [PATCH 089/424] remove reference package --- ORM/EFCore/Lab.SQLite/Lab.DAL/Lab.DAL.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/ORM/EFCore/Lab.SQLite/Lab.DAL/Lab.DAL.csproj b/ORM/EFCore/Lab.SQLite/Lab.DAL/Lab.DAL.csproj index 2ad7fe1f..e7762bff 100644 --- a/ORM/EFCore/Lab.SQLite/Lab.DAL/Lab.DAL.csproj +++ b/ORM/EFCore/Lab.SQLite/Lab.DAL/Lab.DAL.csproj @@ -10,7 +10,6 @@ - From 74e63a0bb02fce6dbe0503889ef1337ac01991b6 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Mon, 10 May 2021 14:49:32 +0800 Subject: [PATCH 090/424] =?UTF-8?q?fix:=20DefaultDependencyResolver.Dispos?= =?UTF-8?q?e()=20=E4=B8=8D=E6=9C=83=E8=AA=BF=E7=94=A8=E7=89=A9=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=20Dispose()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../App_Start/DefaultDependencyResolver.cs | 19 ++++++++++--------- DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs | 6 ++++-- .../WebApiNet48/Message/LogMessager.cs | 5 +++++ .../WebApiNet48/Message/MachineMessager.cs | 5 +++++ .../WebApiNet48/Message/MultiMessager.cs | 5 +++++ 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/DI/Lab.MsDI/WebApiNet48/App_Start/DefaultDependencyResolver.cs b/DI/Lab.MsDI/WebApiNet48/App_Start/DefaultDependencyResolver.cs index 6dc35633..e9e64035 100644 --- a/DI/Lab.MsDI/WebApiNet48/App_Start/DefaultDependencyResolver.cs +++ b/DI/Lab.MsDI/WebApiNet48/App_Start/DefaultDependencyResolver.cs @@ -7,33 +7,34 @@ namespace WebApiNet48 { public class DefaultDependencyResolver : IDependencyResolver { - protected IServiceProvider ServiceProvider { get; set; } + private readonly IServiceProvider _serviceProvider; + private IServiceScope _serviceScope; - public DefaultDependencyResolver(IServiceProvider serviceProvider) + public DefaultDependencyResolver(IServiceProvider serviceProvider, IServiceScope serviceScope = null) { - this.ServiceProvider = serviceProvider; + this._serviceProvider = serviceProvider; + this._serviceScope = serviceScope; } public object GetService(Type serviceType) { - return this.ServiceProvider.GetService(serviceType); + return this._serviceProvider.GetService(serviceType); } public IEnumerable GetServices(Type serviceType) { - return this.ServiceProvider.GetServices(serviceType); + return this._serviceProvider.GetServices(serviceType); } public IDependencyScope BeginScope() { - return new DefaultDependencyResolver(this.ServiceProvider.CreateScope().ServiceProvider); + this._serviceScope = this._serviceProvider.CreateScope(); + return new DefaultDependencyResolver(this._serviceScope.ServiceProvider,this._serviceScope); } public void Dispose() { - // you can implement this interface just when you use .net core 2.0 - // this.ServiceProvider.Dispose(); - ((ServiceProvider) this.ServiceProvider).Dispose(); + this._serviceScope?.Dispose(); } } } \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs index 137ae92c..9883346c 100644 --- a/DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs +++ b/DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs @@ -1,6 +1,8 @@ -namespace WebApiNet48 +using System; + +namespace WebApiNet48 { - public interface IMessager + public interface IMessager:IDisposable { string OperationId { get; } } diff --git a/DI/Lab.MsDI/WebApiNet48/Message/LogMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/LogMessager.cs index 91e273eb..a73f42f4 100644 --- a/DI/Lab.MsDI/WebApiNet48/Message/LogMessager.cs +++ b/DI/Lab.MsDI/WebApiNet48/Message/LogMessager.cs @@ -5,5 +5,10 @@ namespace WebApiNet48 internal class LogMessager : IMessager { public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; + + public void Dispose() + { + Console.WriteLine($"{nameof(LogMessager)} GC"); + } } } \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/MachineMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/MachineMessager.cs index 39e04bc5..8aed0c19 100644 --- a/DI/Lab.MsDI/WebApiNet48/Message/MachineMessager.cs +++ b/DI/Lab.MsDI/WebApiNet48/Message/MachineMessager.cs @@ -5,5 +5,10 @@ namespace WebApiNet48 internal class MachineMessager : IMessager { public string OperationId { get; } = $"機器-{Guid.NewGuid()}"; + + public void Dispose() + { + Console.WriteLine($"{nameof(MachineMessager)} GC"); + } } } \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/MultiMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/MultiMessager.cs index 17d01e6c..7e3edf7b 100644 --- a/DI/Lab.MsDI/WebApiNet48/Message/MultiMessager.cs +++ b/DI/Lab.MsDI/WebApiNet48/Message/MultiMessager.cs @@ -5,5 +5,10 @@ namespace WebApiNet48 public class MultiMessager : IScopeMessager, ISingleMessager, ITransientMessager { public string OperationId { get; } = $"多個接口-{Guid.NewGuid()}"; + + public void Dispose() + { + Console.WriteLine($"{nameof(MultiMessager)} GC"); + } } } \ No newline at end of file From 16c77af4233930c4c13fca06f70a475bf5142714 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Mon, 10 May 2021 15:07:02 +0800 Subject: [PATCH 091/424] =?UTF-8?q?refactor:=20=E8=A7=80=E5=AF=9F=20mvc5?= =?UTF-8?q?=20=E5=B0=88=E6=A1=88=E7=9A=84=20DI=20=E6=9C=89=E6=B2=92?= =?UTF-8?q?=E6=9C=89=E6=AD=A3=E5=B8=B8=E7=9A=84=E5=91=BC=E5=8F=AB=20IMessa?= =?UTF-8?q?ge.Dispose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Mvc5Net48/App_Start/DefaultDependencyResolver.cs | 1 - DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs | 6 ++++-- DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs | 5 +++++ DI/Lab.MsDI/Mvc5Net48/Message/MachineMessager.cs | 4 ++++ DI/Lab.MsDI/Mvc5Net48/Message/MultiMessager.cs | 4 ++++ DI/Lab.MsDI/Mvc5Net48/Web.config | 8 ++++++++ 6 files changed, 25 insertions(+), 3 deletions(-) diff --git a/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver.cs b/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver.cs index 18630899..ab391905 100644 --- a/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver.cs +++ b/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver.cs @@ -8,7 +8,6 @@ namespace Mvc5Net48 { internal class DefaultDependencyResolver : IDependencyResolver { - public object GetService(Type serviceType) { if (HttpContext.Current?.Items[typeof(IServiceScope)] is IServiceScope scope) diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs index 8f3da6d6..e3bda957 100644 --- a/DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs +++ b/DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs @@ -1,6 +1,8 @@ -namespace Mvc5Net48.Message +using System; + +namespace Mvc5Net48.Message { - public interface IMessager + public interface IMessager:IDisposable { string OperationId { get; } } diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs index 85dc6327..b40b1319 100644 --- a/DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs +++ b/DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs @@ -5,5 +5,10 @@ namespace Mvc5Net48.Message internal class LogMessager : IMessager { public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; + + public void Dispose() + { + Console.WriteLine($"{nameof(LogMessager)} GC"); + } } } \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/MachineMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/MachineMessager.cs index 84fa31b8..8d91ec1d 100644 --- a/DI/Lab.MsDI/Mvc5Net48/Message/MachineMessager.cs +++ b/DI/Lab.MsDI/Mvc5Net48/Message/MachineMessager.cs @@ -5,5 +5,9 @@ namespace Mvc5Net48.Message internal class MachineMessager : IMessager { public string OperationId { get; } = $"機器-{Guid.NewGuid()}"; + public void Dispose() + { + Console.WriteLine($"{nameof(MachineMessager)} GC"); + } } } \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/MultiMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/MultiMessager.cs index 1b155f0c..d2206ff8 100644 --- a/DI/Lab.MsDI/Mvc5Net48/Message/MultiMessager.cs +++ b/DI/Lab.MsDI/Mvc5Net48/Message/MultiMessager.cs @@ -5,5 +5,9 @@ namespace Mvc5Net48.Message public class MultiMessager : IScopeMessager, ISingleMessager, ITransientMessager { public string OperationId { get; } = $"多個接口-{Guid.NewGuid()}"; + public void Dispose() + { + Console.WriteLine($"{nameof(MultiMessager)} GC"); + } } } \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Web.config b/DI/Lab.MsDI/Mvc5Net48/Web.config index 417fbf99..58fb7220 100644 --- a/DI/Lab.MsDI/Mvc5Net48/Web.config +++ b/DI/Lab.MsDI/Mvc5Net48/Web.config @@ -28,6 +28,14 @@ + + + + + + + + From 596eb7e2651a810c0edb2e879fa5c91b81a04b43 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Mon, 10 May 2021 15:11:32 +0800 Subject: [PATCH 092/424] =?UTF-8?q?refactor:=20=E8=A7=80=E5=AF=9F=20asp.ne?= =?UTF-8?q?t=20core=20=E5=B0=88=E6=A1=88=E7=9A=84=20DI=20=E6=9C=89?= =?UTF-8?q?=E6=B2=92=E6=9C=89=E6=AD=A3=E5=B8=B8=E7=9A=84=E5=91=BC=E5=8F=AB?= =?UTF-8?q?=20IMessage.Dispose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DI/Lab.MsDI/WebApiNetCore31/Message/IMessager.cs | 6 ++++-- DI/Lab.MsDI/WebApiNetCore31/Message/LogMessager.cs | 5 +++++ DI/Lab.MsDI/WebApiNetCore31/Message/MachineMessager.cs | 4 ++++ DI/Lab.MsDI/WebApiNetCore31/Message/MultiMessager.cs | 5 +++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/IMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/IMessager.cs index 07685fa7..479144a9 100644 --- a/DI/Lab.MsDI/WebApiNetCore31/Message/IMessager.cs +++ b/DI/Lab.MsDI/WebApiNetCore31/Message/IMessager.cs @@ -1,6 +1,8 @@ -namespace WebApiNetCore31 +using System; + +namespace WebApiNetCore31 { - public interface IMessager + public interface IMessager:IDisposable { string OperationId { get; } } diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/LogMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/LogMessager.cs index 633adcba..9819ac66 100644 --- a/DI/Lab.MsDI/WebApiNetCore31/Message/LogMessager.cs +++ b/DI/Lab.MsDI/WebApiNetCore31/Message/LogMessager.cs @@ -5,5 +5,10 @@ namespace WebApiNetCore31 internal class LogMessager : IMessager { public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; + + public void Dispose() + { + Console.WriteLine($"{nameof(LogMessager)} GC"); + } } } \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/MachineMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/MachineMessager.cs index d3e4038f..ed49b5dd 100644 --- a/DI/Lab.MsDI/WebApiNetCore31/Message/MachineMessager.cs +++ b/DI/Lab.MsDI/WebApiNetCore31/Message/MachineMessager.cs @@ -5,5 +5,9 @@ namespace WebApiNetCore31 internal class MachineMessager : IMessager { public string OperationId { get; } = $"機器-{Guid.NewGuid()}"; + public void Dispose() + { + Console.WriteLine($"{nameof(MachineMessager)} GC"); + } } } \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/MultiMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/MultiMessager.cs index e4eb78a9..68182ed0 100644 --- a/DI/Lab.MsDI/WebApiNetCore31/Message/MultiMessager.cs +++ b/DI/Lab.MsDI/WebApiNetCore31/Message/MultiMessager.cs @@ -5,5 +5,10 @@ namespace WebApiNetCore31 public class MultiMessager : IScopeMessager, ISingleMessager, ITransientMessager { public string OperationId { get; } = $"多個接口-{Guid.NewGuid()}"; + + public void Dispose() + { + Console.WriteLine($"{nameof(MultiMessager)} GC"); + } } } \ No newline at end of file From 1d90cf803738d8d0f6c5ef07352c0e9362facf9d Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 20 May 2021 10:22:55 +0800 Subject: [PATCH 093/424] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=20func=20nam?= =?UTF-8?q?e=20=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DI/Lab.MultipleImpl/Client/Client.csproj | 21 +++++++ DI/Lab.MultipleImpl/Client/UnitTest1.cs | 56 +++++++++++++++++++ DI/Lab.MultipleImpl/Lab.MultipleImpl.sln | 22 ++++++++ .../Server/Controllers/DefaultController.cs | 31 ++++++++++ .../Controllers/WeatherForecastController.cs | 39 +++++++++++++ .../Server/File/FileProvider.cs | 14 +++++ .../Server/File/IFileProvider.cs | 7 +++ .../Server/File/ZipFileProvider.cs | 14 +++++ DI/Lab.MultipleImpl/Server/Program.cs | 23 ++++++++ .../Server/Properties/launchSettings.json | 31 ++++++++++ DI/Lab.MultipleImpl/Server/Server.csproj | 19 +++++++ .../Server/ServiceProviderExtension.cs | 14 +++++ DI/Lab.MultipleImpl/Server/Startup.cs | 52 +++++++++++++++++ DI/Lab.MultipleImpl/Server/WeatherForecast.cs | 15 +++++ .../Server/appsettings.Development.json | 9 +++ DI/Lab.MultipleImpl/Server/appsettings.json | 10 ++++ 16 files changed, 377 insertions(+) create mode 100644 DI/Lab.MultipleImpl/Client/Client.csproj create mode 100644 DI/Lab.MultipleImpl/Client/UnitTest1.cs create mode 100644 DI/Lab.MultipleImpl/Lab.MultipleImpl.sln create mode 100644 DI/Lab.MultipleImpl/Server/Controllers/DefaultController.cs create mode 100644 DI/Lab.MultipleImpl/Server/Controllers/WeatherForecastController.cs create mode 100644 DI/Lab.MultipleImpl/Server/File/FileProvider.cs create mode 100644 DI/Lab.MultipleImpl/Server/File/IFileProvider.cs create mode 100644 DI/Lab.MultipleImpl/Server/File/ZipFileProvider.cs create mode 100644 DI/Lab.MultipleImpl/Server/Program.cs create mode 100644 DI/Lab.MultipleImpl/Server/Properties/launchSettings.json create mode 100644 DI/Lab.MultipleImpl/Server/Server.csproj create mode 100644 DI/Lab.MultipleImpl/Server/ServiceProviderExtension.cs create mode 100644 DI/Lab.MultipleImpl/Server/Startup.cs create mode 100644 DI/Lab.MultipleImpl/Server/WeatherForecast.cs create mode 100644 DI/Lab.MultipleImpl/Server/appsettings.Development.json create mode 100644 DI/Lab.MultipleImpl/Server/appsettings.json diff --git a/DI/Lab.MultipleImpl/Client/Client.csproj b/DI/Lab.MultipleImpl/Client/Client.csproj new file mode 100644 index 00000000..5dfa1709 --- /dev/null +++ b/DI/Lab.MultipleImpl/Client/Client.csproj @@ -0,0 +1,21 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + + diff --git a/DI/Lab.MultipleImpl/Client/UnitTest1.cs b/DI/Lab.MultipleImpl/Client/UnitTest1.cs new file mode 100644 index 00000000..ba1277ba --- /dev/null +++ b/DI/Lab.MultipleImpl/Client/UnitTest1.cs @@ -0,0 +1,56 @@ +using System; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Server; + +namespace Client +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + using var server = + new TestServer(WebHost.CreateDefaultBuilder() + .UseStartup() + .ConfigureServices(UseFuncName) + ) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "default/zip"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider",response); + } + + private static void UseFuncName(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton>(provider => + key => + { + switch (key) + { + case "zip": + return provider + .GetService(); + case "file": + return provider + .GetService(); + default: + throw new NotSupportedException(); + } + }); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Lab.MultipleImpl.sln b/DI/Lab.MultipleImpl/Lab.MultipleImpl.sln new file mode 100644 index 00000000..748e5666 --- /dev/null +++ b/DI/Lab.MultipleImpl/Lab.MultipleImpl.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{DAE7F74D-E847-4B2F-8930-59AF2698FD1D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Client\Client.csproj", "{6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DAE7F74D-E847-4B2F-8930-59AF2698FD1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DAE7F74D-E847-4B2F-8930-59AF2698FD1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DAE7F74D-E847-4B2F-8930-59AF2698FD1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DAE7F74D-E847-4B2F-8930-59AF2698FD1D}.Release|Any CPU.Build.0 = Release|Any CPU + {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/DI/Lab.MultipleImpl/Server/Controllers/DefaultController.cs b/DI/Lab.MultipleImpl/Server/Controllers/DefaultController.cs new file mode 100644 index 00000000..79efc330 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Controllers/DefaultController.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Server.Controllers +{ + [ApiController] + [Route("[controller]")] + public class DefaultController : ControllerBase + { + + private readonly ILogger _logger; + + public DefaultController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + [Route("{type}")] + public IActionResult Get(string type) + { + var fileProvider = this.HttpContext.RequestServices.GetService(type); + var result = fileProvider.Print(); + return this.Ok(result); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Controllers/WeatherForecastController.cs b/DI/Lab.MultipleImpl/Server/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..69c4d675 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Controllers/WeatherForecastController.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Server.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public IEnumerable Get() + { + var rng = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = rng.Next(-20, 55), + Summary = Summaries[rng.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/File/FileProvider.cs b/DI/Lab.MultipleImpl/Server/File/FileProvider.cs new file mode 100644 index 00000000..06045138 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/File/FileProvider.cs @@ -0,0 +1,14 @@ +using System; + +namespace Server +{ + public class FileProvider : IFileProvider + { + public string Print() + { + var msg = "FileProvider"; + Console.WriteLine(msg); + return msg; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/File/IFileProvider.cs b/DI/Lab.MultipleImpl/Server/File/IFileProvider.cs new file mode 100644 index 00000000..7c69a1ae --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/File/IFileProvider.cs @@ -0,0 +1,7 @@ +namespace Server +{ + public interface IFileProvider + { + string Print(); + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/File/ZipFileProvider.cs b/DI/Lab.MultipleImpl/Server/File/ZipFileProvider.cs new file mode 100644 index 00000000..804a286a --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/File/ZipFileProvider.cs @@ -0,0 +1,14 @@ +using System; + +namespace Server +{ + public class ZipFileProvider : IFileProvider + { + public string Print() + { + var msg = "ZipFileProvider"; + Console.WriteLine(msg); + return msg; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Program.cs b/DI/Lab.MultipleImpl/Server/Program.cs new file mode 100644 index 00000000..a8f15ae6 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Program.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Server +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Properties/launchSettings.json b/DI/Lab.MultipleImpl/Server/Properties/launchSettings.json new file mode 100644 index 00000000..f822a28b --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:59369", + "sslPort": 44389 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Server": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/DI/Lab.MultipleImpl/Server/Server.csproj b/DI/Lab.MultipleImpl/Server/Server.csproj new file mode 100644 index 00000000..743b9e0a --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Server.csproj @@ -0,0 +1,19 @@ + + + + net5.0 + + + + + + + + + + + + + + + diff --git a/DI/Lab.MultipleImpl/Server/ServiceProviderExtension.cs b/DI/Lab.MultipleImpl/Server/ServiceProviderExtension.cs new file mode 100644 index 00000000..601138e2 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/ServiceProviderExtension.cs @@ -0,0 +1,14 @@ +using System; + +namespace Server +{ + public static class ServiceProviderExtension + { + public static T GetService(this IServiceProvider provider, string name) + { + var pool = (Func) provider.GetService(typeof(Func)); + return (T) pool(name); + } + } + +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Startup.cs b/DI/Lab.MultipleImpl/Server/Startup.cs new file mode 100644 index 00000000..e7ecb450 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Startup.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; + +namespace Server +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Server", Version = "v1"}); }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server v1")); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/WeatherForecast.cs b/DI/Lab.MultipleImpl/Server/WeatherForecast.cs new file mode 100644 index 00000000..36e011e2 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/WeatherForecast.cs @@ -0,0 +1,15 @@ +using System; + +namespace Server +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int) (TemperatureC / 0.5556); + + public string Summary { get; set; } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/appsettings.Development.json b/DI/Lab.MultipleImpl/Server/appsettings.Development.json new file mode 100644 index 00000000..8983e0fc --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/DI/Lab.MultipleImpl/Server/appsettings.json b/DI/Lab.MultipleImpl/Server/appsettings.json new file mode 100644 index 00000000..d9d9a9bf --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} From 76a30ca8b7aca2f681da17fc032de4efb84fbc3a Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 20 May 2021 10:24:43 +0800 Subject: [PATCH 094/424] refactor: test case name --- DI/Lab.MultipleImpl/Client/UnitTest1.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DI/Lab.MultipleImpl/Client/UnitTest1.cs b/DI/Lab.MultipleImpl/Client/UnitTest1.cs index ba1277ba..63adb3e1 100644 --- a/DI/Lab.MultipleImpl/Client/UnitTest1.cs +++ b/DI/Lab.MultipleImpl/Client/UnitTest1.cs @@ -12,7 +12,7 @@ namespace Client public class UnitTest1 { [TestMethod] - public void TestMethod1() + public void 注入FuncName() { using var server = new TestServer(WebHost.CreateDefaultBuilder() @@ -29,7 +29,7 @@ public void TestMethod1() response.EnsureSuccessStatusCode(); var result = response.Content.ReadAsStringAsync().Result; - Assert.AreEqual("ZipFileProvider",response); + Assert.AreEqual("ZipFileProvider",result); } private static void UseFuncName(IServiceCollection services) From 69c21ed4992ecf5f7ee8319f3ee29b6c3dcf1cdb Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 20 May 2021 11:34:13 +0800 Subject: [PATCH 095/424] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20Unity=20?= =?UTF-8?q?=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DI/Lab.MultipleImpl/Client/Client.csproj | 1 + DI/Lab.MultipleImpl/Client/UnitTest1.cs | 41 ++++++++++++++++++- .../Controllers/UnityDefaultController.cs | 39 ++++++++++++++++++ DI/Lab.MultipleImpl/Server/Server.csproj | 1 + 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 DI/Lab.MultipleImpl/Server/Controllers/UnityDefaultController.cs diff --git a/DI/Lab.MultipleImpl/Client/Client.csproj b/DI/Lab.MultipleImpl/Client/Client.csproj index 5dfa1709..e7dd30b6 100644 --- a/DI/Lab.MultipleImpl/Client/Client.csproj +++ b/DI/Lab.MultipleImpl/Client/Client.csproj @@ -12,6 +12,7 @@ + diff --git a/DI/Lab.MultipleImpl/Client/UnitTest1.cs b/DI/Lab.MultipleImpl/Client/UnitTest1.cs index 63adb3e1..fee0b493 100644 --- a/DI/Lab.MultipleImpl/Client/UnitTest1.cs +++ b/DI/Lab.MultipleImpl/Client/UnitTest1.cs @@ -5,12 +5,39 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; using Server; +using Unity; +using Unity.Microsoft.DependencyInjection; namespace Client { [TestClass] public class UnitTest1 { + [TestMethod] + public void Unity注入ServiceName() + { + var unityContainer = new UnityContainer(); + ConfigureContainer(unityContainer); + + using var server = + new TestServer(WebHost.CreateDefaultBuilder() + .UseStartup() + .UseUnityServiceProvider(unityContainer) + .ConfigureServices(UseUnityController) + ) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "UnityDefault"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } + [TestMethod] public void 注入FuncName() { @@ -29,7 +56,13 @@ public void 注入FuncName() response.EnsureSuccessStatusCode(); var result = response.Content.ReadAsStringAsync().Result; - Assert.AreEqual("ZipFileProvider",result); + Assert.AreEqual("ZipFileProvider", result); + } + + private static void ConfigureContainer(IUnityContainer container) + { + container.RegisterType("zip"); + container.RegisterType("file"); } private static void UseFuncName(IServiceCollection services) @@ -52,5 +85,11 @@ private static void UseFuncName(IServiceCollection services) } }); } + + private static void UseUnityController(IServiceCollection services) + { + services.AddControllers() + .AddControllersAsServices(); + } } } \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Controllers/UnityDefaultController.cs b/DI/Lab.MultipleImpl/Server/Controllers/UnityDefaultController.cs new file mode 100644 index 00000000..2e437bea --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Controllers/UnityDefaultController.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Unity; + +namespace Server.Controllers +{ + [ApiController] + [Route("[controller]")] + public class UnityDefaultController : ControllerBase + { + private readonly IFileProvider _fileProvider; + + private readonly ILogger _logger; + + // public UnityDefaultController(ILogger logger, + // [Dependency("zip")] IFileProvider fileProvider) + // { + // this._logger = logger; + // this._fileProvider = fileProvider; + // } + public UnityDefaultController(ILogger logger) + { + this._logger = logger; + + // this._fileProvider = fileProvider; + } + + [HttpGet] + public IActionResult Get() + { + var serviceProvider = this.HttpContext.RequestServices; + var unityServiceProvider = (Unity.Microsoft.DependencyInjection.ServiceProvider) serviceProvider; + var unityContainer = (UnityContainer) unityServiceProvider; + var fileProvider = unityContainer.Resolve("zip"); + var result = fileProvider.Print(); + return this.Ok(result); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Server.csproj b/DI/Lab.MultipleImpl/Server/Server.csproj index 743b9e0a..d456ed92 100644 --- a/DI/Lab.MultipleImpl/Server/Server.csproj +++ b/DI/Lab.MultipleImpl/Server/Server.csproj @@ -6,6 +6,7 @@ + From 31cf8cef69bc18bf6285938f8552ff54bf3794fc Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 21 May 2021 10:46:28 +0800 Subject: [PATCH 096/424] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20Autofac=20?= =?UTF-8?q?test=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DI/Lab.MultipleImpl/Client/Client.csproj | 1 + DI/Lab.MultipleImpl/Client/UnitTest1.cs | 33 ++++++++++ DI/Lab.MultipleImpl/Server/AutofacStartup.cs | 63 +++++++++++++++++++ .../Server/Controllers/AutofacController.cs | 35 +++++++++++ ...efaultController.cs => UnityController.cs} | 18 ++---- .../Server/DependencyConfig.cs | 35 +++++++++++ DI/Lab.MultipleImpl/Server/Program.cs | 2 + DI/Lab.MultipleImpl/Server/Server.csproj | 1 + 8 files changed, 176 insertions(+), 12 deletions(-) create mode 100644 DI/Lab.MultipleImpl/Server/AutofacStartup.cs create mode 100644 DI/Lab.MultipleImpl/Server/Controllers/AutofacController.cs rename DI/Lab.MultipleImpl/Server/Controllers/{UnityDefaultController.cs => UnityController.cs} (57%) create mode 100644 DI/Lab.MultipleImpl/Server/DependencyConfig.cs diff --git a/DI/Lab.MultipleImpl/Client/Client.csproj b/DI/Lab.MultipleImpl/Client/Client.csproj index e7dd30b6..e7e23018 100644 --- a/DI/Lab.MultipleImpl/Client/Client.csproj +++ b/DI/Lab.MultipleImpl/Client/Client.csproj @@ -7,6 +7,7 @@ + diff --git a/DI/Lab.MultipleImpl/Client/UnitTest1.cs b/DI/Lab.MultipleImpl/Client/UnitTest1.cs index fee0b493..20e25d88 100644 --- a/DI/Lab.MultipleImpl/Client/UnitTest1.cs +++ b/DI/Lab.MultipleImpl/Client/UnitTest1.cs @@ -1,10 +1,14 @@ using System; +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Autofac.Features.AttributeFilters; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; using Server; +using Server.Controllers; using Unity; using Unity.Microsoft.DependencyInjection; @@ -13,6 +17,28 @@ namespace Client [TestClass] public class UnitTest1 { + [TestMethod] + public void Autofac注入ServiceName() + { + var hostBuilder = WebHost.CreateDefaultBuilder() + // .UseServiceProviderFactory(new AutofacServiceProviderFactory()) + .ConfigureServices(services => { services.AddAutofac(); }) + .UseStartup() + ; + using var server = new TestServer(hostBuilder) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "autofac"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } + [TestMethod] public void Unity注入ServiceName() { @@ -59,6 +85,13 @@ public void 注入FuncName() Assert.AreEqual("ZipFileProvider", result); } + private static void ConfigureContainer(ContainerBuilder builder) + { + // builder.RegisterType().Keyed("file"); + // builder.RegisterType().Keyed("zip"); + // builder.RegisterType().WithAttributeFiltering(); + } + private static void ConfigureContainer(IUnityContainer container) { container.RegisterType("zip"); diff --git a/DI/Lab.MultipleImpl/Server/AutofacStartup.cs b/DI/Lab.MultipleImpl/Server/AutofacStartup.cs new file mode 100644 index 00000000..1e93de1e --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/AutofacStartup.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Autofac; +using Autofac.Features.AttributeFilters; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; +using Server.Controllers; + +namespace Server +{ + public class AutofacStartup + { + public AutofacStartup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + public void ConfigureContainer(ContainerBuilder builder) + { + builder.RegisterType().Keyed("file"); + builder.RegisterType().Keyed("zip"); + builder.RegisterType().WithAttributeFiltering(); + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers() + .AddControllersAsServices(); + services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Server", Version = "v1"}); }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server v1")); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Controllers/AutofacController.cs b/DI/Lab.MultipleImpl/Server/Controllers/AutofacController.cs new file mode 100644 index 00000000..45debe20 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Controllers/AutofacController.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations; +using Autofac.Features.AttributeFilters; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Server.Controllers +{ + [ApiController] + [Route("[controller]")] + public class AutofacController : ControllerBase + { + private readonly IFileProvider _fileProvider; + + private readonly ILogger _logger; + + // public AutofacDefaultController(ILogger logger) + // { + // this._logger = logger; + // } + + public AutofacController(ILogger logger, + [KeyFilter("zip")] IFileProvider fileProvider) + { + this._logger = logger; + this._fileProvider = fileProvider; + this._fileProvider.Print(); + } + + [HttpGet] + public IActionResult Get() + { + return this.Ok(this._fileProvider.Print()); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Controllers/UnityDefaultController.cs b/DI/Lab.MultipleImpl/Server/Controllers/UnityController.cs similarity index 57% rename from DI/Lab.MultipleImpl/Server/Controllers/UnityDefaultController.cs rename to DI/Lab.MultipleImpl/Server/Controllers/UnityController.cs index 2e437bea..44910a17 100644 --- a/DI/Lab.MultipleImpl/Server/Controllers/UnityDefaultController.cs +++ b/DI/Lab.MultipleImpl/Server/Controllers/UnityController.cs @@ -6,23 +6,17 @@ namespace Server.Controllers { [ApiController] [Route("[controller]")] - public class UnityDefaultController : ControllerBase + public class UnityController : ControllerBase { private readonly IFileProvider _fileProvider; - private readonly ILogger _logger; + private readonly ILogger _logger; - // public UnityDefaultController(ILogger logger, - // [Dependency("zip")] IFileProvider fileProvider) - // { - // this._logger = logger; - // this._fileProvider = fileProvider; - // } - public UnityDefaultController(ILogger logger) + public UnityController(ILogger logger, + [Dependency("zip")] IFileProvider fileProvider) { - this._logger = logger; - - // this._fileProvider = fileProvider; + this._logger = logger; + this._fileProvider = fileProvider; } [HttpGet] diff --git a/DI/Lab.MultipleImpl/Server/DependencyConfig.cs b/DI/Lab.MultipleImpl/Server/DependencyConfig.cs new file mode 100644 index 00000000..b6df11a4 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/DependencyConfig.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.OpenApi.Models; + +namespace Server +{ + public class DependencyConfig + { + public static void ConfigureServices(IServiceCollection services) + { + services.AddControllers() + .AddControllersAsServices() + ; + } + public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server v1")); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Program.cs b/DI/Lab.MultipleImpl/Server/Program.cs index a8f15ae6..c083a4aa 100644 --- a/DI/Lab.MultipleImpl/Server/Program.cs +++ b/DI/Lab.MultipleImpl/Server/Program.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; @@ -18,6 +19,7 @@ public static void Main(string[] args) public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) + .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); } } \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Server.csproj b/DI/Lab.MultipleImpl/Server/Server.csproj index d456ed92..153ed02c 100644 --- a/DI/Lab.MultipleImpl/Server/Server.csproj +++ b/DI/Lab.MultipleImpl/Server/Server.csproj @@ -5,6 +5,7 @@ + From b69506035e40f5b88c60ec16cddc02abbeb12c60 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 21 May 2021 10:59:48 +0800 Subject: [PATCH 097/424] fix --- DI/Lab.MultipleImpl/Client/UnitTest1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DI/Lab.MultipleImpl/Client/UnitTest1.cs b/DI/Lab.MultipleImpl/Client/UnitTest1.cs index 20e25d88..0dcc77b8 100644 --- a/DI/Lab.MultipleImpl/Client/UnitTest1.cs +++ b/DI/Lab.MultipleImpl/Client/UnitTest1.cs @@ -56,7 +56,7 @@ public void Unity注入ServiceName() }; var client = server.CreateClient(); - var url = "UnityDefault"; + var url = "unity"; var response = client.GetAsync(url).Result; response.EnsureSuccessStatusCode(); From 3735188b5bcfa09d2518c055822e1f52d442891c Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 21 May 2021 11:17:32 +0800 Subject: [PATCH 098/424] add NET5.TestProject.csproj --- DI/Lab.MultipleImpl/Lab.MultipleImpl.sln | 6 ++ .../NET5.TestProject/AutofacStartup.cs | 53 ++++++++++ .../Controllers/AutofacController.cs | 35 +++++++ .../Controllers/DefaultController.cs | 27 +++++ .../Controllers/UnityController.cs | 34 +++++++ .../Controllers/WeatherForecastController.cs | 38 +++++++ .../NET5.TestProject/File/FileProvider.cs | 14 +++ .../NET5.TestProject/File/IFileProvider.cs | 7 ++ .../NET5.TestProject/File/ZipFileProvider.cs | 14 +++ .../NET5.TestProject/FuncStartup.cs | 63 ++++++++++++ .../NET5.TestProject/NET5.TestProject.csproj | 27 +++++ .../ServiceProviderExtension.cs | 15 +++ .../NET5.TestProject/Startup.cs | 41 ++++++++ .../NET5.TestProject/UnitTest1.cs | 99 +++++++++++++++++++ .../NET5.TestProject/WeatherForecast.cs | 15 +++ .../NET5.TestProject/appsettings.json | 10 ++ 16 files changed, 498 insertions(+) create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/WeatherForecastController.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/File/FileProvider.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/File/IFileProvider.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/File/ZipFileProvider.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/FuncStartup.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Startup.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/WeatherForecast.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/appsettings.json diff --git a/DI/Lab.MultipleImpl/Lab.MultipleImpl.sln b/DI/Lab.MultipleImpl/Lab.MultipleImpl.sln index 748e5666..8a158dd3 100644 --- a/DI/Lab.MultipleImpl/Lab.MultipleImpl.sln +++ b/DI/Lab.MultipleImpl/Lab.MultipleImpl.sln @@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Client\Client.csproj", "{6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NET5.TestProject", "NET5.TestProject\NET5.TestProject.csproj", "{A433C8F8-3B75-412E-955F-287639C55C5F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Debug|Any CPU.Build.0 = Debug|Any CPU {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Release|Any CPU.ActiveCfg = Release|Any CPU {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Release|Any CPU.Build.0 = Release|Any CPU + {A433C8F8-3B75-412E-955F-287639C55C5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A433C8F8-3B75-412E-955F-287639C55C5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A433C8F8-3B75-412E-955F-287639C55C5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A433C8F8-3B75-412E-955F-287639C55C5F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs b/DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs new file mode 100644 index 00000000..46dd9d76 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs @@ -0,0 +1,53 @@ +using Autofac; +using Autofac.Features.AttributeFilters; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using NET5.TestProject.Controllers; +using NET5.TestProject.File; + +namespace NET5.TestProject +{ + public class AutofacStartup + { + public AutofacStartup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + public void ConfigureContainer(ContainerBuilder builder) + { + builder.RegisterType().Keyed("file"); + builder.RegisterType().Keyed("zip"); + builder.RegisterType().WithAttributeFiltering(); + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers() + .AddControllersAsServices(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs new file mode 100644 index 00000000..29da9a47 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs @@ -0,0 +1,35 @@ +using Autofac.Features.AttributeFilters; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using NET5.TestProject.File; + +namespace NET5.TestProject.Controllers +{ + [ApiController] + [Route("[controller]")] + public class AutofacController : ControllerBase + { + private readonly IFileProvider _fileProvider; + + private readonly ILogger _logger; + + // public AutofacDefaultController(ILogger logger) + // { + // this._logger = logger; + // } + + public AutofacController(ILogger logger, + [KeyFilter("zip")] IFileProvider fileProvider) + { + this._logger = logger; + this._fileProvider = fileProvider; + this._fileProvider.Print(); + } + + [HttpGet] + public IActionResult Get() + { + return this.Ok(this._fileProvider.Print()); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs new file mode 100644 index 00000000..31f21928 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using NET5.TestProject.File; + +namespace NET5.TestProject.Controllers +{ + [ApiController] + [Route("[controller]")] + public class FuncController : ControllerBase + { + private readonly ILogger _logger; + + public FuncController(ILogger logger) + { + this._logger = logger; + } + + [HttpGet] + [Route("{type}")] + public IActionResult Get(string type) + { + var fileProvider = this.HttpContext.RequestServices.GetService(type); + var result = fileProvider.Print(); + return this.Ok(result); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs new file mode 100644 index 00000000..2a748fc6 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using NET5.TestProject.File; +using Unity; + +namespace NET5.TestProject.Controllers +{ + [ApiController] + [Route("[controller]")] + public class UnityController : ControllerBase + { + private readonly IFileProvider _fileProvider; + + private readonly ILogger _logger; + + public UnityController(ILogger logger, + [Dependency("zip")] IFileProvider fileProvider) + { + this._logger = logger; + this._fileProvider = fileProvider; + } + + [HttpGet] + public IActionResult Get() + { + var serviceProvider = this.HttpContext.RequestServices; + var unityServiceProvider = (Unity.Microsoft.DependencyInjection.ServiceProvider) serviceProvider; + var unityContainer = (UnityContainer) unityServiceProvider; + var fileProvider = unityContainer.Resolve("zip"); + var result = fileProvider.Print(); + return this.Ok(result); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/WeatherForecastController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..2dc89a7f --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/WeatherForecastController.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace NET5.TestProject.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + this._logger = logger; + } + + [HttpGet] + public IEnumerable Get() + { + var rng = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = rng.Next(-20, 55), + Summary = Summaries[rng.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/File/FileProvider.cs b/DI/Lab.MultipleImpl/NET5.TestProject/File/FileProvider.cs new file mode 100644 index 00000000..f9ea7bea --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/File/FileProvider.cs @@ -0,0 +1,14 @@ +using System; + +namespace NET5.TestProject.File +{ + public class FileProvider : IFileProvider + { + public string Print() + { + var msg = "FileProvider"; + Console.WriteLine(msg); + return msg; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/File/IFileProvider.cs b/DI/Lab.MultipleImpl/NET5.TestProject/File/IFileProvider.cs new file mode 100644 index 00000000..e72e465a --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/File/IFileProvider.cs @@ -0,0 +1,7 @@ +namespace NET5.TestProject.File +{ + public interface IFileProvider + { + string Print(); + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/File/ZipFileProvider.cs b/DI/Lab.MultipleImpl/NET5.TestProject/File/ZipFileProvider.cs new file mode 100644 index 00000000..058d383d --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/File/ZipFileProvider.cs @@ -0,0 +1,14 @@ +using System; + +namespace NET5.TestProject.File +{ + public class ZipFileProvider : IFileProvider + { + public string Print() + { + var msg = "ZipFileProvider"; + Console.WriteLine(msg); + return msg; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/FuncStartup.cs b/DI/Lab.MultipleImpl/NET5.TestProject/FuncStartup.cs new file mode 100644 index 00000000..5be65458 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/FuncStartup.cs @@ -0,0 +1,63 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using NET5.TestProject.File; + +namespace NET5.TestProject +{ + public class FuncStartup + { + public FuncStartup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + UseFuncName(services); + } + private static void UseFuncName(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton>(provider => + key => + { + switch (key) + { + case "zip": + return provider + .GetService(); + case "file": + return provider + .GetService(); + default: + throw new NotSupportedException(); + } + }); + } + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj b/DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj new file mode 100644 index 00000000..c363eb46 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj @@ -0,0 +1,27 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + + true + PreserveNewest + PreserveNewest + + + + diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs b/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs new file mode 100644 index 00000000..5384bc0f --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs @@ -0,0 +1,15 @@ +using System; +using NET5.TestProject.File; + +namespace NET5.TestProject +{ + public static class ServiceProviderExtension + { + public static T GetService(this IServiceProvider provider, string name) + { + var pool = (Func) provider.GetService(typeof(Func)); + return (T) pool(name); + } + } + +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Startup.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Startup.cs new file mode 100644 index 00000000..2bad24fc --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Startup.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace NET5.TestProject +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs new file mode 100644 index 00000000..8f09b0a1 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs @@ -0,0 +1,99 @@ +using System; +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NET5.TestProject.File; +using Unity; +using Unity.Microsoft.DependencyInjection; + +namespace NET5.TestProject +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void Autofac注入ServiceName() + { + var hostBuilder = WebHost.CreateDefaultBuilder() + // .UseServiceProviderFactory(new AutofacServiceProviderFactory()) + .ConfigureServices(services => { services.AddAutofac(); }) + .UseStartup() + ; + using var server = new TestServer(hostBuilder) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "autofac"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } + + [TestMethod] + public void Unity注入ServiceName() + { + var unityContainer = new UnityContainer(); + ConfigureContainer(unityContainer); + + using var server = + new TestServer(WebHost.CreateDefaultBuilder() + .UseStartup() + .UseUnityServiceProvider(unityContainer) + .ConfigureServices(UseUnityController) + ) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "unity"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } + + [TestMethod] + public void 注入FuncName() + { + using var server = + new TestServer(WebHost.CreateDefaultBuilder() + .UseStartup() + ) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "default/zip"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } + + private static void ConfigureContainer(IUnityContainer container) + { + container.RegisterType("zip"); + container.RegisterType("file"); + } + + + + private static void UseUnityController(IServiceCollection services) + { + services.AddControllers() + .AddControllersAsServices(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/WeatherForecast.cs b/DI/Lab.MultipleImpl/NET5.TestProject/WeatherForecast.cs new file mode 100644 index 00000000..1eb10ce9 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/WeatherForecast.cs @@ -0,0 +1,15 @@ +using System; + +namespace NET5.TestProject +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int) (this.TemperatureC / 0.5556); + + public string Summary { get; set; } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/appsettings.json b/DI/Lab.MultipleImpl/NET5.TestProject/appsettings.json new file mode 100644 index 00000000..d9d9a9bf --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} From d89c69b93b7ccfd410b7786c2adeea19ae79fc65 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 21 May 2021 12:22:43 +0800 Subject: [PATCH 099/424] refactor --- ...DefaultController.cs => FuncController.cs} | 0 .../NET5.TestProject/FileAdapter.cs | 19 +++++++++++++++++++ .../NET5.TestProject/NET5.TestProject.csproj | 1 + 3 files changed, 20 insertions(+) rename DI/Lab.MultipleImpl/NET5.TestProject/Controllers/{DefaultController.cs => FuncController.cs} (100%) create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/FileAdapter.cs diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/FuncController.cs similarity index 100% rename from DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs rename to DI/Lab.MultipleImpl/NET5.TestProject/Controllers/FuncController.cs diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/FileAdapter.cs b/DI/Lab.MultipleImpl/NET5.TestProject/FileAdapter.cs new file mode 100644 index 00000000..41034a00 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/FileAdapter.cs @@ -0,0 +1,19 @@ +using NET5.TestProject.File; + +namespace NET5.TestProject +{ + public class FileAdapter + { + private readonly IFileProvider _fileProvider; + + public FileAdapter(IFileProvider fileProvider) + { + this._fileProvider = fileProvider; + } + + public string Get() + { + return this._fileProvider.Print(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj b/DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj index c363eb46..1c284244 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj +++ b/DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj @@ -7,6 +7,7 @@ + From ff8e202538de87327086e6f99e9a09e6fcd24e8c Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 21 May 2021 13:42:05 +0800 Subject: [PATCH 100/424] new manual di register --- .../Controllers/DefaultController.cs | 30 ++++++++++++++ .../NET5.TestProject/UnitTest1.cs | 39 +++++++++++++++++-- 2 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs new file mode 100644 index 00000000..fad0093e --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using NET5.TestProject.File; + +namespace NET5.TestProject.Controllers +{ + [ApiController] + [Route("[controller]")] + public class DefaultController : ControllerBase + { + private readonly ILogger _logger; + private readonly IFileProvider _fileProvider; + + public DefaultController(ILogger logger, + IFileProvider fileProvider) + { + this._logger = logger; + this._fileProvider = fileProvider; + } + [HttpGet] + public IActionResult Get() + { + // var fileProvider = this.HttpContext.RequestServices.GetService(); + var fileProvider = this._fileProvider; + var result = fileProvider.Print(); + return this.Ok(result); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs index 8f09b0a1..39c66c8f 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs +++ b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs @@ -1,11 +1,12 @@ using System; -using Autofac; using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; +using NET5.TestProject.Controllers; using NET5.TestProject.File; using Unity; using Unity.Microsoft.DependencyInjection; @@ -19,6 +20,7 @@ public class UnitTest1 public void Autofac注入ServiceName() { var hostBuilder = WebHost.CreateDefaultBuilder() + // .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureServices(services => { services.AddAutofac(); }) .UseStartup() @@ -62,6 +64,39 @@ public void Unity注入ServiceName() Assert.AreEqual("ZipFileProvider", result); } + [TestMethod] + public void 手動註冊() + { + var hostBuilder = + WebHost.CreateDefaultBuilder() + .UseStartup() + .ConfigureServices(services => + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(p => + { + var fileProvider = p.GetService(); + var logger = p.GetService>(); + return new DefaultController(logger, fileProvider); + }); + services.AddControllers().AddControllersAsServices(); + }) + ; + using var server = new TestServer(hostBuilder) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "default"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } + [TestMethod] public void 注入FuncName() { @@ -88,8 +123,6 @@ private static void ConfigureContainer(IUnityContainer container) container.RegisterType("file"); } - - private static void UseUnityController(IServiceCollection services) { services.AddControllers() From 8c4950c02d3cd93cc213af415b5586caeb6f180c Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 21 May 2021 14:21:51 +0800 Subject: [PATCH 101/424] refactor --- .../NET5.TestProject/UnitTest1.cs | 100 ++++++++++-------- 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs index 39c66c8f..d116d7ce 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs +++ b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs @@ -22,8 +22,12 @@ public void Autofac注入ServiceName() var hostBuilder = WebHost.CreateDefaultBuilder() // .UseServiceProviderFactory(new AutofacServiceProviderFactory()) - .ConfigureServices(services => { services.AddAutofac(); }) .UseStartup() + .ConfigureServices(services => + { + services.AddAutofac(); + services.AddControllers().AddControllersAsServices(); + }) ; using var server = new TestServer(hostBuilder) { @@ -43,17 +47,22 @@ public void Autofac注入ServiceName() public void Unity注入ServiceName() { var unityContainer = new UnityContainer(); - ConfigureContainer(unityContainer); - - using var server = - new TestServer(WebHost.CreateDefaultBuilder() - .UseStartup() - .UseUnityServiceProvider(unityContainer) - .ConfigureServices(UseUnityController) - ) - { - BaseAddress = new Uri("http://localhost:9527") - }; + unityContainer.RegisterType("zip"); + unityContainer.RegisterType("file"); + + var builder = WebHost.CreateDefaultBuilder() + .UseStartup() + .UseUnityServiceProvider(unityContainer) + .ConfigureServices(s => + { + s.AddControllers() + .AddControllersAsServices(); + }) + ; + using var server = new TestServer(builder) + { + BaseAddress = new Uri("http://localhost:9527") + }; var client = server.CreateClient(); var url = "unity"; @@ -70,17 +79,18 @@ public void 手動註冊() var hostBuilder = WebHost.CreateDefaultBuilder() .UseStartup() - .ConfigureServices(services => + .ConfigureServices(s => { - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(p => - { - var fileProvider = p.GetService(); - var logger = p.GetService>(); - return new DefaultController(logger, fileProvider); - }); - services.AddControllers().AddControllersAsServices(); + s.AddSingleton(); + s.AddSingleton(); + s.AddSingleton(p => + { + var fileProvider = p.GetService(); + var logger = + p.GetService>(); + return new DefaultController(logger, fileProvider); + }); + s.AddControllers().AddControllersAsServices(); }) ; using var server = new TestServer(hostBuilder) @@ -100,33 +110,39 @@ public void 手動註冊() [TestMethod] public void 注入FuncName() { - using var server = - new TestServer(WebHost.CreateDefaultBuilder() - .UseStartup() - ) - { - BaseAddress = new Uri("http://localhost:9527") - }; + var builder = WebHost.CreateDefaultBuilder() + .UseStartup() + .ConfigureServices(s => + { + s.AddSingleton(); + s.AddSingleton(); + s.AddSingleton>(p => + key => + { + switch (key) + { + case "zip": + return p.GetService(); + case "file": + return p.GetService(); + default: + throw new NotSupportedException(); + } + }); + }) + ; + using var server = new TestServer(builder) + { + BaseAddress = new Uri("http://localhost:9527") + }; var client = server.CreateClient(); - var url = "default/zip"; + var url = "func/zip"; var response = client.GetAsync(url).Result; response.EnsureSuccessStatusCode(); var result = response.Content.ReadAsStringAsync().Result; Assert.AreEqual("ZipFileProvider", result); } - - private static void ConfigureContainer(IUnityContainer container) - { - container.RegisterType("zip"); - container.RegisterType("file"); - } - - private static void UseUnityController(IServiceCollection services) - { - services.AddControllers() - .AddControllersAsServices(); - } } } \ No newline at end of file From a9cb5c64cde226975bcaec1f5408500a01983ca0 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 21 May 2021 15:05:45 +0800 Subject: [PATCH 102/424] refactor --- .../NET5.TestProject/Controllers/FuncController.cs | 12 +++++++++--- .../NET5.TestProject/ServiceProviderExtension.cs | 1 - 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/FuncController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/FuncController.cs index 31f21928..43728f63 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/FuncController.cs +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/FuncController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using System; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using NET5.TestProject.File; @@ -8,11 +9,16 @@ namespace NET5.TestProject.Controllers [Route("[controller]")] public class FuncController : ControllerBase { + private readonly IFileProvider _fileProvider; private readonly ILogger _logger; - public FuncController(ILogger logger) + public FuncController(ILogger logger, + Func pool) { - this._logger = logger; + this._fileProvider = pool("zip"); + this._logger = logger; + var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; + Console.WriteLine(msg); } [HttpGet] diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs b/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs index 5384bc0f..6944fea0 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs +++ b/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs @@ -11,5 +11,4 @@ public static T GetService(this IServiceProvider provider, string name) return (T) pool(name); } } - } \ No newline at end of file From 3b090d0dbc4f3df80df5a6efe07a84db846f19ec Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 21 May 2021 15:22:49 +0800 Subject: [PATCH 103/424] refactor --- .../Controllers/UnityController.cs | 12 +++--- .../Controllers/WeatherForecastController.cs | 38 ------------------- .../NET5.TestProject/UnitTest1.cs | 2 +- .../NET5.TestProject/WeatherForecast.cs | 15 -------- 4 files changed, 8 insertions(+), 59 deletions(-) delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/WeatherForecastController.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/WeatherForecast.cs diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs index 2a748fc6..fcc4ccef 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using NET5.TestProject.File; using Unity; +using Unity.Microsoft.DependencyInjection; namespace NET5.TestProject.Controllers { @@ -13,20 +14,21 @@ public class UnityController : ControllerBase private readonly ILogger _logger; - public UnityController(ILogger logger, - [Dependency("zip")] IFileProvider fileProvider) + public UnityController(ILogger logger, + [Dependency("zip")] IFileProvider fileProvider) { this._logger = logger; this._fileProvider = fileProvider; } [HttpGet] - public IActionResult Get() + [Route("{key}")] + public IActionResult Get(string key) { var serviceProvider = this.HttpContext.RequestServices; - var unityServiceProvider = (Unity.Microsoft.DependencyInjection.ServiceProvider) serviceProvider; + var unityServiceProvider = (ServiceProvider) serviceProvider; var unityContainer = (UnityContainer) unityServiceProvider; - var fileProvider = unityContainer.Resolve("zip"); + var fileProvider = unityContainer.Resolve(key); var result = fileProvider.Print(); return this.Ok(result); } diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/WeatherForecastController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/WeatherForecastController.cs deleted file mode 100644 index 2dc89a7f..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace NET5.TestProject.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - this._logger = logger; - } - - [HttpGet] - public IEnumerable Get() - { - var rng = new Random(); - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateTime.Now.AddDays(index), - TemperatureC = rng.Next(-20, 55), - Summary = Summaries[rng.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs index d116d7ce..80f0060f 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs +++ b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs @@ -65,7 +65,7 @@ public void Unity注入ServiceName() }; var client = server.CreateClient(); - var url = "unity"; + var url = "unity/zip"; var response = client.GetAsync(url).Result; response.EnsureSuccessStatusCode(); diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/WeatherForecast.cs b/DI/Lab.MultipleImpl/NET5.TestProject/WeatherForecast.cs deleted file mode 100644 index 1eb10ce9..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/WeatherForecast.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace NET5.TestProject -{ - public class WeatherForecast - { - public DateTime Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int) (this.TemperatureC / 0.5556); - - public string Summary { get; set; } - } -} \ No newline at end of file From fe65354bc2ffe6b6df7928d40685b83e2b5ab5bf Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 21 May 2021 15:41:06 +0800 Subject: [PATCH 104/424] refactor --- .../Controllers/AutofacController.cs | 25 +++++++++++-------- .../Controllers/UnityController.cs | 5 +++- .../NET5.TestProject/UnitTest1.cs | 2 +- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs index 29da9a47..50bdf475 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs @@ -1,4 +1,7 @@ -using Autofac.Features.AttributeFilters; +using System; +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Autofac.Features.AttributeFilters; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using NET5.TestProject.File; @@ -13,23 +16,23 @@ public class AutofacController : ControllerBase private readonly ILogger _logger; - // public AutofacDefaultController(ILogger logger) - // { - // this._logger = logger; - // } - - public AutofacController(ILogger logger, - [KeyFilter("zip")] IFileProvider fileProvider) + public AutofacController(ILogger logger, + [KeyFilter("zip")] IFileProvider fileProvider) { this._logger = logger; this._fileProvider = fileProvider; - this._fileProvider.Print(); + var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; + Console.WriteLine(msg); } [HttpGet] - public IActionResult Get() + [Route("{key}")] + public IActionResult Get(string key) { - return this.Ok(this._fileProvider.Print()); + var serviceProvider = this.HttpContext.RequestServices; + var autofacServiceProvider = (AutofacServiceProvider) serviceProvider; + var fileProvider = autofacServiceProvider.LifetimeScope.ResolveKeyed(key); + return this.Ok(fileProvider.Print()); } } } \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs index fcc4ccef..c94576a0 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using System; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using NET5.TestProject.File; using Unity; @@ -19,6 +20,8 @@ public UnityController(ILogger logger, { this._logger = logger; this._fileProvider = fileProvider; + var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; + Console.WriteLine(msg); } [HttpGet] diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs index 80f0060f..21275860 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs +++ b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs @@ -35,7 +35,7 @@ public void Autofac注入ServiceName() }; var client = server.CreateClient(); - var url = "autofac"; + var url = "autofac/zip"; var response = client.GetAsync(url).Result; response.EnsureSuccessStatusCode(); From a8b2f4fe1025d58ffa5025a3154f2259c64afb5d Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 21 May 2021 15:55:58 +0800 Subject: [PATCH 105/424] refactor --- DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs | 2 +- DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs b/DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs index 46dd9d76..a1721c5a 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs +++ b/DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs @@ -23,7 +23,7 @@ public void ConfigureContainer(ContainerBuilder builder) { builder.RegisterType().Keyed("file"); builder.RegisterType().Keyed("zip"); - builder.RegisterType().WithAttributeFiltering(); + builder.RegisterType().WithAttributeFiltering();//<-- add line } // This method gets called by the runtime. Use this method to add services to the container. diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs index 21275860..775aa2e3 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs +++ b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs @@ -22,11 +22,11 @@ public void Autofac注入ServiceName() var hostBuilder = WebHost.CreateDefaultBuilder() // .UseServiceProviderFactory(new AutofacServiceProviderFactory()) - .UseStartup() + .UseStartup() //<-- add line .ConfigureServices(services => { services.AddAutofac(); - services.AddControllers().AddControllersAsServices(); + services.AddControllers().AddControllersAsServices();//<-- add line }) ; using var server = new TestServer(hostBuilder) @@ -48,15 +48,15 @@ public void Unity注入ServiceName() { var unityContainer = new UnityContainer(); unityContainer.RegisterType("zip"); - unityContainer.RegisterType("file"); + unityContainer.RegisterType("file"); //<-- add line var builder = WebHost.CreateDefaultBuilder() .UseStartup() - .UseUnityServiceProvider(unityContainer) + .UseUnityServiceProvider(unityContainer) //<-- add line .ConfigureServices(s => { s.AddControllers() - .AddControllersAsServices(); + .AddControllersAsServices(); //<-- add line }) ; using var server = new TestServer(builder) From ae91f87295669cf6aa1d7f9ef823f8a986a5be88 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 21 May 2021 22:39:53 +0800 Subject: [PATCH 106/424] refactor --- .../Controllers/MultiController.cs | 56 +++++++++++++++++++ .../NET5.TestProject/UnitTest1.cs | 45 ++++++++++++++- 2 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/MultiController.cs diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/MultiController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/MultiController.cs new file mode 100644 index 00000000..48942f3f --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/MultiController.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using NET5.TestProject.File; + +namespace NET5.TestProject.Controllers +{ + [ApiController] + [Route("[controller]")] + public class MultiController : ControllerBase + { + private readonly IFileProvider _fileProvider; + + private readonly ILogger _logger; + + // public MultiController(ILogger logger, + // IEnumerable pool) + // { + // this._logger = logger; + // this._fileProvider = pool.FirstOrDefault(p => p.GetType().Name == "ZipFileProvider"); + // var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; + // Console.WriteLine(msg); + // } + // + // [HttpGet] + // public IActionResult Get() + // { + // var serviceProvider = this.HttpContext.RequestServices; + // var pool = serviceProvider.GetServices(); + // var fileProvider = pool.FirstOrDefault(p => p.GetType().Name == "ZipFileProvider"); + // return this.Ok(fileProvider.Print()); + // } + + public MultiController(ILogger logger, + Dictionary pool) + { + this._logger = logger; + this._fileProvider = pool["zip"]; + var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; + Console.WriteLine(msg); + } + + [HttpGet] + [Route("{key}")] + public IActionResult Get(string key) + { + var serviceProvider = this.HttpContext.RequestServices; + var pool = serviceProvider.GetService>(); + var fileProvider = pool[key]; + return this.Ok(fileProvider.Print()); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs index 775aa2e3..37fc84ca 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs +++ b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; @@ -26,7 +27,8 @@ public void Autofac注入ServiceName() .ConfigureServices(services => { services.AddAutofac(); - services.AddControllers().AddControllersAsServices();//<-- add line + services.AddControllers() + .AddControllersAsServices(); //<-- add line }) ; using var server = new TestServer(hostBuilder) @@ -122,9 +124,11 @@ public void 注入FuncName() switch (key) { case "zip": - return p.GetService(); + return p + .GetService(); case "file": - return p.GetService(); + return p + .GetService(); default: throw new NotSupportedException(); } @@ -144,5 +148,40 @@ public void 注入FuncName() var result = response.Content.ReadAsStringAsync().Result; Assert.AreEqual("ZipFileProvider", result); } + + [TestMethod] + public void 注入相同的介面() + { + var hostBuilder = WebHost.CreateDefaultBuilder() + .UseStartup() //<-- add line + .ConfigureServices(s => + { + s.AddSingleton(); + s.AddSingleton(); + s.AddSingleton(p => + { + var pool = + new Dictionary + { + {"zip", p.GetService()}, + {"file", p.GetService()}}; + + return pool; + }); + }) + ; + using var server = new TestServer(hostBuilder) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "multi/zip"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } } } \ No newline at end of file From d1f9081e37980208d394fce096bcf9b76e47d7b8 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Fri, 21 May 2021 23:07:23 +0800 Subject: [PATCH 107/424] refactor --- .../ServiceProviderExtension.cs | 23 ++++++++ .../NET5.TestProject/UnitTest1.cs | 53 ++++++++++++++----- 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs b/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs index 6944fea0..2c381a2f 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs +++ b/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Reflection; using NET5.TestProject.File; namespace NET5.TestProject @@ -10,5 +12,26 @@ public static T GetService(this IServiceProvider provider, string name) var pool = (Func) provider.GetService(typeof(Func)); return (T) pool(name); } + + public static List GetTypesAssignableFrom(this Assembly assembly) + { + return assembly.GetTypesAssignableFrom(typeof(T)); + } + + public static List GetTypesAssignableFrom(this Assembly assembly, Type compareType) + { + var results = new List(); + foreach (var type in assembly.DefinedTypes) + { + if (compareType.IsAssignableFrom(type) + && compareType != type + ) + { + results.Add(type); + } + } + + return results; + } } } \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs index 37fc84ca..6641e17a 100644 --- a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs +++ b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reflection; using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; @@ -154,20 +155,11 @@ public void 注入相同的介面() { var hostBuilder = WebHost.CreateDefaultBuilder() .UseStartup() //<-- add line - .ConfigureServices(s => + .ConfigureServices(service => { - s.AddSingleton(); - s.AddSingleton(); - s.AddSingleton(p => - { - var pool = - new Dictionary - { - {"zip", p.GetService()}, - {"file", p.GetService()}}; - - return pool; - }); + ScanToDictionary(service); + + // AddToDictionary(service); }) ; using var server = new TestServer(hostBuilder) @@ -183,5 +175,40 @@ public void 注入相同的介面() var result = response.Content.ReadAsStringAsync().Result; Assert.AreEqual("ZipFileProvider", result); } + + private static void AddToDictionary(IServiceCollection s) + { + s.AddSingleton(); + s.AddSingleton(); + s.AddSingleton(p => + { + var pool = + new Dictionary + { + {"zip", p.GetService()}, + {"file", p.GetService()} + }; + + return pool; + }); + } + + private static void ScanToDictionary(IServiceCollection services) + { + var assembly = Assembly.GetExecutingAssembly(); + assembly.GetTypesAssignableFrom() + .ForEach(t => { services.AddSingleton(t); }); + services.AddSingleton(p => + { + var pool = + new Dictionary + { + {"zip", p.GetService()}, + {"file", p.GetService()} + }; + + return pool; + }); + } } } \ No newline at end of file From eb4d0226d14f4aff8f6de4768ba889be28fec6fd Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Jul 2021 11:32:09 +0800 Subject: [PATCH 108/424] add project --- .../Lab.LineBot.SDK/ILineNotifyProvider.cs | 21 + .../Internals/MimeTypeMapping.cs | 827 ++++++++++++++++++ .../Lab.LineBot.SDK/Internals/Validation.cs | 21 + .../Lab.LineBot.SDK/Lab.LineBot.SDK.csproj | 11 + .../Lab.LineBot.SDK/LineNotifyProvider.cs | 262 ++++++ .../LineNotifyProviderException.cs | 34 + .../Models/AuthorizeCodeUrlRequest.cs | 16 + .../Lab.LineBot.SDK/Models/GenericResponse.cs | 13 + .../Models/NotifyWithImageRequest.cs | 17 + .../Models/NotifyWithStickerRequest.cs | 18 + .../Models/TokenInfoResponse.cs | 26 + .../Lab.LineBot.SDK/Models/TokenRequest.cs | 19 + .../Lab.LineBot.SDK/Models/TokenResponse.cs | 10 + .../Controllers/AuthorizeController.cs | 61 ++ .../Lab.LineNotify.Service.csproj | 17 + .../Lab.LineNotify.Service/Program.cs | 19 + .../Properties/launchSettings.json | 31 + .../ReviceAuthorizeCodeRequest.cs | 20 + .../Lab.LineNotify.Service/Startup.cs | 52 ++ .../Lab.LineNotify.Service/WeatherForecast.cs | 15 + .../appsettings.Development.json | 9 + .../Lab.LineNotify.Service/appsettings.json | 16 + Line/Lab.LineNotify/Lab.LineNotify.sln | 22 + 23 files changed, 1557 insertions(+) create mode 100644 Line/Lab.LineNotify/Lab.LineBot.SDK/ILineNotifyProvider.cs create mode 100644 Line/Lab.LineNotify/Lab.LineBot.SDK/Internals/MimeTypeMapping.cs create mode 100644 Line/Lab.LineNotify/Lab.LineBot.SDK/Internals/Validation.cs create mode 100644 Line/Lab.LineNotify/Lab.LineBot.SDK/Lab.LineBot.SDK.csproj create mode 100644 Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs create mode 100644 Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProviderException.cs create mode 100644 Line/Lab.LineNotify/Lab.LineBot.SDK/Models/AuthorizeCodeUrlRequest.cs create mode 100644 Line/Lab.LineNotify/Lab.LineBot.SDK/Models/GenericResponse.cs create mode 100644 Line/Lab.LineNotify/Lab.LineBot.SDK/Models/NotifyWithImageRequest.cs create mode 100644 Line/Lab.LineNotify/Lab.LineBot.SDK/Models/NotifyWithStickerRequest.cs create mode 100644 Line/Lab.LineNotify/Lab.LineBot.SDK/Models/TokenInfoResponse.cs create mode 100644 Line/Lab.LineNotify/Lab.LineBot.SDK/Models/TokenRequest.cs create mode 100644 Line/Lab.LineNotify/Lab.LineBot.SDK/Models/TokenResponse.cs create mode 100644 Line/Lab.LineNotify/Lab.LineNotify.Service/Controllers/AuthorizeController.cs create mode 100644 Line/Lab.LineNotify/Lab.LineNotify.Service/Lab.LineNotify.Service.csproj create mode 100644 Line/Lab.LineNotify/Lab.LineNotify.Service/Program.cs create mode 100644 Line/Lab.LineNotify/Lab.LineNotify.Service/Properties/launchSettings.json create mode 100644 Line/Lab.LineNotify/Lab.LineNotify.Service/ServiceModels/ReviceAuthorizeCodeRequest.cs create mode 100644 Line/Lab.LineNotify/Lab.LineNotify.Service/Startup.cs create mode 100644 Line/Lab.LineNotify/Lab.LineNotify.Service/WeatherForecast.cs create mode 100644 Line/Lab.LineNotify/Lab.LineNotify.Service/appsettings.Development.json create mode 100644 Line/Lab.LineNotify/Lab.LineNotify.Service/appsettings.json create mode 100644 Line/Lab.LineNotify/Lab.LineNotify.sln diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/ILineNotifyProvider.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/ILineNotifyProvider.cs new file mode 100644 index 00000000..3cb72d37 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/ILineNotifyProvider.cs @@ -0,0 +1,21 @@ +using System.Threading; +using System.Threading.Tasks; +using Lab.LineBot.SDK.Models; + +namespace Lab.LineBot.SDK +{ + public interface ILineNotifyProvider + { + string CreateAuthorizeCodeUrl(AuthorizeCodeUrlRequest request); + + Task GetAccessTokenAsync(TokenRequest request, CancellationToken cancelToken); + + Task GetAccessTokenInfoAsync(string accessToken, CancellationToken cancelToken); + + Task NotifyAsync(NotifyWithStickerRequest request, CancellationToken cancelToken); + + Task NotifyAsync(NotifyWithImageRequest request, CancellationToken cancelToken); + + Task RevokeAsync(string accessToken, CancellationToken cancelToken); + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/Internals/MimeTypeMapping.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/Internals/MimeTypeMapping.cs new file mode 100644 index 00000000..f4dd853d --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/Internals/MimeTypeMapping.cs @@ -0,0 +1,827 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Lab.LineBot.SDK.Internals +{ + /// + /// Class MimeTypeMap. + /// + public static class MimeTypeMapping + { + private const string Dot = "."; + private const string QuestionMark = "?"; + private const string DefaultMimeType = "application/octet-stream"; + + private static readonly Lazy> _mappings = + new Lazy>(BuildMappings); + + /// + /// Gets the extension from the provided MINE type. + /// + /// Type of the MIME. + /// if set to true, throws error if extension's not found. + /// The extension. + /// + /// + public static string GetExtension(string mimeType, bool throwErrorIfNotFound = true) + { + if (mimeType == null) + { + throw new ArgumentNullException(nameof(mimeType)); + } + + if (mimeType.StartsWith(Dot)) + { + throw new ArgumentException("Requested mime type is not valid: " + mimeType); + } + + if (_mappings.Value.TryGetValue(mimeType, out var extension)) + { + return extension; + } + + if (throwErrorIfNotFound) + { + throw new ArgumentException("Requested mime type is not registered: " + mimeType); + } + + return string.Empty; + } + + /// + /// Gets the type of the MIME from the provided string. + /// + /// The filename or extension. + /// The MIME type. + /// + public static string GetMimeType(string str) + { + return TryGetMimeType(str, out var result) ? result : DefaultMimeType; + } + + /// + /// Tries to get the type of the MIME from the provided string. + /// + /// The filename or extension. + /// The variable to store the MIME type. + /// The MIME type. + /// + public static bool TryGetMimeType(string str, out string mimeType) + { + if (str == null) + { + throw new ArgumentNullException(nameof(str)); + } + + var indexQuestionMark = str.IndexOf(QuestionMark, StringComparison.Ordinal); + if (indexQuestionMark != -1) + { + str = str.Remove(indexQuestionMark); + } + + if (!str.StartsWith(Dot)) + { + var index = str.LastIndexOf(Dot); + if (index != -1 && str.Length > index + 1) + { + str = str.Substring(index + 1); + } + + str = Dot + str; + } + + return _mappings.Value.TryGetValue(str, out mimeType); + } + + private static IDictionary BuildMappings() + { + var mappings = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + #region Big freaking list of mime types + + // maps both ways, + // extension -> mime type + // and + // mime type -> extension + // + // any mime types on left side not pre-loaded on right side, are added automatically + // some mime types can map to multiple extensions, so to get a deterministic mapping, + // add those to the dictionary specifically + // + // combination of values from Windows 7 Registry and + // from C:\Windows\System32\inetsrv\config\applicationHost.config + // some added, including .7z and .dat + // + // Some added based on http://www.iana.org/assignments/media-types/media-types.xhtml + // which lists mime types, but not extensions + // + {".323", "text/h323"}, + {".3g2", "video/3gpp2"}, + {".3gp", "video/3gpp"}, + {".3gp2", "video/3gpp2"}, + {".3gpp", "video/3gpp"}, + {".7z", "application/x-7z-compressed"}, + {".aa", "audio/audible"}, + {".AAC", "audio/aac"}, + {".aaf", "application/octet-stream"}, + {".aax", "audio/vnd.audible.aax"}, + {".ac3", "audio/ac3"}, + {".aca", "application/octet-stream"}, + {".accda", "application/msaccess.addin"}, + {".accdb", "application/msaccess"}, + {".accdc", "application/msaccess.cab"}, + {".accde", "application/msaccess"}, + {".accdr", "application/msaccess.runtime"}, + {".accdt", "application/msaccess"}, + {".accdw", "application/msaccess.webapplication"}, + {".accft", "application/msaccess.ftemplate"}, + {".acx", "application/internet-property-stream"}, + {".AddIn", "text/xml"}, + {".ade", "application/msaccess"}, + {".adobebridge", "application/x-bridge-url"}, + {".adp", "application/msaccess"}, + {".ADT", "audio/vnd.dlna.adts"}, + {".ADTS", "audio/aac"}, + {".afm", "application/octet-stream"}, + {".ai", "application/postscript"}, + {".aif", "audio/aiff"}, + {".aifc", "audio/aiff"}, + {".aiff", "audio/aiff"}, + {".air", "application/vnd.adobe.air-application-installer-package+zip"}, + {".amc", "application/mpeg"}, + {".anx", "application/annodex"}, + {".apk", "application/vnd.android.package-archive"}, + {".apng", "image/apng"}, + {".application", "application/x-ms-application"}, + {".art", "image/x-jg"}, + {".asa", "application/xml"}, + {".asax", "application/xml"}, + {".ascx", "application/xml"}, + {".asd", "application/octet-stream"}, + {".asf", "video/x-ms-asf"}, + {".ashx", "application/xml"}, + {".asi", "application/octet-stream"}, + {".asm", "text/plain"}, + {".asmx", "application/xml"}, + {".aspx", "application/xml"}, + {".asr", "video/x-ms-asf"}, + {".asx", "video/x-ms-asf"}, + {".atom", "application/atom+xml"}, + {".au", "audio/basic"}, + {".avci", "image/avci"}, + {".avcs", "image/avcs"}, + {".avi", "video/x-msvideo"}, + {".avif", "image/avif"}, + {".avifs", "image/avif-sequence"}, + {".axa", "audio/annodex"}, + {".axs", "application/olescript"}, + {".axv", "video/annodex"}, + {".bas", "text/plain"}, + {".bcpio", "application/x-bcpio"}, + {".bin", "application/octet-stream"}, + {".bmp", "image/bmp"}, + {".c", "text/plain"}, + {".cab", "application/octet-stream"}, + {".caf", "audio/x-caf"}, + {".calx", "application/vnd.ms-office.calx"}, + {".cat", "application/vnd.ms-pki.seccat"}, + {".cc", "text/plain"}, + {".cd", "text/plain"}, + {".cdda", "audio/aiff"}, + {".cdf", "application/x-cdf"}, + {".cer", "application/x-x509-ca-cert"}, + {".cfg", "text/plain"}, + {".chm", "application/octet-stream"}, + {".class", "application/x-java-applet"}, + {".clp", "application/x-msclip"}, + {".cmd", "text/plain"}, + {".cmx", "image/x-cmx"}, + {".cnf", "text/plain"}, + {".cod", "image/cis-cod"}, + {".config", "application/xml"}, + {".contact", "text/x-ms-contact"}, + {".coverage", "application/xml"}, + {".cpio", "application/x-cpio"}, + {".cpp", "text/plain"}, + {".crd", "application/x-mscardfile"}, + {".crl", "application/pkix-crl"}, + {".crt", "application/x-x509-ca-cert"}, + {".cs", "text/plain"}, + {".csdproj", "text/plain"}, + {".csh", "application/x-csh"}, + {".csproj", "text/plain"}, + {".css", "text/css"}, + {".csv", "text/csv"}, + {".cur", "application/octet-stream"}, + {".czx", "application/x-czx"}, + {".cxx", "text/plain"}, + {".dat", "application/octet-stream"}, + {".datasource", "application/xml"}, + {".dbproj", "text/plain"}, + {".dcr", "application/x-director"}, + {".def", "text/plain"}, + {".deploy", "application/octet-stream"}, + {".der", "application/x-x509-ca-cert"}, + {".dgml", "application/xml"}, + {".dib", "image/bmp"}, + {".dif", "video/x-dv"}, + {".dir", "application/x-director"}, + {".disco", "text/xml"}, + {".divx", "video/divx"}, + {".dll", "application/x-msdownload"}, + {".dll.config", "text/xml"}, + {".dlm", "text/dlm"}, + {".doc", "application/msword"}, + {".docm", "application/vnd.ms-word.document.macroEnabled.12"}, + {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + {".dot", "application/msword"}, + {".dotm", "application/vnd.ms-word.template.macroEnabled.12"}, + {".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"}, + {".dsp", "application/octet-stream"}, + {".dsw", "text/plain"}, + {".dtd", "text/xml"}, + {".dtsConfig", "text/xml"}, + {".dv", "video/x-dv"}, + {".dvi", "application/x-dvi"}, + {".dwf", "drawing/x-dwf"}, + {".dwg", "application/acad"}, + {".dwp", "application/octet-stream"}, + {".dxf", "application/x-dxf"}, + {".dxr", "application/x-director"}, + {".eml", "message/rfc822"}, + {".emf", "image/emf"}, + {".emz", "application/octet-stream"}, + {".eot", "application/vnd.ms-fontobject"}, + {".eps", "application/postscript"}, + {".es", "application/ecmascript"}, + {".etl", "application/etl"}, + {".etx", "text/x-setext"}, + {".evy", "application/envoy"}, + {".exe", "application/vnd.microsoft.portable-executable"}, + {".exe.config", "text/xml"}, + {".f4v", "video/mp4"}, + {".fdf", "application/vnd.fdf"}, + {".fif", "application/fractals"}, + {".filters", "application/xml"}, + {".fla", "application/octet-stream"}, + {".flac", "audio/flac"}, + {".flr", "x-world/x-vrml"}, + {".flv", "video/x-flv"}, + {".fsscript", "application/fsharp-script"}, + {".fsx", "application/fsharp-script"}, + {".generictest", "application/xml"}, + {".geojson", "application/geo+json"}, + {".gif", "image/gif"}, + {".gpx", "application/gpx+xml"}, + {".group", "text/x-ms-group"}, + {".gsm", "audio/x-gsm"}, + {".gtar", "application/x-gtar"}, + {".gz", "application/x-gzip"}, + {".h", "text/plain"}, + {".hdf", "application/x-hdf"}, + {".hdml", "text/x-hdml"}, + {".heic", "image/heic"}, + {".heics", "image/heic-sequence"}, + {".heif", "image/heif"}, + {".heifs", "image/heif-sequence"}, + {".hhc", "application/x-oleobject"}, + {".hhk", "application/octet-stream"}, + {".hhp", "application/octet-stream"}, + {".hlp", "application/winhlp"}, + {".hpp", "text/plain"}, + {".hqx", "application/mac-binhex40"}, + {".hta", "application/hta"}, + {".htc", "text/x-component"}, + {".htm", "text/html"}, + {".html", "text/html"}, + {".htt", "text/webviewhtml"}, + {".hxa", "application/xml"}, + {".hxc", "application/xml"}, + {".hxd", "application/octet-stream"}, + {".hxe", "application/xml"}, + {".hxf", "application/xml"}, + {".hxh", "application/octet-stream"}, + {".hxi", "application/octet-stream"}, + {".hxk", "application/xml"}, + {".hxq", "application/octet-stream"}, + {".hxr", "application/octet-stream"}, + {".hxs", "application/octet-stream"}, + {".hxt", "text/html"}, + {".hxv", "application/xml"}, + {".hxw", "application/octet-stream"}, + {".hxx", "text/plain"}, + {".i", "text/plain"}, + {".ical", "text/calendar"}, + {".icalendar", "text/calendar"}, + {".ico", "image/x-icon"}, + {".ics", "text/calendar"}, + {".idl", "text/plain"}, + {".ief", "image/ief"}, + {".ifb", "text/calendar"}, + {".iii", "application/x-iphone"}, + {".inc", "text/plain"}, + {".inf", "application/octet-stream"}, + {".ini", "text/plain"}, + {".inl", "text/plain"}, + {".ins", "application/x-internet-signup"}, + {".ipa", "application/x-itunes-ipa"}, + {".ipg", "application/x-itunes-ipg"}, + {".ipproj", "text/plain"}, + {".ipsw", "application/x-itunes-ipsw"}, + {".iqy", "text/x-ms-iqy"}, + {".isp", "application/x-internet-signup"}, + {".isma", "application/octet-stream"}, + {".ismv", "application/octet-stream"}, + {".ite", "application/x-itunes-ite"}, + {".itlp", "application/x-itunes-itlp"}, + {".itms", "application/x-itunes-itms"}, + {".itpc", "application/x-itunes-itpc"}, + {".IVF", "video/x-ivf"}, + {".jar", "application/java-archive"}, + {".java", "application/octet-stream"}, + {".jck", "application/liquidmotion"}, + {".jcz", "application/liquidmotion"}, + {".jfif", "image/pjpeg"}, + {".jnlp", "application/x-java-jnlp-file"}, + {".jpb", "application/octet-stream"}, + {".jpe", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".jpg", "image/jpeg"}, + {".js", "application/javascript"}, + {".json", "application/json"}, + {".jsx", "text/jscript"}, + {".jsxbin", "text/plain"}, + {".latex", "application/x-latex"}, + {".library-ms", "application/windows-library+xml"}, + {".lit", "application/x-ms-reader"}, + {".loadtest", "application/xml"}, + {".lpk", "application/octet-stream"}, + {".lsf", "video/x-la-asf"}, + {".lst", "text/plain"}, + {".lsx", "video/x-la-asf"}, + {".lzh", "application/octet-stream"}, + {".m13", "application/x-msmediaview"}, + {".m14", "application/x-msmediaview"}, + {".m1v", "video/mpeg"}, + {".m2t", "video/vnd.dlna.mpeg-tts"}, + {".m2ts", "video/vnd.dlna.mpeg-tts"}, + {".m2v", "video/mpeg"}, + {".m3u", "audio/x-mpegurl"}, + {".m3u8", "audio/x-mpegurl"}, + {".m4a", "audio/m4a"}, + {".m4b", "audio/m4b"}, + {".m4p", "audio/m4p"}, + {".m4r", "audio/x-m4r"}, + {".m4v", "video/x-m4v"}, + {".mac", "image/x-macpaint"}, + {".mak", "text/plain"}, + {".man", "application/x-troff-man"}, + {".manifest", "application/x-ms-manifest"}, + {".map", "text/plain"}, + {".master", "application/xml"}, + {".mbox", "application/mbox"}, + {".mda", "application/msaccess"}, + {".mdb", "application/x-msaccess"}, + {".mde", "application/msaccess"}, + {".mdp", "application/octet-stream"}, + {".me", "application/x-troff-me"}, + {".mfp", "application/x-shockwave-flash"}, + {".mht", "message/rfc822"}, + {".mhtml", "message/rfc822"}, + {".mid", "audio/mid"}, + {".midi", "audio/mid"}, + {".mix", "application/octet-stream"}, + {".mk", "text/plain"}, + {".mk3d", "video/x-matroska-3d"}, + {".mka", "audio/x-matroska"}, + {".mkv", "video/x-matroska"}, + {".mmf", "application/x-smaf"}, + {".mno", "text/xml"}, + {".mny", "application/x-msmoney"}, + {".mod", "video/mpeg"}, + {".mov", "video/quicktime"}, + {".movie", "video/x-sgi-movie"}, + {".mp2", "video/mpeg"}, + {".mp2v", "video/mpeg"}, + {".mp3", "audio/mpeg"}, + {".mp4", "video/mp4"}, + {".mp4v", "video/mp4"}, + {".mpa", "video/mpeg"}, + {".mpe", "video/mpeg"}, + {".mpeg", "video/mpeg"}, + {".mpf", "application/vnd.ms-mediapackage"}, + {".mpg", "video/mpeg"}, + {".mpp", "application/vnd.ms-project"}, + {".mpv2", "video/mpeg"}, + {".mqv", "video/quicktime"}, + {".ms", "application/x-troff-ms"}, + {".msg", "application/vnd.ms-outlook"}, + {".msi", "application/octet-stream"}, + {".mso", "application/octet-stream"}, + {".mts", "video/vnd.dlna.mpeg-tts"}, + {".mtx", "application/xml"}, + {".mvb", "application/x-msmediaview"}, + {".mvc", "application/x-miva-compiled"}, + {".mxf", "application/mxf"}, + {".mxp", "application/x-mmxp"}, + {".nc", "application/x-netcdf"}, + {".nsc", "video/x-ms-asf"}, + {".nws", "message/rfc822"}, + {".ocx", "application/octet-stream"}, + {".oda", "application/oda"}, + {".odb", "application/vnd.oasis.opendocument.database"}, + {".odc", "application/vnd.oasis.opendocument.chart"}, + {".odf", "application/vnd.oasis.opendocument.formula"}, + {".odg", "application/vnd.oasis.opendocument.graphics"}, + {".odh", "text/plain"}, + {".odi", "application/vnd.oasis.opendocument.image"}, + {".odl", "text/plain"}, + {".odm", "application/vnd.oasis.opendocument.text-master"}, + {".odp", "application/vnd.oasis.opendocument.presentation"}, + {".ods", "application/vnd.oasis.opendocument.spreadsheet"}, + {".odt", "application/vnd.oasis.opendocument.text"}, + {".oga", "audio/ogg"}, + {".ogg", "audio/ogg"}, + {".ogv", "video/ogg"}, + {".ogx", "application/ogg"}, + {".one", "application/onenote"}, + {".onea", "application/onenote"}, + {".onepkg", "application/onenote"}, + {".onetmp", "application/onenote"}, + {".onetoc", "application/onenote"}, + {".onetoc2", "application/onenote"}, + {".opus", "audio/ogg"}, + {".orderedtest", "application/xml"}, + {".osdx", "application/opensearchdescription+xml"}, + {".otf", "application/font-sfnt"}, + {".otg", "application/vnd.oasis.opendocument.graphics-template"}, + {".oth", "application/vnd.oasis.opendocument.text-web"}, + {".otp", "application/vnd.oasis.opendocument.presentation-template"}, + {".ots", "application/vnd.oasis.opendocument.spreadsheet-template"}, + {".ott", "application/vnd.oasis.opendocument.text-template"}, + {".oxps", "application/oxps"}, + {".oxt", "application/vnd.openofficeorg.extension"}, + {".p10", "application/pkcs10"}, + {".p12", "application/x-pkcs12"}, + {".p7b", "application/x-pkcs7-certificates"}, + {".p7c", "application/pkcs7-mime"}, + {".p7m", "application/pkcs7-mime"}, + {".p7r", "application/x-pkcs7-certreqresp"}, + {".p7s", "application/pkcs7-signature"}, + {".pbm", "image/x-portable-bitmap"}, + {".pcast", "application/x-podcast"}, + {".pct", "image/pict"}, + {".pcx", "application/octet-stream"}, + {".pcz", "application/octet-stream"}, + {".pdf", "application/pdf"}, + {".pfb", "application/octet-stream"}, + {".pfm", "application/octet-stream"}, + {".pfx", "application/x-pkcs12"}, + {".pgm", "image/x-portable-graymap"}, + {".pic", "image/pict"}, + {".pict", "image/pict"}, + {".pkgdef", "text/plain"}, + {".pkgundef", "text/plain"}, + {".pko", "application/vnd.ms-pki.pko"}, + {".pls", "audio/scpls"}, + {".pma", "application/x-perfmon"}, + {".pmc", "application/x-perfmon"}, + {".pml", "application/x-perfmon"}, + {".pmr", "application/x-perfmon"}, + {".pmw", "application/x-perfmon"}, + {".png", "image/png"}, + {".pnm", "image/x-portable-anymap"}, + {".pnt", "image/x-macpaint"}, + {".pntg", "image/x-macpaint"}, + {".pnz", "image/png"}, + {".pot", "application/vnd.ms-powerpoint"}, + {".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"}, + {".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"}, + {".ppa", "application/vnd.ms-powerpoint"}, + {".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"}, + {".ppm", "image/x-portable-pixmap"}, + {".pps", "application/vnd.ms-powerpoint"}, + {".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"}, + {".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"}, + {".ppt", "application/vnd.ms-powerpoint"}, + {".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"}, + {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, + {".prf", "application/pics-rules"}, + {".prm", "application/octet-stream"}, + {".prx", "application/octet-stream"}, + {".ps", "application/postscript"}, + {".psc1", "application/PowerShell"}, + {".psd", "application/octet-stream"}, + {".psess", "application/xml"}, + {".psm", "application/octet-stream"}, + {".psp", "application/octet-stream"}, + {".pst", "application/vnd.ms-outlook"}, + {".pub", "application/x-mspublisher"}, + {".pwz", "application/vnd.ms-powerpoint"}, + {".qht", "text/x-html-insertion"}, + {".qhtm", "text/x-html-insertion"}, + {".qt", "video/quicktime"}, + {".qti", "image/x-quicktime"}, + {".qtif", "image/x-quicktime"}, + {".qtl", "application/x-quicktimeplayer"}, + {".qxd", "application/octet-stream"}, + {".ra", "audio/x-pn-realaudio"}, + {".ram", "audio/x-pn-realaudio"}, + {".rar", "application/x-rar-compressed"}, + {".ras", "image/x-cmu-raster"}, + {".rat", "application/rat-file"}, + {".rc", "text/plain"}, + {".rc2", "text/plain"}, + {".rct", "text/plain"}, + {".rdlc", "application/xml"}, + {".reg", "text/plain"}, + {".resx", "application/xml"}, + {".rf", "image/vnd.rn-realflash"}, + {".rgb", "image/x-rgb"}, + {".rgs", "text/plain"}, + {".rm", "application/vnd.rn-realmedia"}, + {".rmi", "audio/mid"}, + {".rmp", "application/vnd.rn-rn_music_package"}, + {".rmvb", "application/vnd.rn-realmedia-vbr"}, + {".roff", "application/x-troff"}, + {".rpm", "audio/x-pn-realaudio-plugin"}, + {".rqy", "text/x-ms-rqy"}, + {".rtf", "application/rtf"}, + {".rtx", "text/richtext"}, + {".rvt", "application/octet-stream"}, + {".ruleset", "application/xml"}, + {".s", "text/plain"}, + {".safariextz", "application/x-safari-safariextz"}, + {".scd", "application/x-msschedule"}, + {".scr", "text/plain"}, + {".sct", "text/scriptlet"}, + {".sd2", "audio/x-sd2"}, + {".sdp", "application/sdp"}, + {".sea", "application/octet-stream"}, + {".searchConnector-ms", "application/windows-search-connector+xml"}, + {".setpay", "application/set-payment-initiation"}, + {".setreg", "application/set-registration-initiation"}, + {".settings", "application/xml"}, + {".sgimb", "application/x-sgimb"}, + {".sgml", "text/sgml"}, + {".sh", "application/x-sh"}, + {".shar", "application/x-shar"}, + {".shtml", "text/html"}, + {".sit", "application/x-stuffit"}, + {".sitemap", "application/xml"}, + {".skin", "application/xml"}, + {".skp", "application/x-koan"}, + {".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"}, + {".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"}, + {".slk", "application/vnd.ms-excel"}, + {".sln", "text/plain"}, + {".slupkg-ms", "application/x-ms-license"}, + {".smd", "audio/x-smd"}, + {".smi", "application/octet-stream"}, + {".smx", "audio/x-smd"}, + {".smz", "audio/x-smd"}, + {".snd", "audio/basic"}, + {".snippet", "application/xml"}, + {".snp", "application/octet-stream"}, + {".sql", "application/sql"}, + {".sol", "text/plain"}, + {".sor", "text/plain"}, + {".spc", "application/x-pkcs7-certificates"}, + {".spl", "application/futuresplash"}, + {".spx", "audio/ogg"}, + {".src", "application/x-wais-source"}, + {".srf", "text/plain"}, + {".SSISDeploymentManifest", "text/xml"}, + {".ssm", "application/streamingmedia"}, + {".sst", "application/vnd.ms-pki.certstore"}, + {".stl", "application/vnd.ms-pki.stl"}, + {".sv4cpio", "application/x-sv4cpio"}, + {".sv4crc", "application/x-sv4crc"}, + {".svc", "application/xml"}, + {".svg", "image/svg+xml"}, + {".swf", "application/x-shockwave-flash"}, + {".step", "application/step"}, + {".stp", "application/step"}, + {".t", "application/x-troff"}, + {".tar", "application/x-tar"}, + {".tcl", "application/x-tcl"}, + {".testrunconfig", "application/xml"}, + {".testsettings", "application/xml"}, + {".tex", "application/x-tex"}, + {".texi", "application/x-texinfo"}, + {".texinfo", "application/x-texinfo"}, + {".tgz", "application/x-compressed"}, + {".thmx", "application/vnd.ms-officetheme"}, + {".thn", "application/octet-stream"}, + {".tif", "image/tiff"}, + {".tiff", "image/tiff"}, + {".tlh", "text/plain"}, + {".tli", "text/plain"}, + {".toc", "application/octet-stream"}, + {".tr", "application/x-troff"}, + {".trm", "application/x-msterminal"}, + {".trx", "application/xml"}, + {".ts", "video/vnd.dlna.mpeg-tts"}, + {".tsv", "text/tab-separated-values"}, + {".ttf", "application/font-sfnt"}, + {".tts", "video/vnd.dlna.mpeg-tts"}, + {".txt", "text/plain"}, + {".u32", "application/octet-stream"}, + {".uls", "text/iuls"}, + {".user", "text/plain"}, + {".ustar", "application/x-ustar"}, + {".vb", "text/plain"}, + {".vbdproj", "text/plain"}, + {".vbk", "video/mpeg"}, + {".vbproj", "text/plain"}, + {".vbs", "text/vbscript"}, + {".vcf", "text/x-vcard"}, + {".vcproj", "application/xml"}, + {".vcs", "text/plain"}, + {".vcxproj", "application/xml"}, + {".vddproj", "text/plain"}, + {".vdp", "text/plain"}, + {".vdproj", "text/plain"}, + {".vdx", "application/vnd.ms-visio.viewer"}, + {".vml", "text/xml"}, + {".vscontent", "application/xml"}, + {".vsct", "text/xml"}, + {".vsd", "application/vnd.visio"}, + {".vsi", "application/ms-vsi"}, + {".vsix", "application/vsix"}, + {".vsixlangpack", "text/xml"}, + {".vsixmanifest", "text/xml"}, + {".vsmdi", "application/xml"}, + {".vspscc", "text/plain"}, + {".vss", "application/vnd.visio"}, + {".vsscc", "text/plain"}, + {".vssettings", "text/xml"}, + {".vssscc", "text/plain"}, + {".vst", "application/vnd.visio"}, + {".vstemplate", "text/xml"}, + {".vsto", "application/x-ms-vsto"}, + {".vsw", "application/vnd.visio"}, + {".vsx", "application/vnd.visio"}, + {".vtt", "text/vtt"}, + {".vtx", "application/vnd.visio"}, + {".wasm", "application/wasm"}, + {".wav", "audio/wav"}, + {".wave", "audio/wav"}, + {".wax", "audio/x-ms-wax"}, + {".wbk", "application/msword"}, + {".wbmp", "image/vnd.wap.wbmp"}, + {".wcm", "application/vnd.ms-works"}, + {".wdb", "application/vnd.ms-works"}, + {".wdp", "image/vnd.ms-photo"}, + {".webarchive", "application/x-safari-webarchive"}, + {".webm", "video/webm"}, + {".webp", "image/webp"}, /* https://en.wikipedia.org/wiki/WebP */ + {".webtest", "application/xml"}, + {".wiq", "application/xml"}, + {".wiz", "application/msword"}, + {".wks", "application/vnd.ms-works"}, + {".WLMP", "application/wlmoviemaker"}, + {".wlpginstall", "application/x-wlpg-detect"}, + {".wlpginstall3", "application/x-wlpg3-detect"}, + {".wm", "video/x-ms-wm"}, + {".wma", "audio/x-ms-wma"}, + {".wmd", "application/x-ms-wmd"}, + {".wmf", "application/x-msmetafile"}, + {".wml", "text/vnd.wap.wml"}, + {".wmlc", "application/vnd.wap.wmlc"}, + {".wmls", "text/vnd.wap.wmlscript"}, + {".wmlsc", "application/vnd.wap.wmlscriptc"}, + {".wmp", "video/x-ms-wmp"}, + {".wmv", "video/x-ms-wmv"}, + {".wmx", "video/x-ms-wmx"}, + {".wmz", "application/x-ms-wmz"}, + {".woff", "application/font-woff"}, + {".woff2", "application/font-woff2"}, + {".wpl", "application/vnd.ms-wpl"}, + {".wps", "application/vnd.ms-works"}, + {".wri", "application/x-mswrite"}, + {".wrl", "x-world/x-vrml"}, + {".wrz", "x-world/x-vrml"}, + {".wsc", "text/scriptlet"}, + {".wsdl", "text/xml"}, + {".wvx", "video/x-ms-wvx"}, + {".x", "application/directx"}, + {".xaf", "x-world/x-vrml"}, + {".xaml", "application/xaml+xml"}, + {".xap", "application/x-silverlight-app"}, + {".xbap", "application/x-ms-xbap"}, + {".xbm", "image/x-xbitmap"}, + {".xdr", "text/plain"}, + {".xht", "application/xhtml+xml"}, + {".xhtml", "application/xhtml+xml"}, + {".xla", "application/vnd.ms-excel"}, + {".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"}, + {".xlc", "application/vnd.ms-excel"}, + {".xld", "application/vnd.ms-excel"}, + {".xlk", "application/vnd.ms-excel"}, + {".xll", "application/vnd.ms-excel"}, + {".xlm", "application/vnd.ms-excel"}, + {".xls", "application/vnd.ms-excel"}, + {".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"}, + {".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"}, + {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + {".xlt", "application/vnd.ms-excel"}, + {".xltm", "application/vnd.ms-excel.template.macroEnabled.12"}, + {".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"}, + {".xlw", "application/vnd.ms-excel"}, + {".xml", "text/xml"}, + {".xmp", "application/octet-stream"}, + {".xmta", "application/xml"}, + {".xof", "x-world/x-vrml"}, + {".XOML", "text/plain"}, + {".xpm", "image/x-xpixmap"}, + {".xps", "application/vnd.ms-xpsdocument"}, + {".xrm-ms", "text/xml"}, + {".xsc", "application/xml"}, + {".xsd", "text/xml"}, + {".xsf", "text/xml"}, + {".xsl", "text/xml"}, + {".xslt", "text/xml"}, + {".xsn", "application/octet-stream"}, + {".xss", "application/xml"}, + {".xspf", "application/xspf+xml"}, + {".xtp", "application/octet-stream"}, + {".xwd", "image/x-xwindowdump"}, + {".z", "application/x-compress"}, + {".zip", "application/zip"}, + + {"application/fsharp-script", ".fsx"}, + {"application/msaccess", ".adp"}, + {"application/msword", ".doc"}, + {"application/octet-stream", ".bin"}, + {"application/onenote", ".one"}, + {"application/postscript", ".eps"}, + {"application/step", ".step"}, + {"application/vnd.ms-excel", ".xls"}, + {"application/vnd.ms-powerpoint", ".ppt"}, + {"application/vnd.ms-works", ".wks"}, + {"application/vnd.visio", ".vsd"}, + {"application/x-director", ".dir"}, + {"application/x-msdos-program", ".exe"}, + {"application/x-shockwave-flash", ".swf"}, + {"application/x-x509-ca-cert", ".cer"}, + {"application/x-zip-compressed", ".zip"}, + {"application/xhtml+xml", ".xhtml"}, + { + "application/xml", ".xml" + }, // anomaly, .xml -> text/xml, but application/xml -> many things, but all are xml, so safest is .xml + {"audio/aac", ".AAC"}, + {"audio/aiff", ".aiff"}, + {"audio/basic", ".snd"}, + {"audio/mid", ".midi"}, + {"audio/mp4", ".m4a"}, // one way mapping only, mime -> ext + {"audio/wav", ".wav"}, + {"audio/x-m4a", ".m4a"}, + {"audio/x-mpegurl", ".m3u"}, + {"audio/x-pn-realaudio", ".ra"}, + {"audio/x-smd", ".smd"}, + {"image/bmp", ".bmp"}, + {"image/jpeg", ".jpg"}, + {"image/pict", ".pic"}, + {"image/png", ".png"}, // Defined in [RFC-2045], [RFC-2048] + { + "image/x-png", ".png" + }, // See https://www.w3.org/TR/PNG/#A-Media-type :"It is recommended that implementations also recognize the media type "image/x-png"." + {"image/tiff", ".tiff"}, + {"image/x-macpaint", ".mac"}, + {"image/x-quicktime", ".qti"}, + {"message/rfc822", ".eml"}, + {"text/calendar", ".ics"}, + {"text/html", ".html"}, + {"text/plain", ".txt"}, + {"text/scriptlet", ".wsc"}, + {"text/xml", ".xml"}, + {"video/3gpp", ".3gp"}, + {"video/3gpp2", ".3gp2"}, + {"video/mp4", ".mp4"}, + {"video/mpeg", ".mpg"}, + {"video/quicktime", ".mov"}, + {"video/vnd.dlna.mpeg-tts", ".m2t"}, + {"video/x-dv", ".dv"}, + {"video/x-la-asf", ".lsf"}, + {"video/x-ms-asf", ".asf"}, + {"x-world/x-vrml", ".xof"}, + + #endregion + }; + + var cache = mappings.ToList(); // need ToList() to avoid modifying while still enumerating + + foreach (var mapping in cache) + { + if (!mappings.ContainsKey(mapping.Value)) + { + mappings.Add(mapping.Value, mapping.Key); + } + } + + return mappings; + } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/Internals/Validation.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/Internals/Validation.cs new file mode 100644 index 00000000..d1bf65bf --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/Internals/Validation.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Lab.LineBot.SDK.Internals +{ + public class Validation + { + public static bool TryValidate(object contact, out List errors) + { + var context = new ValidationContext(contact, null, null); + errors = new List(); + return Validator.TryValidateObject(contact, context, errors, true); + } + + public static void Validate(object instance) + { + var context = new ValidationContext(instance, null, null); + Validator.ValidateObject(instance, context, true); + } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/Lab.LineBot.SDK.csproj b/Line/Lab.LineNotify/Lab.LineBot.SDK/Lab.LineBot.SDK.csproj new file mode 100644 index 00000000..600f4841 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/Lab.LineBot.SDK.csproj @@ -0,0 +1,11 @@ + + + + net5.0 + + + + + + + diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs new file mode 100644 index 00000000..0b59d0df --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Lab.LineBot.SDK.Internals; +using Lab.LineBot.SDK.Models; + +namespace Lab.LineBot.SDK +{ + public class LineNotifyProvider : ILineNotifyProvider + { + private static readonly Lazy s_oAuth2ClientLazy = + new Lazy(() => new HttpClient + { + BaseAddress = new Uri(OAuth2Endpoint) + }); + + private static readonly Lazy s_apiClientLazy = + new Lazy(() => new HttpClient + { + BaseAddress = new Uri(ApiEndpoint) + }); + + private static readonly string OAuth2Endpoint = "https://notify-bot.line.me/"; + private static readonly string ApiEndpoint = "https://notify-api.line.me/"; + + public bool IsThrowInternalError { get; set; } = false; + + public HttpClient OAuth2Client + { + get + { + if (this._oAuth2Client == null) + { + return s_oAuth2ClientLazy.Value; + } + + return this._oAuth2Client; + } + set => this._oAuth2Client = value; + } + + public HttpClient ApiClient + { + get + { + if (this._apiClient == null) + { + return s_apiClientLazy.Value; + } + + return this._apiClient; + } + set => this._apiClient = value; + } + + private HttpClient _apiClient; + + private HttpClient _oAuth2Client; + + public async Task NotifyAsync(NotifyWithImageRequest request, + CancellationToken cancelToken) + { + Validation.Validate(request); + var url = $"api/notify?message={request.Message}"; + using var formDataContent = new MultipartFormDataContent(); + + var imageName = Path.GetFileName(request.FilePath); + var mimeType = MimeTypeMapping.GetMimeType(imageName); + var imageContent = new ByteArrayContent(request.FileBytes); + imageContent.Headers.ContentType = new MediaTypeHeaderValue(mimeType); + + formDataContent.Add(imageContent, "imageFile", imageName); + + var httpRequest = new HttpRequestMessage(HttpMethod.Post, url) + { + Headers = {Authorization = new AuthenticationHeaderValue("Bearer", request.AccessToken)}, + Content = formDataContent + }; + + var response = await this.ApiClient.SendAsync(httpRequest, cancelToken); + + if (response.StatusCode != HttpStatusCode.OK) + { + if (this.IsThrowInternalError) + { + var error = await response.Content.ReadAsStringAsync(); + throw new LineNotifyProviderException(error); + } + } + + return await response.Content.ReadAsAsync(cancelToken); + } + + public async Task NotifyAsync(NotifyWithStickerRequest request, + CancellationToken cancelToken) + { + Validation.Validate(request); + + var url = "api/notify"; + var httpRequest = new HttpRequestMessage(HttpMethod.Post, url) + { + Headers = {Authorization = new AuthenticationHeaderValue("Bearer", request.AccessToken)}, + Content = new FormUrlEncodedContent(new Dictionary + { + {"message", request.Message}, + {"stickerPackageId", request.StickerPackageId}, + {"stickerId", request.StickerId}, + }), + }; + + var response = await this.ApiClient.SendAsync(httpRequest, cancelToken); + + if (response.StatusCode != HttpStatusCode.OK) + { + if (this.IsThrowInternalError) + { + var error = await response.Content.ReadAsStringAsync(); + throw new LineNotifyProviderException(error); + } + } + + return await response.Content.ReadAsAsync(cancelToken); + } + + public async Task GetAccessTokenInfoAsync(string accessToken, + CancellationToken cancelToken) + { + if (string.IsNullOrWhiteSpace(accessToken)) + { + throw new ArgumentNullException(nameof(accessToken)); + } + + var url = "api/status"; + var httpRequest = new HttpRequestMessage(HttpMethod.Get, url) + { + Headers = {Authorization = new AuthenticationHeaderValue("Bearer", accessToken)}, + Content = new FormUrlEncodedContent(new Dictionary()), + }; + + var response = await this.ApiClient.SendAsync(httpRequest, cancelToken); + + if (response.StatusCode != HttpStatusCode.OK) + { + if (this.IsThrowInternalError) + { + var error = await response.Content.ReadAsStringAsync(); + throw new LineNotifyProviderException(error); + } + } + + var tokenInfo = await response.Content.ReadAsAsync(cancelToken); + tokenInfo.Limit = GetValue(response, "X-RateLimit-Limit"); + tokenInfo.ImageLimit = GetValue(response, "X-RateLimit-ImageLimit"); + tokenInfo.Remaining = GetValue(response, "X-RateLimit-Remaining"); + tokenInfo.ImageRemaining = GetValue(response, "X-RateLimit-ImageRemaining"); + tokenInfo.Reset = GetValue(response, "X-RateLimit-Reset"); + tokenInfo.ResetLocalTime = ToLocalTime(tokenInfo.Reset); + return tokenInfo; + } + + public string CreateAuthorizeCodeUrl(AuthorizeCodeUrlRequest request) + { + Validation.Validate(request); + + var url = "oauth/authorize"; + return $"{OAuth2Endpoint}" + + url + + "?response_type=code" + + "&scope=notify" + + "&response_mode=form_post" + + $"&client_id={request.ClientId}" + + $"&redirect_uri={request.CallbackUrl}" + + $"&state={request.State}" + ; + } + + public async Task GetAccessTokenAsync(TokenRequest request, + CancellationToken cancelToken) + { + Validation.Validate(request); + + var url = "oauth/token"; + + var content = new FormUrlEncodedContent(new Dictionary + { + {"grant_type", "authorization_code"}, + {"code", request.Code}, + {"redirect_uri", request.CallbackUrl}, + {"client_id", request.ClientId}, + {"client_secret", request.ClientSecret}, + }); + + var response = await this.OAuth2Client.PostAsync(url, content, cancelToken); + string result = null; + + if (response.StatusCode != HttpStatusCode.OK) + { + if (this.IsThrowInternalError) + { + var error = await response.Content.ReadAsStringAsync(); + throw new LineNotifyProviderException(error); + } + } + + return await response.Content.ReadAsAsync(cancelToken); + } + + public async Task RevokeAsync(string accessToken, CancellationToken cancelToken) + { + if (string.IsNullOrWhiteSpace(accessToken)) + { + throw new ArgumentNullException(nameof(accessToken)); + } + + var url = "api/revoke"; + var httpRequest = new HttpRequestMessage(HttpMethod.Post, url) + { + Headers = {Authorization = new AuthenticationHeaderValue("Bearer", accessToken)}, + Content = new FormUrlEncodedContent(new Dictionary()), + }; + + var response = await this.ApiClient.SendAsync(httpRequest, cancelToken); + + if (response.StatusCode != HttpStatusCode.OK) + { + if (this.IsThrowInternalError) + { + var error = await response.Content.ReadAsStringAsync(); + throw new LineNotifyProviderException(error); + } + } + + return await response.Content.ReadAsAsync(cancelToken); + } + + private static T GetValue(HttpResponseMessage response, string key) + { + var result = default(T); + response.Headers.TryGetValues(key, out var values); + if (values == null) + { + return result; + } + + var content = values.FirstOrDefault(); + + return (T) Convert.ChangeType(content, typeof(T)); + } + + private static DateTime ToLocalTime(long source) + { + var timeOffset = DateTimeOffset.FromUnixTimeSeconds(source); + return timeOffset.DateTime.ToUniversalTime(); + } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProviderException.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProviderException.cs new file mode 100644 index 00000000..58a527dc --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProviderException.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.Serialization; + +namespace Lab.LineBot.SDK +{ + [Serializable] + public class LineNotifyProviderException : Exception + { + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public LineNotifyProviderException() + { + } + + public LineNotifyProviderException(string message) : base(message) + { + } + + public LineNotifyProviderException(string message, Exception inner) : base(message, inner) + { + } + + protected LineNotifyProviderException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/AuthorizeCodeUrlRequest.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/AuthorizeCodeUrlRequest.cs new file mode 100644 index 00000000..1d9300db --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/AuthorizeCodeUrlRequest.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace Lab.LineBot.SDK.Models +{ + public class AuthorizeCodeUrlRequest + { + [Required] + public string CallbackUrl { get; set; } + + [Required] + public string ClientId { get; set; } + + [Required] + public string State { get; set; } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/GenericResponse.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/GenericResponse.cs new file mode 100644 index 00000000..f39e70ab --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/GenericResponse.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Lab.LineBot.SDK.Models +{ + public class GenericResponse + { + [JsonProperty("status")] + public int Status { get; set; } + + [JsonProperty("message")] + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/NotifyWithImageRequest.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/NotifyWithImageRequest.cs new file mode 100644 index 00000000..1d55a823 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/NotifyWithImageRequest.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; + +namespace Lab.LineBot.SDK.Models +{ + public class NotifyWithImageRequest + { + [Required] + public string Message { get; set; } + + [Required] + public string AccessToken { get; set; } + + public string FilePath { get; set; } + + public byte[] FileBytes { get; set; } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/NotifyWithStickerRequest.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/NotifyWithStickerRequest.cs new file mode 100644 index 00000000..b0da5d02 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/NotifyWithStickerRequest.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; + +namespace Lab.LineBot.SDK.Models +{ + public class NotifyWithStickerRequest + { + [Required] + public string Message { get; set; } + + [Required] + public string AccessToken { get; set; } + + //https://developers.line.biz/en/docs/messaging-api/sticker-list/#sticker-definitions + public string StickerPackageId { get; set; } + + public string StickerId { get; set; } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/TokenInfoResponse.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/TokenInfoResponse.cs new file mode 100644 index 00000000..8f6d9af9 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/TokenInfoResponse.cs @@ -0,0 +1,26 @@ +using System; +using Newtonsoft.Json; + +namespace Lab.LineBot.SDK.Models +{ + public class TokenInfoResponse : GenericResponse + { + [JsonProperty("targetType")] + public string TargetType { get; set; } + + [JsonProperty("target")] + public string Target { get; set; } + + public int Limit { get; set; } + + public int ImageLimit { get; set; } + + public int Remaining { get; set; } + + public int ImageRemaining { get; set; } + + public int Reset { get; set; } + + public DateTime ResetLocalTime { get; set; } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/TokenRequest.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/TokenRequest.cs new file mode 100644 index 00000000..07288524 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/TokenRequest.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; + +namespace Lab.LineBot.SDK.Models +{ + public class TokenRequest + { + [Required] + public string Code { get; set; } + + [Required] + public string ClientId { get; set; } + + [Required] + public string ClientSecret { get; set; } + + [Required] + public string CallbackUrl { get; set; } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/TokenResponse.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/TokenResponse.cs new file mode 100644 index 00000000..8d72d9dd --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/Models/TokenResponse.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Lab.LineBot.SDK.Models +{ + public class TokenResponse : GenericResponse + { + [JsonProperty("access_token")] + public string AccessToken { get; set; } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service/Controllers/AuthorizeController.cs b/Line/Lab.LineNotify/Lab.LineNotify.Service/Controllers/AuthorizeController.cs new file mode 100644 index 00000000..39a3c996 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service/Controllers/AuthorizeController.cs @@ -0,0 +1,61 @@ +using System.Threading; +using System.Threading.Tasks; +using Lab.LineBot.SDK; +using Lab.LineBot.SDK.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Lab.LineNotify.Service.Controllers +{ + [ApiController] + [Route("[controller]")] + public class AuthorizeCodeController : ControllerBase + { + private readonly IConfiguration _config; + private readonly ILineNotifyProvider _lineNotifyProvider; + private readonly ILogger _logger; + + public AuthorizeCodeController(ILogger logger, + IConfiguration config, + ILineNotifyProvider lineNotifyProvider) + { + this._logger = logger; + this._config = config; + this._lineNotifyProvider = lineNotifyProvider; + } + + [HttpPost] + public async Task Post([FromForm] IFormCollection data, CancellationToken cancelToken) + { + if (data.TryGetValue("code", out var code) == false) + { + this.ModelState.AddModelError("code 欄位", "必填"); + return this.BadRequest(this.ModelState); + } + + if (data.TryGetValue("state", out var state) == false) + { + this.ModelState.AddModelError("state 欄位", "必填"); + return this.BadRequest(this.ModelState); + } + + var config = this._config; + var lineNotifyProvider = this._lineNotifyProvider; + + var lineConfig = config.GetSection("LineNotify"); + var request = new TokenRequest + { + Code = code, + ClientId = lineConfig.GetValue("clientId"), + ClientSecret = lineConfig.GetValue("clientSecret"), + CallbackUrl = lineConfig.GetValue("redirectUri"), + }; + var accessToken = await lineNotifyProvider.GetAccessTokenAsync(request, cancelToken); + + //TODO:不應該回傳 Access Token + return this.Ok(accessToken); + } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service/Lab.LineNotify.Service.csproj b/Line/Lab.LineNotify/Lab.LineNotify.Service/Lab.LineNotify.Service.csproj new file mode 100644 index 00000000..7cd965d3 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service/Lab.LineNotify.Service.csproj @@ -0,0 +1,17 @@ + + + + net5.0 + + + + + + + + + + + + + diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service/Program.cs b/Line/Lab.LineNotify/Lab.LineNotify.Service/Program.cs new file mode 100644 index 00000000..af088d45 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service/Program.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Lab.LineNotify.Service +{ + public class Program + { + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + } + + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service/Properties/launchSettings.json b/Line/Lab.LineNotify/Lab.LineNotify.Service/Properties/launchSettings.json new file mode 100644 index 00000000..39776679 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:24864", + "sslPort": 44336 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Lab.LineNotify.Service": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service/ServiceModels/ReviceAuthorizeCodeRequest.cs b/Line/Lab.LineNotify/Lab.LineNotify.Service/ServiceModels/ReviceAuthorizeCodeRequest.cs new file mode 100644 index 00000000..ff163342 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service/ServiceModels/ReviceAuthorizeCodeRequest.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace Lab.LineNotify.Service.ServiceModels +{ + public class ReceiveAuthorizeCodeRequest + { + + [JsonProperty("code")] + public string Code { get; set; } + + [JsonProperty("state")] + public int State { get; set; } + + [JsonProperty("error")] + public string Error { get; set; } + + [JsonProperty("error_description")] + public string ErrorDescription { get; set; } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service/Startup.cs b/Line/Lab.LineNotify/Lab.LineNotify.Service/Startup.cs new file mode 100644 index 00000000..1767dc7b --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service/Startup.cs @@ -0,0 +1,52 @@ +using Lab.LineBot.SDK; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.OpenApi.Models; + +namespace Lab.LineNotify.Service +{ + public class Startup + { + public IConfiguration Configuration { get; } + + public Startup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Lab.LineNotify.Service v1")); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers().AddNewtonsoftJson(); + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", + new OpenApiInfo {Title = "Lab.LineNotify.Service", Version = "v1"}); + }); + services.AddSingleton(); + services.AddSingleton(p => p.GetService()); + } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service/WeatherForecast.cs b/Line/Lab.LineNotify/Lab.LineNotify.Service/WeatherForecast.cs new file mode 100644 index 00000000..1a6b47f7 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service/WeatherForecast.cs @@ -0,0 +1,15 @@ +using System; + +namespace Lab.LineNotify.Service +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int) (this.TemperatureC / 0.5556); + + public string Summary { get; set; } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service/appsettings.Development.json b/Line/Lab.LineNotify/Lab.LineNotify.Service/appsettings.Development.json new file mode 100644 index 00000000..8983e0fc --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service/appsettings.json b/Line/Lab.LineNotify/Lab.LineNotify.Service/appsettings.json new file mode 100644 index 00000000..5f313c19 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service/appsettings.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + "LineNotify": { + "clientId": "Ppu33o7F0c2BTcryJ3PVDQ", + "clientSecret": "cf9ya2A9HA1TzWeXr7GF0ixqCC6vYtIb0Yq8KkOMSwj", + "redirectUri": "https://localhost:5001/AuthorizeCode", + "state": "NO_STATE" + } +} diff --git a/Line/Lab.LineNotify/Lab.LineNotify.sln b/Line/Lab.LineNotify/Lab.LineNotify.sln new file mode 100644 index 00000000..114055d7 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineNotify.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.LineNotify.Service", "Lab.LineNotify.Service\Lab.LineNotify.Service.csproj", "{AFD89464-B981-4C9D-8336-5E2A9A8A0F60}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.LineBot.SDK", "Lab.LineBot.SDK\Lab.LineBot.SDK.csproj", "{0EA7AE1F-ECF9-4BC8-BA95-CA2F8FA1E95F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AFD89464-B981-4C9D-8336-5E2A9A8A0F60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFD89464-B981-4C9D-8336-5E2A9A8A0F60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFD89464-B981-4C9D-8336-5E2A9A8A0F60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFD89464-B981-4C9D-8336-5E2A9A8A0F60}.Release|Any CPU.Build.0 = Release|Any CPU + {0EA7AE1F-ECF9-4BC8-BA95-CA2F8FA1E95F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0EA7AE1F-ECF9-4BC8-BA95-CA2F8FA1E95F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0EA7AE1F-ECF9-4BC8-BA95-CA2F8FA1E95F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0EA7AE1F-ECF9-4BC8-BA95-CA2F8FA1E95F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From b038c53ec2f782d4d3cf9f279e1091998e6ea8e3 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Jul 2021 14:34:55 +0800 Subject: [PATCH 109/424] refactor: use sockethandler --- .../Lab.LineBot.SDK/LineNotifyProvider.cs | 90 ++++++++++++++----- .../Controllers/AuthorizeController.cs | 2 +- 2 files changed, 69 insertions(+), 23 deletions(-) diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs index 0b59d0df..656bfe7d 100644 --- a/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs @@ -14,30 +14,67 @@ namespace Lab.LineBot.SDK { public class LineNotifyProvider : ILineNotifyProvider { - private static readonly Lazy s_oAuth2ClientLazy = - new Lazy(() => new HttpClient + private static readonly string OAuth2Endpoint = "https://notify-bot.line.me/"; + private static readonly string ApiEndpoint = "https://notify-api.line.me/"; + + private static readonly Lazy s_oauthSocketsHandlerLazy = + new(() => + new SocketsHttpHandler + { + PooledConnectionLifetime = TimeSpan.FromMinutes(10), + PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5), + MaxConnectionsPerServer = 10 + }); + + private static readonly Lazy s_apiSocketsHandlerLazy = + new(() => + new SocketsHttpHandler + { + PooledConnectionLifetime = TimeSpan.FromMinutes(10), + PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5), + MaxConnectionsPerServer = 10 + }); + + internal SocketsHttpHandler OAuth2SocketsHandler + { + get { - BaseAddress = new Uri(OAuth2Endpoint) - }); + if (this._oAuth2SocketsHandler == null) + { + return s_oauthSocketsHandlerLazy.Value; + } + + return this._oAuth2SocketsHandler; + } + set => this._oAuth2SocketsHandler = value; + } - private static readonly Lazy s_apiClientLazy = - new Lazy(() => new HttpClient + internal SocketsHttpHandler ApiSocketsHandler + { + get { - BaseAddress = new Uri(ApiEndpoint) - }); + if (this._apiSocketsHandler == null) + { + return s_apiSocketsHandlerLazy.Value; + } - private static readonly string OAuth2Endpoint = "https://notify-bot.line.me/"; - private static readonly string ApiEndpoint = "https://notify-api.line.me/"; + return this._apiSocketsHandler; + } + set => this._apiSocketsHandler = value; + } public bool IsThrowInternalError { get; set; } = false; - public HttpClient OAuth2Client + internal HttpClient OAuth2Client { get { if (this._oAuth2Client == null) { - return s_oAuth2ClientLazy.Value; + return new HttpClient(this.OAuth2SocketsHandler) + { + BaseAddress = new Uri(OAuth2Endpoint) + }; } return this._oAuth2Client; @@ -45,13 +82,16 @@ public HttpClient OAuth2Client set => this._oAuth2Client = value; } - public HttpClient ApiClient + internal HttpClient ApiClient { get { if (this._apiClient == null) { - return s_apiClientLazy.Value; + return new HttpClient(this.ApiSocketsHandler) + { + BaseAddress = new Uri(OAuth2Endpoint) + }; } return this._apiClient; @@ -59,9 +99,10 @@ public HttpClient ApiClient set => this._apiClient = value; } - private HttpClient _apiClient; - - private HttpClient _oAuth2Client; + private HttpClient _apiClient; + private SocketsHttpHandler _apiSocketsHandler; + private HttpClient _oAuth2Client; + private SocketsHttpHandler _oAuth2SocketsHandler; public async Task NotifyAsync(NotifyWithImageRequest request, CancellationToken cancelToken) @@ -83,7 +124,8 @@ public async Task NotifyAsync(NotifyWithImageRequest request, Content = formDataContent }; - var response = await this.ApiClient.SendAsync(httpRequest, cancelToken); + var client = this.ApiClient; + var response = await client.SendAsync(httpRequest, cancelToken); if (response.StatusCode != HttpStatusCode.OK) { @@ -114,7 +156,8 @@ public async Task NotifyAsync(NotifyWithStickerRequest request, }), }; - var response = await this.ApiClient.SendAsync(httpRequest, cancelToken); + var client = this.ApiClient; + var response = await client.SendAsync(httpRequest, cancelToken); if (response.StatusCode != HttpStatusCode.OK) { @@ -143,7 +186,8 @@ public async Task GetAccessTokenInfoAsync(string a Content = new FormUrlEncodedContent(new Dictionary()), }; - var response = await this.ApiClient.SendAsync(httpRequest, cancelToken); + var client = this.ApiClient; + var response = await client.SendAsync(httpRequest, cancelToken); if (response.StatusCode != HttpStatusCode.OK) { @@ -196,7 +240,8 @@ public async Task GetAccessTokenAsync(TokenRequest request, {"client_secret", request.ClientSecret}, }); - var response = await this.OAuth2Client.PostAsync(url, content, cancelToken); + var client = this.OAuth2Client; + var response = await client.PostAsync(url, content, cancelToken); string result = null; if (response.StatusCode != HttpStatusCode.OK) @@ -225,7 +270,8 @@ public async Task RevokeAsync(string accessToken, CancellationT Content = new FormUrlEncodedContent(new Dictionary()), }; - var response = await this.ApiClient.SendAsync(httpRequest, cancelToken); + var client = this.ApiClient; + var response = await client.SendAsync(httpRequest, cancelToken); if (response.StatusCode != HttpStatusCode.OK) { diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service/Controllers/AuthorizeController.cs b/Line/Lab.LineNotify/Lab.LineNotify.Service/Controllers/AuthorizeController.cs index 39a3c996..590b16e8 100644 --- a/Line/Lab.LineNotify/Lab.LineNotify.Service/Controllers/AuthorizeController.cs +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service/Controllers/AuthorizeController.cs @@ -54,7 +54,7 @@ public async Task Post([FromForm] IFormCollection data, Cancellat }; var accessToken = await lineNotifyProvider.GetAccessTokenAsync(request, cancelToken); - //TODO:不應該回傳 Access Token + //TODO:應該記錄在你的 DB 或是其它地方,不應該回傳 Access Token return this.Ok(accessToken); } } From 62210f0399e4addeb04202fc2f157110583961ab Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Jul 2021 14:37:14 +0800 Subject: [PATCH 110/424] refactor --- .../Lab.LineBot.SDK/LineNotifyProvider.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs index 656bfe7d..188da851 100644 --- a/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs @@ -124,8 +124,8 @@ public async Task NotifyAsync(NotifyWithImageRequest request, Content = formDataContent }; - var client = this.ApiClient; - var response = await client.SendAsync(httpRequest, cancelToken); + using var client = this.ApiClient; + var response = await client.SendAsync(httpRequest, cancelToken); if (response.StatusCode != HttpStatusCode.OK) { @@ -156,8 +156,8 @@ public async Task NotifyAsync(NotifyWithStickerRequest request, }), }; - var client = this.ApiClient; - var response = await client.SendAsync(httpRequest, cancelToken); + using var client = this.ApiClient; + var response = await client.SendAsync(httpRequest, cancelToken); if (response.StatusCode != HttpStatusCode.OK) { @@ -186,8 +186,8 @@ public async Task GetAccessTokenInfoAsync(string a Content = new FormUrlEncodedContent(new Dictionary()), }; - var client = this.ApiClient; - var response = await client.SendAsync(httpRequest, cancelToken); + using var client = this.ApiClient; + var response = await client.SendAsync(httpRequest, cancelToken); if (response.StatusCode != HttpStatusCode.OK) { @@ -240,9 +240,9 @@ public async Task GetAccessTokenAsync(TokenRequest request, {"client_secret", request.ClientSecret}, }); - var client = this.OAuth2Client; - var response = await client.PostAsync(url, content, cancelToken); - string result = null; + using var client = this.OAuth2Client; + var response = await client.PostAsync(url, content, cancelToken); + string result = null; if (response.StatusCode != HttpStatusCode.OK) { @@ -270,8 +270,8 @@ public async Task RevokeAsync(string accessToken, CancellationT Content = new FormUrlEncodedContent(new Dictionary()), }; - var client = this.ApiClient; - var response = await client.SendAsync(httpRequest, cancelToken); + using var client = this.ApiClient; + var response = await client.SendAsync(httpRequest, cancelToken); if (response.StatusCode != HttpStatusCode.OK) { From f6e34d7df45d1a6c7cae78dba7263d0995ee7377 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Jul 2021 14:49:16 +0800 Subject: [PATCH 111/424] refactor --- .../Lab.LineBot.SDK/LineNotifyProvider.cs | 93 +++++-------------- 1 file changed, 21 insertions(+), 72 deletions(-) diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs index 188da851..c23a2a6a 100644 --- a/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs @@ -35,75 +35,8 @@ public class LineNotifyProvider : ILineNotifyProvider MaxConnectionsPerServer = 10 }); - internal SocketsHttpHandler OAuth2SocketsHandler - { - get - { - if (this._oAuth2SocketsHandler == null) - { - return s_oauthSocketsHandlerLazy.Value; - } - - return this._oAuth2SocketsHandler; - } - set => this._oAuth2SocketsHandler = value; - } - - internal SocketsHttpHandler ApiSocketsHandler - { - get - { - if (this._apiSocketsHandler == null) - { - return s_apiSocketsHandlerLazy.Value; - } - - return this._apiSocketsHandler; - } - set => this._apiSocketsHandler = value; - } - public bool IsThrowInternalError { get; set; } = false; - internal HttpClient OAuth2Client - { - get - { - if (this._oAuth2Client == null) - { - return new HttpClient(this.OAuth2SocketsHandler) - { - BaseAddress = new Uri(OAuth2Endpoint) - }; - } - - return this._oAuth2Client; - } - set => this._oAuth2Client = value; - } - - internal HttpClient ApiClient - { - get - { - if (this._apiClient == null) - { - return new HttpClient(this.ApiSocketsHandler) - { - BaseAddress = new Uri(OAuth2Endpoint) - }; - } - - return this._apiClient; - } - set => this._apiClient = value; - } - - private HttpClient _apiClient; - private SocketsHttpHandler _apiSocketsHandler; - private HttpClient _oAuth2Client; - private SocketsHttpHandler _oAuth2SocketsHandler; - public async Task NotifyAsync(NotifyWithImageRequest request, CancellationToken cancelToken) { @@ -124,7 +57,7 @@ public async Task NotifyAsync(NotifyWithImageRequest request, Content = formDataContent }; - using var client = this.ApiClient; + using var client = this.CreateApiClient(); var response = await client.SendAsync(httpRequest, cancelToken); if (response.StatusCode != HttpStatusCode.OK) @@ -156,7 +89,7 @@ public async Task NotifyAsync(NotifyWithStickerRequest request, }), }; - using var client = this.ApiClient; + using var client = this.CreateApiClient(); var response = await client.SendAsync(httpRequest, cancelToken); if (response.StatusCode != HttpStatusCode.OK) @@ -186,7 +119,7 @@ public async Task GetAccessTokenInfoAsync(string a Content = new FormUrlEncodedContent(new Dictionary()), }; - using var client = this.ApiClient; + using var client = this.CreateApiClient(); var response = await client.SendAsync(httpRequest, cancelToken); if (response.StatusCode != HttpStatusCode.OK) @@ -240,7 +173,7 @@ public async Task GetAccessTokenAsync(TokenRequest request, {"client_secret", request.ClientSecret}, }); - using var client = this.OAuth2Client; + using var client = this.CreateOAuth2Client(); var response = await client.PostAsync(url, content, cancelToken); string result = null; @@ -270,7 +203,7 @@ public async Task RevokeAsync(string accessToken, CancellationT Content = new FormUrlEncodedContent(new Dictionary()), }; - using var client = this.ApiClient; + using var client = this.CreateApiClient(); var response = await client.SendAsync(httpRequest, cancelToken); if (response.StatusCode != HttpStatusCode.OK) @@ -285,6 +218,22 @@ public async Task RevokeAsync(string accessToken, CancellationT return await response.Content.ReadAsAsync(cancelToken); } + private HttpClient CreateApiClient() + { + return new(s_apiSocketsHandlerLazy.Value) + { + BaseAddress = new Uri(OAuth2Endpoint) + }; + } + + private HttpClient CreateOAuth2Client() + { + return new(s_oauthSocketsHandlerLazy.Value) + { + BaseAddress = new Uri(OAuth2Endpoint) + }; + } + private static T GetValue(HttpResponseMessage response, string key) { var result = default(T); From 8720295ca22921952e8db902cf3b28c254b9529d Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Jul 2021 18:44:18 +0800 Subject: [PATCH 112/424] add test case --- .../Lab.LineBot.SDK/LineNotifyProvider.cs | 13 ++++-- .../Lab.LineNotify.Service.TestProject/1.jpg | Bin 0 -> 150689 bytes .../Lab.LineNotify.Service.TestProject.csproj | 26 +++++++++++ .../LineNotifyProviderTests.cs | 42 ++++++++++++++++++ Line/Lab.LineNotify/Lab.LineNotify.sln | 6 +++ 5 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/1.jpg create mode 100644 Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/Lab.LineNotify.Service.TestProject.csproj create mode 100644 Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/LineNotifyProviderTests.cs diff --git a/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs b/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs index c23a2a6a..503010ff 100644 --- a/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs +++ b/Line/Lab.LineNotify/Lab.LineBot.SDK/LineNotifyProvider.cs @@ -64,7 +64,7 @@ public async Task NotifyAsync(NotifyWithImageRequest request, { if (this.IsThrowInternalError) { - var error = await response.Content.ReadAsStringAsync(); + var error = await response.Content.ReadAsStringAsync(cancelToken); throw new LineNotifyProviderException(error); } } @@ -94,11 +94,16 @@ public async Task NotifyAsync(NotifyWithStickerRequest request, if (response.StatusCode != HttpStatusCode.OK) { + var error = await response.Content.ReadAsStringAsync(cancelToken); if (this.IsThrowInternalError) { - var error = await response.Content.ReadAsStringAsync(); throw new LineNotifyProviderException(error); } + + return new GenericResponse + { + Message = error, + }; } return await response.Content.ReadAsAsync(cancelToken); @@ -126,7 +131,7 @@ public async Task GetAccessTokenInfoAsync(string a { if (this.IsThrowInternalError) { - var error = await response.Content.ReadAsStringAsync(); + var error = await response.Content.ReadAsStringAsync(cancelToken); throw new LineNotifyProviderException(error); } } @@ -222,7 +227,7 @@ private HttpClient CreateApiClient() { return new(s_apiSocketsHandlerLazy.Value) { - BaseAddress = new Uri(OAuth2Endpoint) + BaseAddress = new Uri(ApiEndpoint) }; } diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/1.jpg b/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8852efa4039cb6a1c4b9800f02184453c8ad3db7 GIT binary patch literal 150689 zcmeFYcUV*Hw1EK*? zP*MO-z<e9bQIsQtK4FD806n{RzKOYK8Dr%Zjv~=_gj1&OX-^alJ z|33QP96S$DQczJ*Qd7~;P*a0q0>NVdH4_cFQjCcS-^oSl=KmtRm=R9snw!B*pHYU@6K`P$Oj_N~36 zx37O-aOmeSVPbM>dS-TReqnKaV{>bJhx~hYj|qfKNli^fO?v{Fg3<^4QZZ4}+_-&; zSyi9*q5B0c@xSO;)MBzee4^*RW3a~h$fJkhB9Fub@A?VqKcN5DFo*nq0{w58{|)l- zM}UcrVw7rtiULH>L_x(waZCVCQ&E6_P%#0@z&^80Bj^6yF`(WEK#5HlKpFNm=(Y;g zVc%9JtseEo=0(H9mLs)bUfp;=Y+Jc2n1-}{6cWs3vyDHR43g!iw3$5M2b7-#p4-X< zwmEI)ohXH(>y*c%j{yx40D8zz0nggI0$BG0uYUy60C!!Fmo`PNK&)8_skKqG8u zj{%?^Q&v56qP%$wST?UJZ$<_4Dr!!tP@(sZfx9YHLHY;tltLBTVnM%}sx#)>&%~0SP;VNg0T##4Lb3lIi z{W0LKOo};Tp*VxZXz=i!k9~c>{lj06IvjOYX)8+=6IPC+$ku(fELJb)2(PMOR5 zXnyNjKgE?1_1X77-~P<_tY||B=Gm8!Z<_Qvnm+~@fHN3bPA)Iy&x(zOfYgr=rF%<< zJ@bKul(H+?47Xk?c^xqwZtrB$Um=|TXABC9{|ZWM_+i^9Y=0WW$2D^IuMIxfYV=wr zU1s&mq7J_Cpb5@#S7#swl{Gi5+f3w~PywpL_)a#jJtG8Dpw7a+rO~cM2RQ?gW!alX zkMBPYnz&Z9LQ-L7ck)oWM>;ss2(01AZ+62Kf4np9v3T{&SEOPW3D{-azBY-s z&l{y|ijEqSWu0K?su~`Jb5y$0b%;KES;KqTUK-GpnJmW(Z8hRd>#9b)n~_Q>;$pZo zLLf((zhYfQzM9W{Oqv z>N7`|>c)l?gtA7T>1y(PTh}0Z|6Y@kg9ag`T6C7dvUrY7-Z+Jd&-ugr8FuT#3$E=V z4~yg=9GEn(P<~%!HC9M_`B)W`OnR+R^*jwD2;PBtMeK6QQ-~05 zKC$9k%!6lC)4CZaH}VU0SB41jn?ASqbFv<3x~lQBX>{xNR^-PL2I1zj!7;+_4Z7=Z z(3AHJ%;~O2+`1Yf_XBI$Gbra=J-9Q3db~3KX}=<2o?O*4qJ!GB@mqCzd7W+d!qM)v z45-w=Uo4zEw+N>eQayGMS=N;bu7Ba7NgGA+RVq6*cR9O!+4?G@T0Hyta~%_A4?-wTVGu1=E5*$^(nBcGy6u~btBR(u z6w^(e0tZYW`}OK|&M;Cw90~8FFYr)nxw=E*K2Lu`-|WM6N-e5PJ}ghg+R~8jVB^tB zAg!1u#Hp{UDbWXuT?!N#zx$BzU8{Po!$!Cc=tbZ(BH8+s7{d<3s4fSX2i&yo49t=Pr(jXpDhIE5%3a4!;!II9;qMG0pa7n z!B`6n{)qS&FmU@s#lPpUs9C~|3vXW1X&f<~%qxb-v**731BRv)^w2R70L~fI+D>jt z_x@J*|2poUDeM2marA;f&-#JmF`(iHB)mE8J{EjRg%$>zQ(>~~JX5M8{QW~VrS|QQ z!0A}X7LW{ll)mIwAT?ZBt;l14YEpGErbd7vz1ZH=L+)!TGcd5W9Ru8dF&%Zsjdbha zqT08dVPn%DdQ=*J|Ci{@m2OH`wj?Lkhi!v(hWc znU5BNmo}hzT!t6tEJs zc1`SvEBM@RO^N2+P3$r7@k#UVSAg>W9N)i_Ks7<+_jtSlaBh7D1x5zjS)7d)UyT~X z4-GsFjs7mmPjL*aMInMV=a9!h@OscX3kCbUg@TjN=8}i>iMTHg0Iw_jat@6J?^|I&HsW~!_N-&t^0A$EW4BP z|7qp^_s#QvL;_~!7%+8Z;oJXF5j*}&{X^KC3hl6~uq@`ysD|=PNBJ(Lkf0k=MpAj8HbH>^4l7a*)n>Dd~{C89!(T({^K7AEQRB{oeb z7wU&p#Tna?82zgFW1#VJfa&11o!1jMl1Nudy^fH_QiFE_zu2pdgfbK#zlKYDfKyJnDIZY!Qes-3H)$jbRShxL+&{Ajj<_}Ml7Ekzw?Vo|LS7SEU6xN>=g zw=^S3M`ZoZJF$s2#|~mj6(&K+WC~+wQGhAkQ)fGOs)j3hUF2!^%_3KHeO9_dEyO-D zw-PbAFQG=|QYbcvA)@l9ct{Pmq9VH3}kAL?_)M^dvepQuBvDJBlOB8YpmWZsCabHZ|Rv6ZQXz1k2deM~Yiy?ws) zQrB&sN;#Uj*_#65djs0!E-7-NfZBMcpxIM(mvF3MqKKH^f@t7bKasr4_{JO0J;tY^ zxB~VL^Rp*s9JcgbwesKPEf5lu3gDkJ2#kzo2$a{Wp))-Gc0%aHrSEUPy?hafHz*tTpOVebA#Kf0JsTS?5)<0bWw~paDx5J6MclcRmEeiaOX})_4O44;i(r|FX(UJAXij5H%L0EnKMsaoO@d(TVQYRaBP*nMCC=5SWM-x=taiAzmQY)tY^5ZxbMx~P*U}Z9NDDX`cQNWZ zKQldVgsNLNS22HxW@zE2g-#X~$+)P?*yS+AdBsYn&-?le4adz4u6T^~au!SD+yz0% z=i3EBo@H@^R~N-CH)8t|Nmetw(s{M19!V=o&dW!GM=hJ0V@E&sT~ns7)gJ?WNBG$( z`DX9g?Zei^Rv$z`WAVPc#rVctTuQ+qHGITtX7(6Jx>r_5=s(OO1?~E;$fC-}O^;6D z=4Qx^Sqb@sCA}cWuNE8q>Zk`?ks>a+1{yTIy0-PWs_@{;@izCSHB_r_s$#sX{1MU) zwS&op`(pMSu0pK`M?(|V1Cs1TVh)kcf%z3BrK`*17;k5Z6-H^V1uV(`QrWI;*G$C} zQBH`ce)doTnrPtec8xvsjDi5$zMc5H?6a#TXWlU5=hX5dzP5Fhr{;0&E*wbfVEX$5 zM>-5*pJ9x12g*uaw$N_+iQOswlCF;WQ0wjNEcUjv`obo2#dvrCtz(hdq6hoIXTD%u zlH&Ht*WO=km3G>N3Fz#HyzHTC^^-=CJg<1i1T^kMr`iqG&9L!y>?T(Rtj~}qo=C$I zq>+ZRJ{}P}Ch<>ub)=?C)A5q>YSw$9<;%NyraqP->!KC;IQU=eZTTe)6kj7LyWMxP zsIHIMo0+ZCZ?;a%JYCCkWao(|b*{`3EBxhH;;*HX#jp@anQ(6UEA@^8eCyE8MN`62 zZw?YC&Fvzv@O>(ftpkE6YMhq7EwcXMmC!`uC9d^SJpMIZXvI++#}`z>By&(6f04SU~S=fj)G_A_LOiN(K`PqsD?ZoWy`x zpDR(PEvEHqWyFR+9KV}jTBbg+G(A3)4ZzSw6H%sA_0(Ubo#w?buXU-X} z4U`TVj(uElTcdyEbmnVphuaSgJ9?g$)Pj_nl~Lo%m4*5nFJVN)VRBrAe~rhdR+6uO zZ?b`p%OL7!yA3hWN=7y)vLib&MSY{tT7U5LinT1jf2460`-8Z!RgIfPOk&m-g zd5b!eUhQ-`Kk)NT-}@sB@R)4@Osv6Dq7g;T)bH-2;4KQF>zAh*fz!cf(=)ojB#&Y; znB%=4STFsi5I^<%(k=s#pl6#0Fg)w4J@Nts&psg?(g61g6=tV?U)XI|VP0liAOurB zzUQgW8miGDrtD`OQkDE z!4A(9QW~pvbIb#D;!lnN85V$=O~Eh70tkQO+u64Er+&&$ancg>F^AQ61}RN z2B>=ks~rSUozzfSE-UB(=Rts6nI9*?IXWu6xht3}{M3TX(d}a(lNW%Vw_8^^^(j3q zY&(L#o4@H% z7eC|R2aK+zb#G=;=y|pU&GS?8s?JGse$42C0!g62et?DTs^5uG830Yrxl_ShH7YFb z2kytfJW_?Z5|Gmaz5xp8Oq!oy62YYRz?~4xmGIPQ?{E`LC*0>LO{~-W_L2cMH~F2_ zKqsnCN&@CTpJY!G*d&pGXXkY4G%I5fZ`krpjZ;g-wbCEFzGm>iQ~GTL+Kw$h2^X5> zC^IbMGV16rYD0obHDaOK)={pB5&c`wbT*~!FLAhxI~Dj8WBco_v9I%(1-V#_A@-hT z6-J6{T=^!ZAhq0;b@03VL48owF9AZN^97ntmmBVp0_qyZEGYG^TyAt0T)r;+&-EUtS+Lc_{XE<4Np@!#h!I;W6-NMC3bCDEBtTsUoD3 zKm1k69SvRbX;YyP-&+mcac8^=!i|O6;S*jNV#0ewfsOJ-fu=AMuJ(@X`X~+!#;Up3 zDTv1;N1;Z848b5~tEY{wlPiWTaz4*z2WQ^bCf!G@@>Q8^Sl>&YeqqMC(t6+dC9E^D zcDKZg*T8rbu3vD)rP~Sx6|b)26Oa|F>)5l`uQQq#;F&QgGb19L3Ts>UoO`3iy2$hP z2ql3b37fOOGp(I{r0*=4elTxCFU)Bb+BTYtJ~Oj4BQ4_HdlD|;K4 z$aD~zbS+RN3zd!4jRakV(VpJ(@wdH;`0(vv1qK51@yR7ZBi22z{BTU~On`N4(h(;@ zBiepSK=`y?CyFHqQF+coYyV!{E+-;9JLahu|ML_^cK3HMsLR=2lj*{Et|9SAN%Rzd zy;ad+{1hAC%~5z|YfrK+Y5bWtFUQsVBQ_0Fv}K*urldVG#!A~ie_Z;d*rk^03`A|B zj>(hd>Ap``1$eFp@8H1gtEcay9|jvpO{|59uw>ob6Y|cLmZaVI`31(!* zaJ36Zi5*|?3GaH|I?|ST)uC)1!wLs-m!Ot)8shnx@ej(JJ}s`e)R~3xZu=TXt@YTH z5j93g4yD-N2Ab)=7X^g9tz3u>gCvc2jV?^&XUJ~99zdA&f z;+CfFq`kxxRr~{Ld?nt12-SunT~J-M>U>c70u2vDfKI{9jNi69b%nW6ITO^CZIfkk zw-1Z1SUTlT8u#-E$KC%)RD3`7aYXu{(t)CB2iaE{v3?8M^9p)9sf}~Yus4vU@htrT z3tz>ZGJUD0Q#raO+WS4t%}eZ8Z$_7>d5_$xd%o`+IcfY#+awWI`C1I~P>bEovgv%q zTaE5r$FZ7?alb^41Jh6a37R4ng@x^{L*qnp%D|>Koc&RGUU*YtVZX?r+@=j)TfI6> zF6D|zx|l>e7Sf6>9Bs<+cj0>9eEVghVkDE(@YT>q-}C;Oa-z_E^7)d{tOc&%&mrm1vk`(wCoQ2kLI&g@KjY zHCZ+NQ`Iq_Bl9^GzyFoD0kMCX@I~B}n+B%tbGhy!IrVyfzje8&i-EfhqIErTySjQ7 z4HIOrS>fO|K*psQv#T)(G=+CC`-`HUvWzBi{90lwm(bqNIJbE5jn!gH*%*POe^)du zKWv0&ql$)?SY4vUM5!?mV?Ii4{OB+O(Oy8W9Lr)gTC+>jSY%<0I%^JMmx zF1ASWi#h7b6WhO}XAdiBd#a7_-pnQFC*7V%DN{m5Drl}b8@#w1_geyu$uCO8%iq%_F7aE;A zbQz4sYvOFax|&vZ){T0_UiXNIiAW*Lu=!WF~eLHT^KnFJhXcmgnp`7OAC1(rS^Gl$9s7cLw&N z5oCyfMx&ge_4GoQUw}@_Cp96QB4Kz$bNT=YS*H!(g8Qc|X`xk2bG(gSm)FakHg)mv0mn{en6W*A&#vNf zy@&)t(hFnY+>7qBimNj-^qDnv>2*F78zeqX!KN4GS8I_<-u~nVGAV-@fPeQx#jTHf z^3#S8;&Po+T9^BDG7rwADc!5Be&y6P9V=mfJzZ5#9k4K-YMylh zjQ6$`yUPHEM!)TCC9oJ_e7NboA3nO*u5w|81wdVk|KIv%QuGF245qxJ!c@FI-t4)g z!gQ}$BJJt#)u5Vq3Vv&t&G#P^xLMKp zO90}(d{##HegK`peqe8#1GluB41^2^d89p^B!i%N|7WT&uYj^aIjqy(q|tpnz?9ni zpgSv*uKdyr$J2wrZt`F-ul+|7DAmN0YN((nlggFj7`UTCSwU3+GgLW0QqkdexGSGQ z!UE8ep$Z#A72pIr*ruo4@B@>rdCHqM<6<^7hux)K6av%KD$VUL zff?v8A5f~0D8*7=Kj^loW8hsTh3Q`>_M)@5Nmhc5D`kv={n3P^0=^2|Z#^0jlevAR zE@V0UTlh3pe|93(IyVc&W=0zaSL>I%efepRq~F_^e~oME5uoYp#zvjx_$&6ld%BK} zm=D`?_nwYYqm@A>17ynnhj>g=cxOHL`PBDM@{okkT~ma10%EoohmM)Pc5CfBp(|&S z2o*vyzFg)xyJ^|(fjj?<9yM5UZq+JkG(&mjo27g5RBr99j-1}##p`vyE4EY23kt*< z64SR+>$&rFbn8O~fBJb;{YEXP7nS*9wtAK9ZNd`W?@m^nRd+q}O7{a>xzblxi@-{A zT8Nm^j}TV0>6xPl3xz6AoI_w}tvhRMkE-!&{bnAExo42hvEOAJJ`H|VQGN!IM|>t1 zo4@uZ`@xAb)s4#yZv)KM3c@hTOfAT~5i1*CF6~2mu7r1km%MGCg>VTz&vgM9)~AC# z6qe;8_KXW-!JP(|Wb|@r9)AJn)IdtS7uwx4&gAGYKW?BcK_aa; z&?d~~Ky0Go3gXfg<9;`%0BB$E?8km^p6nuvKusv zD4sc}PqhDjcaJm1k=Gu7TKHGS`eJRx%;DFZ3fIbtsyYN_V88p>rQ24-C&iP)gJyaC zxlg7epK+J?&X}7+Xtb_}oTZyK<%x^3Vp&bzInsH$8q?z~+^QonRP`BEJXRbu9kBoF z7&vdSNNXkb2c4 zEBwoOAxou%Y$CzEh=!5Rw@cx@4;m+&@f1ho5`t*Nlk(yM@Xg*lB%E%&jgGV;)y}gX z2~OOC-19Z{bpIuP|JFM)$`7r+??zfa)~}g0GJfjboadoGZT6fkfH@KZ3yRc8eOO(h zo^8_X;B7SP#ml>{(G!-l6Z;f4Mw<5}eHA(*DpTR=#qP?Smph2=Y4da`ty36W8~OUi zw|srK@^c~OheWji{OF*!WpezYxm<#+fpu@Yv|v$wY^6cKP+net9;aHIPK&0smaf1T z)15q?u{r^%0+jGOLI7*u29XMO&^YoP_6>6W~xwq(MywYVHeLi8qD;{o9yOGnq1zRzH zk>I6++u?0{J*Wk2D)qaZE)X8p=Zu>PIdyjBP}AZI)g|4_7T!wyzmSC^xyG_Z0zv*6 zNQ7;GPDSKQQ^JbSb`tO8lxtPTGFUdAelUU{=q5atJoMDj(T1fM^rIZ?k{5-ZzcYe7 zrcLr>(Gd4>BtLAuoTSV(#3APKoGy=K^jW|#P%+nGa(CO$qcE?d8a0fqxYw5!aJRW0 zb^%f1T+|^nv1x#+Ci!%-+KpI8I!-`J6&N4p-Fl7M)` zm72s0b=mXpPCv7EbFAWNvV5AP?d6FvR_rCgctOo#{gC*wx`85%cHgnVA=~?dbH5{} z@Zu&oq|GuTn`1+0?3nt%NB#$ngjz4+d1b-0_UnQSZ=^C~69Uu%dZr7YXDmiZ<^=rs zWEtbis!g%$6?~0;_CW5OV5DOI1>LiQ#0}LYq~Po7YK&@qtF`#UZx{7K0*^Xm4$sY4 zd)w?@oIG?sGK?I}TIj=L9y+ zB{$Y7lgwOfcK0<7nihwx7nVePsyE1qVZsv6!9Jkh5ILi*rwl{b*ChI7s3J`*nqLN( zmLg2*bkoYP#RQ%Ui{~wNEdo%058~=qzEt_!_IJ5i2_r@kWp3)$@r(7U`xXrY7W=n7 zpiGUm~iKA`HD|vzWS(^I$*Y zE=TQ?BNL`pkDeWdCpcc-V%X4nB`*lbd;}i| zYq^X*kga4)R)iH)fDI|@hV-T}kmYoe{dqi{^z&<9QMo8i>jy}mv-p2}^)#LTed77g zh!k{S>UP^Ss-XEqB3JlRgsgIa-yiydUPC%;0VD>X=8EPO6%Px6k#55udmuf4+Moi@ z$w$h7ttx|wRcBw^&^ieu!GXT`i6$Yj~;YsAq_WkIEB~I`x)0lSBiO%9vS3%Q;5rR)y`Y3AtF-@(_ zXfxlcbU_x#w0O1Nf0O|-&Hi@C$%ubDW~zv^u(b7eG6);LmN5!*KFJ2-PIPAXUGzJ5 zGN9CE|CUlZ$c11j+0<;`pX=L?26+{gThrS+C*%n>H@Wn&bdZtqjl-19`h zpS%z@Y6DXRPcQx(0cAz)V&DlwL~t9t{Dg(aTtP~SBVXtn1cy#?RfNG@*D`?$?zr(j zkYGkx!yUKh$LS2bns}<;zQ4W~6bM??#PV#xpMlt(DEzS;BQEOi9qQJ!d<7>dA;QF} z=b)Y3rF8eO?HF(g=CMzSeg_g&+>U|u4xn|NtMf2TY1Tk0SVc`nxRIPOqjI(2)e-yr zm}CR4anKg|mm^D$F#MfWjID(0Bt*JEbvuWixKhP$ycde437LbVb|ZPWEa7jtN)k( z8vKh7A))agacRe2-u7|gjq`9;A~(sNv7xnGK0QL<;ww!;?raIU0b>T>-uX2z;66m@0X9EvYSv>@0GJ)B4CaeO{L(=lJ4 zc>a3*1pVDywE}r7yXqDkl8t-mA}jN<<@RaVnEbBdb0=c2#e7+FiXYlyr5qRdI8OuL zDCyIu8;&F{G$osRag-fgkn8mM!TK6HrmU#mB}`^#4~a~ih>)_rK+

Iy%kR&p7S$A_r-!!9PoVOP4 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

5o}K~le6j}}>pvh$$iHU+ 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 J8v4IR{uh)3jt>9; literal 0 HcmV?d00001 diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/Lab.LineNotify.Service.TestProject.csproj b/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/Lab.LineNotify.Service.TestProject.csproj new file mode 100644 index 00000000..4e97586f --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/Lab.LineNotify.Service.TestProject.csproj @@ -0,0 +1,26 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + + + Always + + + + diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/LineNotifyProviderTests.cs b/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/LineNotifyProviderTests.cs new file mode 100644 index 00000000..da5eee85 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/LineNotifyProviderTests.cs @@ -0,0 +1,42 @@ +using System.IO; +using System.Threading; +using Lab.LineBot.SDK; +using Lab.LineBot.SDK.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.LineNotify.Service.TestProject +{ + [TestClass] + public class LineNotifyProviderTests + { + [TestMethod] + public void 發送訊息和表情() + { + var provider = new LineNotifyProvider(); + var response = provider.NotifyAsync(new NotifyWithStickerRequest + { + AccessToken = "3lZwryen62tiQ4BKfh3uH3NFoFtALF4SrfgLWMIKrXh", + Message = "HI~請給我黃金", + StickerPackageId = 1.ToString(), + StickerId = 113.ToString() + }, CancellationToken.None) + .Result; + Assert.AreEqual(200, response.Status); + } + + [TestMethod] + public void 發送訊息和圖片() + { + var provider = new LineNotifyProvider(); + var response = provider.NotifyAsync(new NotifyWithImageRequest + { + AccessToken = "3lZwryen62tiQ4BKfh3uH3NFoFtALF4SrfgLWMIKrXh", + Message = "HI~請給我黃金", + FilePath = "1.jpg", + FileBytes = File.ReadAllBytes("1.jpg") + }, CancellationToken.None) + .Result; + Assert.AreEqual(200, response.Status); + } + } +} \ No newline at end of file diff --git a/Line/Lab.LineNotify/Lab.LineNotify.sln b/Line/Lab.LineNotify/Lab.LineNotify.sln index 114055d7..461679d2 100644 --- a/Line/Lab.LineNotify/Lab.LineNotify.sln +++ b/Line/Lab.LineNotify/Lab.LineNotify.sln @@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.LineNotify.Service", "L EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.LineBot.SDK", "Lab.LineBot.SDK\Lab.LineBot.SDK.csproj", "{0EA7AE1F-ECF9-4BC8-BA95-CA2F8FA1E95F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.LineNotify.Service.TestProject", "Lab.LineNotify.Service.TestProject\Lab.LineNotify.Service.TestProject.csproj", "{8355BA66-8285-407B-B8D4-3208E66B2D6B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {0EA7AE1F-ECF9-4BC8-BA95-CA2F8FA1E95F}.Debug|Any CPU.Build.0 = Debug|Any CPU {0EA7AE1F-ECF9-4BC8-BA95-CA2F8FA1E95F}.Release|Any CPU.ActiveCfg = Release|Any CPU {0EA7AE1F-ECF9-4BC8-BA95-CA2F8FA1E95F}.Release|Any CPU.Build.0 = Release|Any CPU + {8355BA66-8285-407B-B8D4-3208E66B2D6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8355BA66-8285-407B-B8D4-3208E66B2D6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8355BA66-8285-407B-B8D4-3208E66B2D6B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8355BA66-8285-407B-B8D4-3208E66B2D6B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From fb1402293b55993ea3ebaac3c23f1235bae71416 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 15 Jul 2021 18:57:47 +0800 Subject: [PATCH 113/424] add test case --- .../LineNotifyProviderTests.cs | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/LineNotifyProviderTests.cs b/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/LineNotifyProviderTests.cs index da5eee85..fd937ee7 100644 --- a/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/LineNotifyProviderTests.cs +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/LineNotifyProviderTests.cs @@ -9,6 +9,16 @@ namespace Lab.LineNotify.Service.TestProject [TestClass] public class LineNotifyProviderTests { + [TestMethod] + public void 取得AccessToken狀態() + { + var provider = new LineNotifyProvider(); + var response = provider + .GetAccessTokenInfoAsync("3lZwryen62tiQ4BKfh3uH3NFoFtALF4SrfgLWMIKrXh", + CancellationToken.None).Result; + Assert.AreEqual(200, response.Status); + } + [TestMethod] public void 發送訊息和表情() { @@ -31,12 +41,22 @@ public void 發送訊息和圖片() var response = provider.NotifyAsync(new NotifyWithImageRequest { AccessToken = "3lZwryen62tiQ4BKfh3uH3NFoFtALF4SrfgLWMIKrXh", - Message = "HI~請給我黃金", - FilePath = "1.jpg", - FileBytes = File.ReadAllBytes("1.jpg") + Message = "HI~請給我黃金", + FilePath = "1.jpg", + FileBytes = File.ReadAllBytes("1.jpg") }, CancellationToken.None) .Result; Assert.AreEqual(200, response.Status); } + + [TestMethod] + public void 註銷AccessToken() + { + var provider = new LineNotifyProvider(); + var response = provider.RevokeAsync("3lZwryen62tiQ4BKfh3uH3NFoFtALF4SrfgLWMIKrXh", + CancellationToken.None) + .Result; + Assert.AreEqual(200, response.Status); + } } } \ No newline at end of file From 8c1c30d61d07d74f6c8b3fef75a5a08a80be53c5 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 19 Aug 2021 22:44:44 +0800 Subject: [PATCH 114/424] refactory --- .../Lab.LineNotify.Service.TestProject/Tests.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/Tests.cs diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/Tests.cs b/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/Tests.cs new file mode 100644 index 00000000..28c3c767 --- /dev/null +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/Tests.cs @@ -0,0 +1,13 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.LineNotify.Service.TestProject +{ + [TestClass] + public class Tests + { + [TestMethod] + public void AAA() + { + } + } +} \ No newline at end of file From 0abe6564b4b22e45cd3cdc11e27c38f1028bc395 Mon Sep 17 00:00:00 2001 From: "yaochang.yu" Date: Thu, 19 Aug 2021 22:51:40 +0800 Subject: [PATCH 115/424] ss --- .../Tests.cs | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/Tests.cs b/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/Tests.cs index 28c3c767..8282edaa 100644 --- a/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/Tests.cs +++ b/Line/Lab.LineNotify/Lab.LineNotify.Service.TestProject/Tests.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Lab.LineNotify.Service.TestProject @@ -5,9 +7,91 @@ namespace Lab.LineNotify.Service.TestProject [TestClass] public class Tests { + private readonly Dictionary> _pool; + + public Tests() + { + this._pool = new Dictionary>(); + this._pool.Add("info", this.GetInfo); + this._pool.Add("status", this.GetStatus); + } + [TestMethod] - public void AAA() + public void GetInfo() + { + var key = "info"; + var response = this.Get(key, "yao", 18); + } + + [TestMethod] + public void GetStatus() + { + var key = "status"; + var response = this.Get(key, "192.168.1.1", 1024); + } + + private TResponse Get(string key, string p1, int? p2) + { + if (this._pool.ContainsKey(key) == false) + { + return default; + } + + var func = this._pool[key]; + return (TResponse) func.Invoke(p1, p2); + } + + private InfoResponse GetInfo(string p1, int? p2) + { + return new InfoResponse + { + Name = p1, + Age = p2 + }; + } + private InfoResponse GetInfo1(string p1, int? p2) + { + return new InfoResponse + { + Name = p1, + }; + } + private StatusResponse GetStatus(string p1, int? p2) { + return new StatusResponse + { + Code = p2.Value, + IpAddress = p1 + }; } } + + internal class Content + { + public string TypeName { get; set; } + } + + internal class StatusResponse + { + public int Code { get; set; } + + public string IpAddress { get; set; } + } + + internal class StatusRequest + { + public int Code { get; set; } + } + + internal class InfoRequest + { + public string Name { get; set; } + } + + internal class InfoResponse + { + public string Name { get; set; } + + public int? Age { get; set; } + } } \ No newline at end of file From a003ff28ca45abd24da4798e2642c7146d9c03a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E5=B0=8F=E7=AB=A0?= Date: Thu, 19 Aug 2021 22:59:54 +0800 Subject: [PATCH 116/424] Delete DI directory --- DI/Lab.MsDI/Lab.MsDI.sln | 49 - .../App_Start/DefaultDependencyResolver.cs | 31 - .../App_Start/DefaultDependencyResolver2.cs | 35 - .../App_Start/DependencyInjectionConfig.cs | 44 - .../Mvc5Net48/App_Start/FilterConfig.cs | 15 - .../Mvc5Net48/App_Start/RouteConfig.cs | 20 - .../App_Start/ServiceProviderExtensions.cs | 27 - .../Controllers/Default1Controller.cs | 28 - .../Controllers/DefaultController.cs | 43 - DI/Lab.MsDI/Mvc5Net48/Global.asax | 1 - DI/Lab.MsDI/Mvc5Net48/Global.asax.cs | 24 - DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute.cs | 40 - DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute2.cs | 30 - DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs | 9 - .../Mvc5Net48/Message/IScopeMessager.cs | 6 - .../Mvc5Net48/Message/ISingleMessager.cs | 6 - .../Mvc5Net48/Message/ITransientMessager.cs | 6 - DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs | 14 - .../Mvc5Net48/Message/MachineMessager.cs | 13 - .../Mvc5Net48/Message/MultiMessager.cs | 13 - DI/Lab.MsDI/Mvc5Net48/Mvc5Net48.csproj | 202 - DI/Lab.MsDI/Mvc5Net48/NLog.config | 45 - DI/Lab.MsDI/Mvc5Net48/NLog.xsd | 3644 ----------------- .../Mvc5Net48/Properties/AssemblyInfo.cs | 35 - .../Mvc5Net48/ServiceScopeHttpModule.cs | 42 - .../Mvc5Net48/Views/Default/Index.cshtml | 18 - .../Mvc5Net48/Views/Default1/Index.cshtml | 18 - DI/Lab.MsDI/Mvc5Net48/Views/web.config | 42 - DI/Lab.MsDI/Mvc5Net48/Web.Debug.config | 30 - DI/Lab.MsDI/Mvc5Net48/Web.Release.config | 31 - DI/Lab.MsDI/Mvc5Net48/Web.config | 51 - DI/Lab.MsDI/Mvc5Net48/packages.config | 16 - .../App_Start/DefaultDependencyResolver.cs | 40 - .../App_Start/DependencyInjectionConfig.cs | 40 - .../App_Start/ServiceProviderExtensions.cs | 27 - .../WebApiNet48/App_Start/WebApiConfig.cs | 23 - .../Controllers/Default1Controller.cs | 27 - .../Controllers/DefaultController.cs | 45 - DI/Lab.MsDI/WebApiNet48/Global.asax | 1 - DI/Lab.MsDI/WebApiNet48/Global.asax.cs | 14 - DI/Lab.MsDI/WebApiNet48/LogFilterAttribute.cs | 28 - DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs | 9 - .../WebApiNet48/Message/IScopeMessager.cs | 6 - .../WebApiNet48/Message/ISingleMessager.cs | 6 - .../WebApiNet48/Message/ITransientMessager.cs | 6 - .../WebApiNet48/Message/LogMessager.cs | 14 - .../WebApiNet48/Message/MachineMessager.cs | 14 - .../WebApiNet48/Message/MultiMessager.cs | 14 - DI/Lab.MsDI/WebApiNet48/NLog.config | 45 - DI/Lab.MsDI/WebApiNet48/NLog.xsd | 3644 ----------------- .../WebApiNet48/Properties/AssemblyInfo.cs | 35 - .../ServiceProviderDependencyResolver.cs | 50 - DI/Lab.MsDI/WebApiNet48/ServiceScopeModule.cs | 42 - DI/Lab.MsDI/WebApiNet48/Web.Debug.config | 30 - DI/Lab.MsDI/WebApiNet48/Web.Release.config | 31 - DI/Lab.MsDI/WebApiNet48/Web.config | 54 - DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj | 186 - .../WebApiNet48.csproj.DotSettings | 2 - DI/Lab.MsDI/WebApiNet48/packages.config | 17 - .../Controllers/Default1Controller.cs | 35 - .../Controllers/DefaultController.cs | 44 - .../WebApiNetCore31/LogFilterAttribute.cs | 27 - .../WebApiNetCore31/Message/IMessager.cs | 9 - .../WebApiNetCore31/Message/IScopeMessager.cs | 6 - .../Message/ISingleMessager.cs | 6 - .../Message/ITransientMessager.cs | 6 - .../WebApiNetCore31/Message/LogMessager.cs | 14 - .../Message/MachineMessager.cs | 13 - .../WebApiNetCore31/Message/MultiMessager.cs | 14 - DI/Lab.MsDI/WebApiNetCore31/Program.cs | 26 - .../Properties/launchSettings.json | 30 - DI/Lab.MsDI/WebApiNetCore31/Startup.cs | 49 - .../WebApiNetCore31/WeatherForecast.cs | 15 - .../WebApiNetCore31/WebApiNetCore31.csproj | 8 - .../WebApiNetCore31.csproj.DotSettings | 2 - DI/Lab.MsDI/WebApiNetCore31/Worker.cs | 22 - .../appsettings.Development.json | 9 - DI/Lab.MsDI/WebApiNetCore31/appsettings.json | 10 - DI/Lab.MsDI/WebApiOwinNet48/App.config | 14 - DI/Lab.MsDI/WebApiOwinNet48/AppSetting.cs | 8 - .../App_Start/DefaultDependencyResolver.cs | 39 - .../App_Start/DependencyInjectionConfig.cs | 34 - .../App_Start/ServiceProviderExtensions.cs | 27 - .../WebApiOwinNet48/App_Start/WebApiConfig.cs | 23 - DI/Lab.MsDI/WebApiOwinNet48/Commander.cs | 15 - .../Controllers/DefaultController.cs | 32 - DI/Lab.MsDI/WebApiOwinNet48/LabMiddleware.cs | 69 - DI/Lab.MsDI/WebApiOwinNet48/Program.cs | 18 - .../Properties/AssemblyInfo.cs | 36 - DI/Lab.MsDI/WebApiOwinNet48/Startup.cs | 19 - .../WebApiOwinNet48/WebApiOwinNet48.csproj | 111 - DI/Lab.MsDI/WebApiOwinNet48/packages.config | 18 - DI/Lab.MsDI/WinFormNet48/App.config | 18 - .../WinFormNet48/DependencyInjectionConfig.cs | 64 - DI/Lab.MsDI/WinFormNet48/Form1.Designer.cs | 61 - DI/Lab.MsDI/WinFormNet48/Form1.cs | 21 - DI/Lab.MsDI/WinFormNet48/Form1.resx | 120 - DI/Lab.MsDI/WinFormNet48/Message/IMessager.cs | 7 - .../WinFormNet48/Message/IScopeMessager.cs | 6 - .../WinFormNet48/Message/ISingleMessager.cs | 6 - .../Message/ITransientMessager.cs | 6 - .../WinFormNet48/Message/LogMessager.cs | 9 - .../WinFormNet48/Message/MachineMessager.cs | 9 - .../WinFormNet48/Message/MultiMessager.cs | 9 - DI/Lab.MsDI/WinFormNet48/Program.cs | 26 - .../WinFormNet48/Properties/AssemblyInfo.cs | 36 - .../Properties/Resources.Designer.cs | 71 - .../WinFormNet48/Properties/Resources.resx | 117 - .../Properties/Settings.Designer.cs | 30 - .../WinFormNet48/Properties/Settings.settings | 7 - DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj | 113 - .../WinFormNet48.csproj.DotSettings | 2 - DI/Lab.MsDI/WinFormNet48/Worker.cs | 25 - DI/Lab.MsDI/WinFormNet48/Workflow.cs | 16 - DI/Lab.MsDI/WinFormNet48/packages.config | 8 - DI/Lab.MsDIForAutofac/Lab.MsDIForAutofac.sln | 31 - .../App_Start/DefaultDependencyResolver.cs | 39 - .../App_Start/DependencyInjectionConfig.cs | 67 - .../App_Start/ServiceProviderExtensions.cs | 20 - .../WebApiNet48/App_Start/WebApiConfig.cs | 25 - .../Controllers/DefaultController.cs | 36 - DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax | 1 - .../WebApiNet48/Global.asax.cs | 17 - .../WebApiNet48/Message/IMessager.cs | 7 - .../WebApiNet48/Message/LogMessager.cs | 9 - .../WebApiNet48/Properties/AssemblyInfo.cs | 35 - .../ServiceCollectionExtensions.cs | 26 - .../WebApiNet48/Web.Debug.config | 30 - .../WebApiNet48/Web.Release.config | 31 - DI/Lab.MsDIForAutofac/WebApiNet48/Web.config | 58 - .../WebApiNet48/WebApiNet48.csproj | 184 - .../WebApiNet48.csproj.DotSettings | 2 - .../WebApiNet48/packages.config | 20 - .../Controllers/DefaultController.cs | 30 - .../WebApiNetCore31/Message/IMessager.cs | 7 - .../WebApiNetCore31/Message/IScopeMessager.cs | 6 - .../Message/ISingleMessager.cs | 6 - .../Message/ITransientMessager.cs | 6 - .../WebApiNetCore31/Message/LogMessager.cs | 9 - .../Message/MachineMessager.cs | 9 - .../WebApiNetCore31/Message/MultiMessager.cs | 9 - .../WebApiNetCore31/Program.cs | 22 - .../Properties/launchSettings.json | 30 - .../WebApiNetCore31/Startup.cs | 49 - .../WebApiNetCore31/WebApiNetCore31.csproj | 20 - .../WebApiNetCore31.csproj.DotSettings | 2 - .../appsettings.Development.json | 9 - .../WebApiNetCore31/appsettings.json | 10 - DI/Lab.MultipleImpl/Client/Client.csproj | 23 - DI/Lab.MultipleImpl/Client/UnitTest1.cs | 128 - DI/Lab.MultipleImpl/Lab.MultipleImpl.sln | 28 - .../NET5.TestProject/AutofacStartup.cs | 53 - .../Controllers/AutofacController.cs | 38 - .../Controllers/DefaultController.cs | 30 - .../Controllers/FuncController.cs | 33 - .../Controllers/MultiController.cs | 56 - .../Controllers/UnityController.cs | 39 - .../NET5.TestProject/File/FileProvider.cs | 14 - .../NET5.TestProject/File/IFileProvider.cs | 7 - .../NET5.TestProject/File/ZipFileProvider.cs | 14 - .../NET5.TestProject/FileAdapter.cs | 19 - .../NET5.TestProject/FuncStartup.cs | 63 - .../NET5.TestProject/NET5.TestProject.csproj | 28 - .../ServiceProviderExtension.cs | 37 - .../NET5.TestProject/Startup.cs | 41 - .../NET5.TestProject/UnitTest1.cs | 214 - .../NET5.TestProject/appsettings.json | 10 - DI/Lab.MultipleImpl/Server/AutofacStartup.cs | 63 - .../Server/Controllers/AutofacController.cs | 35 - .../Server/Controllers/DefaultController.cs | 31 - .../Server/Controllers/UnityController.cs | 33 - .../Controllers/WeatherForecastController.cs | 39 - .../Server/DependencyConfig.cs | 35 - .../Server/File/FileProvider.cs | 14 - .../Server/File/IFileProvider.cs | 7 - .../Server/File/ZipFileProvider.cs | 14 - DI/Lab.MultipleImpl/Server/Program.cs | 25 - .../Server/Properties/launchSettings.json | 31 - DI/Lab.MultipleImpl/Server/Server.csproj | 21 - .../Server/ServiceProviderExtension.cs | 14 - DI/Lab.MultipleImpl/Server/Startup.cs | 52 - DI/Lab.MultipleImpl/Server/WeatherForecast.cs | 15 - .../Server/appsettings.Development.json | 9 - DI/Lab.MultipleImpl/Server/appsettings.json | 10 - DI/Lib.MsDiForScrutor | 1 - 185 files changed, 12859 deletions(-) delete mode 100644 DI/Lab.MsDI/Lab.MsDI.sln delete mode 100644 DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver2.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/App_Start/DependencyInjectionConfig.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/App_Start/FilterConfig.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/App_Start/RouteConfig.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/App_Start/ServiceProviderExtensions.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Controllers/Default1Controller.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Controllers/DefaultController.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Global.asax delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Global.asax.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute2.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Message/IScopeMessager.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Message/ISingleMessager.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Message/ITransientMessager.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Message/MachineMessager.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Message/MultiMessager.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Mvc5Net48.csproj delete mode 100644 DI/Lab.MsDI/Mvc5Net48/NLog.config delete mode 100644 DI/Lab.MsDI/Mvc5Net48/NLog.xsd delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Properties/AssemblyInfo.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/ServiceScopeHttpModule.cs delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Views/Default/Index.cshtml delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Views/Default1/Index.cshtml delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Views/web.config delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Web.Debug.config delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Web.Release.config delete mode 100644 DI/Lab.MsDI/Mvc5Net48/Web.config delete mode 100644 DI/Lab.MsDI/Mvc5Net48/packages.config delete mode 100644 DI/Lab.MsDI/WebApiNet48/App_Start/DefaultDependencyResolver.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/App_Start/DependencyInjectionConfig.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/App_Start/ServiceProviderExtensions.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/App_Start/WebApiConfig.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/Controllers/Default1Controller.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/Controllers/DefaultController.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/Global.asax delete mode 100644 DI/Lab.MsDI/WebApiNet48/Global.asax.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/LogFilterAttribute.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/Message/IScopeMessager.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/Message/ISingleMessager.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/Message/ITransientMessager.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/Message/LogMessager.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/Message/MachineMessager.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/Message/MultiMessager.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/NLog.config delete mode 100644 DI/Lab.MsDI/WebApiNet48/NLog.xsd delete mode 100644 DI/Lab.MsDI/WebApiNet48/Properties/AssemblyInfo.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/ServiceProviderDependencyResolver.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/ServiceScopeModule.cs delete mode 100644 DI/Lab.MsDI/WebApiNet48/Web.Debug.config delete mode 100644 DI/Lab.MsDI/WebApiNet48/Web.Release.config delete mode 100644 DI/Lab.MsDI/WebApiNet48/Web.config delete mode 100644 DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj delete mode 100644 DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj.DotSettings delete mode 100644 DI/Lab.MsDI/WebApiNet48/packages.config delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/Controllers/Default1Controller.cs delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/Controllers/DefaultController.cs delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/LogFilterAttribute.cs delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/Message/IMessager.cs delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/Message/IScopeMessager.cs delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/Message/ISingleMessager.cs delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/Message/ITransientMessager.cs delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/Message/LogMessager.cs delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/Message/MachineMessager.cs delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/Message/MultiMessager.cs delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/Program.cs delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/Properties/launchSettings.json delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/Startup.cs delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/WeatherForecast.cs delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/Worker.cs delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/appsettings.Development.json delete mode 100644 DI/Lab.MsDI/WebApiNetCore31/appsettings.json delete mode 100644 DI/Lab.MsDI/WebApiOwinNet48/App.config delete mode 100644 DI/Lab.MsDI/WebApiOwinNet48/AppSetting.cs delete mode 100644 DI/Lab.MsDI/WebApiOwinNet48/App_Start/DefaultDependencyResolver.cs delete mode 100644 DI/Lab.MsDI/WebApiOwinNet48/App_Start/DependencyInjectionConfig.cs delete mode 100644 DI/Lab.MsDI/WebApiOwinNet48/App_Start/ServiceProviderExtensions.cs delete mode 100644 DI/Lab.MsDI/WebApiOwinNet48/App_Start/WebApiConfig.cs delete mode 100644 DI/Lab.MsDI/WebApiOwinNet48/Commander.cs delete mode 100644 DI/Lab.MsDI/WebApiOwinNet48/Controllers/DefaultController.cs delete mode 100644 DI/Lab.MsDI/WebApiOwinNet48/LabMiddleware.cs delete mode 100644 DI/Lab.MsDI/WebApiOwinNet48/Program.cs delete mode 100644 DI/Lab.MsDI/WebApiOwinNet48/Properties/AssemblyInfo.cs delete mode 100644 DI/Lab.MsDI/WebApiOwinNet48/Startup.cs delete mode 100644 DI/Lab.MsDI/WebApiOwinNet48/WebApiOwinNet48.csproj delete mode 100644 DI/Lab.MsDI/WebApiOwinNet48/packages.config delete mode 100644 DI/Lab.MsDI/WinFormNet48/App.config delete mode 100644 DI/Lab.MsDI/WinFormNet48/DependencyInjectionConfig.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Form1.Designer.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Form1.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Form1.resx delete mode 100644 DI/Lab.MsDI/WinFormNet48/Message/IMessager.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Message/IScopeMessager.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Message/ISingleMessager.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Message/ITransientMessager.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Message/LogMessager.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Message/MachineMessager.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Message/MultiMessager.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Program.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Properties/AssemblyInfo.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Properties/Resources.Designer.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Properties/Resources.resx delete mode 100644 DI/Lab.MsDI/WinFormNet48/Properties/Settings.Designer.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Properties/Settings.settings delete mode 100644 DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj delete mode 100644 DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj.DotSettings delete mode 100644 DI/Lab.MsDI/WinFormNet48/Worker.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/Workflow.cs delete mode 100644 DI/Lab.MsDI/WinFormNet48/packages.config delete mode 100644 DI/Lab.MsDIForAutofac/Lab.MsDIForAutofac.sln delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DefaultDependencyResolver.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DependencyInjectionConfig.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/ServiceProviderExtensions.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/WebApiConfig.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Controllers/DefaultController.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Message/IMessager.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Message/LogMessager.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Properties/AssemblyInfo.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/ServiceCollectionExtensions.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Web.Debug.config delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Web.Release.config delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Web.config delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj.DotSettings delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/packages.config delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/DefaultController.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IMessager.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IScopeMessager.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ISingleMessager.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ITransientMessager.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/LogMessager.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MachineMessager.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MultiMessager.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Program.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Properties/launchSettings.json delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Startup.cs delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.Development.json delete mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.json delete mode 100644 DI/Lab.MultipleImpl/Client/Client.csproj delete mode 100644 DI/Lab.MultipleImpl/Client/UnitTest1.cs delete mode 100644 DI/Lab.MultipleImpl/Lab.MultipleImpl.sln delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/FuncController.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/MultiController.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/File/FileProvider.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/File/IFileProvider.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/File/ZipFileProvider.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/FileAdapter.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/FuncStartup.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Startup.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs delete mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/appsettings.json delete mode 100644 DI/Lab.MultipleImpl/Server/AutofacStartup.cs delete mode 100644 DI/Lab.MultipleImpl/Server/Controllers/AutofacController.cs delete mode 100644 DI/Lab.MultipleImpl/Server/Controllers/DefaultController.cs delete mode 100644 DI/Lab.MultipleImpl/Server/Controllers/UnityController.cs delete mode 100644 DI/Lab.MultipleImpl/Server/Controllers/WeatherForecastController.cs delete mode 100644 DI/Lab.MultipleImpl/Server/DependencyConfig.cs delete mode 100644 DI/Lab.MultipleImpl/Server/File/FileProvider.cs delete mode 100644 DI/Lab.MultipleImpl/Server/File/IFileProvider.cs delete mode 100644 DI/Lab.MultipleImpl/Server/File/ZipFileProvider.cs delete mode 100644 DI/Lab.MultipleImpl/Server/Program.cs delete mode 100644 DI/Lab.MultipleImpl/Server/Properties/launchSettings.json delete mode 100644 DI/Lab.MultipleImpl/Server/Server.csproj delete mode 100644 DI/Lab.MultipleImpl/Server/ServiceProviderExtension.cs delete mode 100644 DI/Lab.MultipleImpl/Server/Startup.cs delete mode 100644 DI/Lab.MultipleImpl/Server/WeatherForecast.cs delete mode 100644 DI/Lab.MultipleImpl/Server/appsettings.Development.json delete mode 100644 DI/Lab.MultipleImpl/Server/appsettings.json delete mode 160000 DI/Lib.MsDiForScrutor diff --git a/DI/Lab.MsDI/Lab.MsDI.sln b/DI/Lab.MsDI/Lab.MsDI.sln deleted file mode 100644 index fa493253..00000000 --- a/DI/Lab.MsDI/Lab.MsDI.sln +++ /dev/null @@ -1,49 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30503.244 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinFormNet48", "WinFormNet48\WinFormNet48.csproj", "{FC5AA11A-0879-43D8-8E79-DFC37DE75FB8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApiNetCore31", "WebApiNetCore31\WebApiNetCore31.csproj", "{08C0CD22-F32B-4E54-BC60-AB6912C8D84F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiNet48", "WebApiNet48\WebApiNet48.csproj", "{429D865C-EDE7-4EA1-BC51-9A5BCAB26BB8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mvc5Net48", "Mvc5Net48\Mvc5Net48.csproj", "{91B2AC8D-1516-4D84-AF08-D72A50854607}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiOwinNet48", "WebApiOwinNet48\WebApiOwinNet48.csproj", "{680EC3CF-CFE2-48F9-8006-F6A3D9B5DC42}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {FC5AA11A-0879-43D8-8E79-DFC37DE75FB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FC5AA11A-0879-43D8-8E79-DFC37DE75FB8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FC5AA11A-0879-43D8-8E79-DFC37DE75FB8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FC5AA11A-0879-43D8-8E79-DFC37DE75FB8}.Release|Any CPU.Build.0 = Release|Any CPU - {08C0CD22-F32B-4E54-BC60-AB6912C8D84F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {08C0CD22-F32B-4E54-BC60-AB6912C8D84F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08C0CD22-F32B-4E54-BC60-AB6912C8D84F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {08C0CD22-F32B-4E54-BC60-AB6912C8D84F}.Release|Any CPU.Build.0 = Release|Any CPU - {429D865C-EDE7-4EA1-BC51-9A5BCAB26BB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {429D865C-EDE7-4EA1-BC51-9A5BCAB26BB8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {429D865C-EDE7-4EA1-BC51-9A5BCAB26BB8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {429D865C-EDE7-4EA1-BC51-9A5BCAB26BB8}.Release|Any CPU.Build.0 = Release|Any CPU - {91B2AC8D-1516-4D84-AF08-D72A50854607}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {91B2AC8D-1516-4D84-AF08-D72A50854607}.Debug|Any CPU.Build.0 = Debug|Any CPU - {91B2AC8D-1516-4D84-AF08-D72A50854607}.Release|Any CPU.ActiveCfg = Release|Any CPU - {91B2AC8D-1516-4D84-AF08-D72A50854607}.Release|Any CPU.Build.0 = Release|Any CPU - {680EC3CF-CFE2-48F9-8006-F6A3D9B5DC42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {680EC3CF-CFE2-48F9-8006-F6A3D9B5DC42}.Debug|Any CPU.Build.0 = Debug|Any CPU - {680EC3CF-CFE2-48F9-8006-F6A3D9B5DC42}.Release|Any CPU.ActiveCfg = Release|Any CPU - {680EC3CF-CFE2-48F9-8006-F6A3D9B5DC42}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {49F395E2-6467-4209-955A-D361369FCEA6} - EndGlobalSection -EndGlobal diff --git a/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver.cs b/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver.cs deleted file mode 100644 index ab391905..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web; -using System.Web.Mvc; -using Microsoft.Extensions.DependencyInjection; - -namespace Mvc5Net48 -{ - internal class DefaultDependencyResolver : IDependencyResolver - { - public object GetService(Type serviceType) - { - if (HttpContext.Current?.Items[typeof(IServiceScope)] is IServiceScope scope) - { - return scope.ServiceProvider.GetService(serviceType); - } - - throw new InvalidOperationException("IServiceScope not provided"); - } - - public IEnumerable GetServices(Type serviceType) - { - if (HttpContext.Current?.Items[typeof(IServiceScope)] is IServiceScope scope) - { - return scope.ServiceProvider.GetServices(serviceType); - } - - throw new InvalidOperationException("IServiceScope not provided"); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver2.cs b/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver2.cs deleted file mode 100644 index 0336d89b..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver2.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web.Mvc; -using Microsoft.Extensions.DependencyInjection; - -namespace Mvc5Net48 -{ - /// - /// Scope 的生命週期錯誤 - /// - public class DefaultDependencyResolver2 : IDependencyResolver - { - private readonly ServiceProvider _serviceProvider; - - public DefaultDependencyResolver2(IServiceProvider serviceProvider) - { - this._serviceProvider = serviceProvider as ServiceProvider; - } - - public object GetService(Type serviceType) - { - return this._serviceProvider.GetService(serviceType); - } - - public IEnumerable GetServices(Type serviceType) - { - return this._serviceProvider.GetServices(serviceType); - } - - public void Dispose() - { - this._serviceProvider?.Dispose(); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/App_Start/DependencyInjectionConfig.cs b/DI/Lab.MsDI/Mvc5Net48/App_Start/DependencyInjectionConfig.cs deleted file mode 100644 index 2c1a5ccf..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/App_Start/DependencyInjectionConfig.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Linq; -using System.Web.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Mvc5Net48.Message; - -namespace Mvc5Net48 -{ - public class DependencyInjectionConfig - { - public static void Register() - { - var services = ConfigureServices(); - - var provider = services.BuildServiceProvider(); - - var resolver = new DefaultDependencyResolver(); - ServiceScopeHttpModule.SetServiceProvider(provider); - DependencyResolver.SetResolver(resolver); - - //config.DependencyResolver = resolver; - } - - /// - /// 使用 MS DI 註冊 - /// - /// - private static ServiceCollection ConfigureServices() - { - var services = new ServiceCollection(); - - //使用 Microsoft.Extensions.DependencyInjection 註冊 - services.AddControllersAsServices(typeof(DependencyInjectionConfig).Assembly - .GetExportedTypes()); - - services.AddScoped(); - - services.AddTransient() - .AddSingleton() - .AddScoped(); - return services; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/App_Start/FilterConfig.cs b/DI/Lab.MsDI/Mvc5Net48/App_Start/FilterConfig.cs deleted file mode 100644 index 921340fc..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/App_Start/FilterConfig.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Web.Mvc; - -namespace Mvc5Net48 -{ - public class FilterConfig - { - public static void RegisterGlobalFilters(GlobalFilterCollection filters) - { - filters.Add(new HandleErrorAttribute()); - - // filters.Add(new LogFilterAttribute()); - filters.Add(new LogFilterAttribute2()); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/App_Start/RouteConfig.cs b/DI/Lab.MsDI/Mvc5Net48/App_Start/RouteConfig.cs deleted file mode 100644 index c951f437..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/App_Start/RouteConfig.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Web.Mvc; -using System.Web.Routing; - -namespace Mvc5Net48 -{ - public class RouteConfig - { - public static void RegisterRoutes(RouteCollection routes) - { - routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); - - routes.MapRoute( - name: "Default", - url: "{controller}/{action}/{id}", - defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } - ); - - } - } -} diff --git a/DI/Lab.MsDI/Mvc5Net48/App_Start/ServiceProviderExtensions.cs b/DI/Lab.MsDI/Mvc5Net48/App_Start/ServiceProviderExtensions.cs deleted file mode 100644 index a346c1b6..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/App_Start/ServiceProviderExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Mvc; -using Microsoft.Extensions.DependencyInjection; - -namespace Mvc5Net48 -{ - public static class ServiceProviderExtensions - { - public static IServiceCollection AddControllersAsServices(this IServiceCollection services, - IEnumerable controllerTypes) - { - var filter = controllerTypes.Where(t => !t.IsAbstract - && !t.IsGenericTypeDefinition) - .Where(t => typeof(ControllerBase).IsAssignableFrom(t) - || t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)); - - foreach (var type in filter) - { - services.AddTransient(type); - } - - return services; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Controllers/Default1Controller.cs b/DI/Lab.MsDI/Mvc5Net48/Controllers/Default1Controller.cs deleted file mode 100644 index 567997d6..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Controllers/Default1Controller.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Web.Mvc; -using Mvc5Net48.Message; -using NLog; - -namespace Mvc5Net48.Controllers -{ - public class Default1Controller : Controller - { - // GET: Default - public ActionResult Index() - { - var single = this.Resolver.GetService(typeof(ISingleMessager)) as ISingleMessager; - var scope = this.Resolver.GetService(typeof(IScopeMessager)) as IScopeMessager; - var transient = this.Resolver.GetService(typeof(ITransientMessager)) as ITransientMessager; - var content = "我在 Controller.Get Action\r\n" - + $"transient:{transient.OperationId}\r\n" - + $"scope:{scope.OperationId}\r\n" - + $"single:{single.OperationId}\r\n" - ; - Console.WriteLine(content); - this.ViewBag.Message = content; - var logger = LogManager.GetCurrentClassLogger(); - logger.Trace(content); - return this.View(); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Controllers/DefaultController.cs b/DI/Lab.MsDI/Mvc5Net48/Controllers/DefaultController.cs deleted file mode 100644 index 3f17c0d1..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Controllers/DefaultController.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Web.Mvc; -using Mvc5Net48.Message; -using NLog; - -namespace Mvc5Net48.Controllers -{ - public class DefaultController : Controller - { - private ITransientMessager Transient { get; } - - private IScopeMessager Scope { get; } - - private ISingleMessager Single { get; } - - public DefaultController(ITransientMessager transient, - IScopeMessager scope, - ISingleMessager single) - { - this.Transient = transient; - this.Scope = scope; - this.Single = single; - } - - // GET: Default - public ActionResult Index() - { - var single = this.Single; - var scope = this.Scope; - var transient = this.Transient; - var content = "我在 Controller.Get Action\r\n" - + $"transient:{transient.OperationId}\r\n" - + $"scope:{scope.OperationId}\r\n" - + $"single:{single.OperationId}\r\n" - ; - Console.WriteLine(content); - this.ViewBag.Message = content; - var logger = LogManager.GetCurrentClassLogger(); - logger.Trace(content); - return this.View(); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Global.asax b/DI/Lab.MsDI/Mvc5Net48/Global.asax deleted file mode 100644 index 9fa1c945..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Global.asax +++ /dev/null @@ -1 +0,0 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="Mvc5Net48.MvcApplication" Language="C#" %> diff --git a/DI/Lab.MsDI/Mvc5Net48/Global.asax.cs b/DI/Lab.MsDI/Mvc5Net48/Global.asax.cs deleted file mode 100644 index e8693b77..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Global.asax.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; -using Mvc5Net48; - -[assembly: PreApplicationStartMethod(typeof(MvcApplication), "InitModule")] -namespace Mvc5Net48 -{ - public class MvcApplication : HttpApplication - { - public static void InitModule() - { - RegisterModule(typeof(ServiceScopeHttpModule)); - } - - protected void Application_Start() - { - AreaRegistration.RegisterAllAreas(); - RouteConfig.RegisterRoutes(RouteTable.Routes); - DependencyInjectionConfig.Register(); - FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute.cs b/DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute.cs deleted file mode 100644 index 8738a24c..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Diagnostics; -using System.Web.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Mvc5Net48.Message; -using NLog; - -namespace Mvc5Net48 -{ - public class LogFilterAttribute : ActionFilterAttribute - { - public override void OnActionExecuting(ActionExecutingContext filterContext) - { - var serviceScope = filterContext.HttpContext?.Items[typeof(IServiceScope)] as IServiceScope; - if (serviceScope == null) - { - return; - } - - var serviceProvider = serviceScope.ServiceProvider; - - var transient = serviceProvider.GetService(); - var single = serviceProvider.GetService(); - - var scope = serviceProvider.GetService(); - var scope2 = DependencyResolver.Current.GetService(); - var noeq = scope.OperationId == scope2.OperationId; - Debug.Assert(noeq); - - var logger = LogManager.GetCurrentClassLogger(); - var content = "我在 LogFilterAttribute.OnActionExecuting\r\n" + - $"transient:{transient.OperationId}\r\n" + - $"scope:{scope.OperationId}\r\n" + - $"single:{single.OperationId}"; - Console.WriteLine(content); - - logger.Trace(content); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute2.cs b/DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute2.cs deleted file mode 100644 index 76e285d4..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute2.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Diagnostics; -using System.Web.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Mvc5Net48.Message; -using NLog; - -namespace Mvc5Net48 -{ - public class LogFilterAttribute2 : ActionFilterAttribute - { - public override void OnActionExecuting(ActionExecutingContext filterContext) - { - // var transientMessager = filterContext.HttpContext.GetService();//失敗 - - var transient = DependencyResolver.Current.GetService(); - var single = DependencyResolver.Current.GetService(); - var scope = DependencyResolver.Current.GetService(); - - var logger = LogManager.GetCurrentClassLogger(); - var content = "我在 LogFilterAttribute.OnActionExecuting\r\n" + - $"transient:{transient.OperationId}\r\n" + - $"scope:{scope.OperationId}\r\n" + - $"single:{single.OperationId}"; - Console.WriteLine(content); - - logger.Trace(content); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs deleted file mode 100644 index e3bda957..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Mvc5Net48.Message -{ - public interface IMessager:IDisposable - { - string OperationId { get; } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/IScopeMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/IScopeMessager.cs deleted file mode 100644 index 3946da29..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Message/IScopeMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Mvc5Net48.Message -{ - public interface IScopeMessager : IMessager - { - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/ISingleMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/ISingleMessager.cs deleted file mode 100644 index 78d712f3..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Message/ISingleMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Mvc5Net48.Message -{ - public interface ISingleMessager : IMessager - { - } -} diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/ITransientMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/ITransientMessager.cs deleted file mode 100644 index b83ff166..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Message/ITransientMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Mvc5Net48.Message -{ - public interface ITransientMessager : IMessager - { - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs deleted file mode 100644 index b40b1319..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Mvc5Net48.Message -{ - internal class LogMessager : IMessager - { - public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; - - public void Dispose() - { - Console.WriteLine($"{nameof(LogMessager)} GC"); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/MachineMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/MachineMessager.cs deleted file mode 100644 index 8d91ec1d..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Message/MachineMessager.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Mvc5Net48.Message -{ - internal class MachineMessager : IMessager - { - public string OperationId { get; } = $"機器-{Guid.NewGuid()}"; - public void Dispose() - { - Console.WriteLine($"{nameof(MachineMessager)} GC"); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/MultiMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/MultiMessager.cs deleted file mode 100644 index d2206ff8..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Message/MultiMessager.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Mvc5Net48.Message -{ - public class MultiMessager : IScopeMessager, ISingleMessager, ITransientMessager - { - public string OperationId { get; } = $"多個接口-{Guid.NewGuid()}"; - public void Dispose() - { - Console.WriteLine($"{nameof(MultiMessager)} GC"); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Mvc5Net48.csproj b/DI/Lab.MsDI/Mvc5Net48/Mvc5Net48.csproj deleted file mode 100644 index 543d462b..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Mvc5Net48.csproj +++ /dev/null @@ -1,202 +0,0 @@ - - - - - Debug - AnyCPU - - - 2.0 - {91B2AC8D-1516-4D84-AF08-D72A50854607} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - Mvc5Net48 - Mvc5Net48 - v4.8 - true - - 44362 - - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - true - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Bcl.AsyncInterfaces.5.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll - - - - ..\packages\Microsoft.Extensions.DependencyInjection.3.1.9\lib\net461\Microsoft.Extensions.DependencyInjection.dll - True - - - ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - True - - - ..\packages\NLog.4.7.6\lib\net45\NLog.dll - - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll - - - - - ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - - - - - - - - - - - - - - - - - - - - - - ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll - - - ..\packages\Microsoft.AspNet.Webpages.3.2.7\lib\net45\System.Web.Webpages.dll - - - ..\packages\Microsoft.AspNet.Webpages.3.2.7\lib\net45\System.Web.Webpages.Deployment.dll - - - ..\packages\Microsoft.AspNet.Webpages.3.2.7\lib\net45\System.Web.Webpages.Razor.dll - - - ..\packages\Microsoft.AspNet.Webpages.3.2.7\lib\net45\System.Web.Helpers.dll - - - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - - ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll - - - ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - - - - - - - - - - - - - - - - - Global.asax - - - - - - - - - - - - - - - - - PreserveNewest - - - Designer - - - - - - Web.config - - - Web.config - - - - - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - True - True - 60289 - / - https://localhost:44362/ - False - False - - - False - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/NLog.config b/DI/Lab.MsDI/Mvc5Net48/NLog.config deleted file mode 100644 index cd5f89eb..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/NLog.config +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DI/Lab.MsDI/Mvc5Net48/NLog.xsd b/DI/Lab.MsDI/Mvc5Net48/NLog.xsd deleted file mode 100644 index 32246a71..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/NLog.xsd +++ /dev/null @@ -1,3644 +0,0 @@ - - - - - - - - - - - - - - - Watch config file for changes and reload automatically. - - - - - Print internal NLog messages to the console. Default value is: false - - - - - Print internal NLog messages to the console error output. Default value is: false - - - - - Write internal NLog messages to the specified file. - - - - - Log level threshold for internal log messages. Default value is: Info. - - - - - Global log level threshold for application log messages. Messages below this level won't be logged. - - - - - Throw an exception when there is an internal error. Default value is: false. Not recommend to set to true in production! - - - - - Throw an exception when there is a configuration error. If not set, determined by throwExceptions. - - - - - Gets or sets a value indicating whether Variables should be kept on configuration reload. Default value is: false. - - - - - Write internal NLog messages to the System.Diagnostics.Trace. Default value is: false. - - - - - Write timestamps for internal NLog messages. Default value is: true. - - - - - Use InvariantCulture as default culture instead of CurrentCulture. Default value is: false. - - - - - Perform message template parsing and formatting of LogEvent messages (true = Always, false = Never, empty = Auto Detect). Default value is: empty. - - - - - - - - - - - - - - Make all targets within this section asynchronous (creates additional threads but the calling thread isn't blocked by any target writes). - - - - - - - - - - - - - - - - - Prefix for targets/layout renderers/filters/conditions loaded from this assembly. - - - - - Load NLog extensions from the specified file (*.dll) - - - - - Load NLog extensions from the specified assembly. Assembly name should be fully qualified. - - - - - - - - - - Filter on the name of the logger. May include wildcard characters ('*' or '?'). - - - - - Comma separated list of levels that this rule matches. - - - - - Minimum level that this rule matches. - - - - - Maximum level that this rule matches. - - - - - Level that this rule matches. - - - - - Comma separated list of target names. - - - - - Ignore further rules if this one matches. - - - - - Enable this rule. Note: disabled rules aren't available from the API. - - - - - Rule identifier to allow rule lookup with Configuration.FindRuleByName and Configuration.RemoveRuleByName. - - - - - - - - - - - - - - - Default action if none of the filters match. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the file to be included. You could use * wildcard. The name is relative to the name of the current config file. - - - - - Ignore any errors in the include file. - - - - - - - - Variable value. Note, the 'value' attribute has precedence over this one. - - - - - - Variable name. - - - - - Variable value. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Number of log events that should be processed in a batch by the lazy writer thread. - - - - - Whether to use the locking queue, instead of a lock-free concurrent queue The locking queue is less concurrent when many logger threads, but reduces memory allocation - - - - - Limit of full s to write before yielding into Performance is better when writing many small batches, than writing a single large batch - - - - - Action to be taken when the lazy writer thread request queue count exceeds the set limit. - - - - - Limit on the number of requests in the lazy writer thread request queue. - - - - - Time in milliseconds to sleep between batches. (1 or less means trigger on new activity) - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - Delay the flush until the LogEvent has been confirmed as written - - - - - Condition expression. Log events who meet this condition will cause a flush on the wrapped target. - - - - - Only flush when LogEvent matches condition. Ignore explicit-flush, config-reload-flush and shutdown-flush - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Number of log events to be buffered. - - - - - Timeout (in milliseconds) after which the contents of buffer will be flushed if there's no write in the specified period of time. Use -1 to disable timed flushes. - - - - - Action to take if the buffer overflows. - - - - - Indicates whether to use sliding timeout. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Encoding to be used. - - - - - Instance of that is used to format log messages. - - - - - End of line value if a newline is appended at the end of log message . - - - - - Maximum message size in bytes. - - - - - Indicates whether to append newline at the end of log message. - - - - - Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. - - - - - Network address. - - - - - Size of the connection cache (number of connections which are kept alive). - - - - - The number of seconds a connection will remain idle before the first keep-alive probe is sent - - - - - Maximum queue size. - - - - - Maximum current connections. 0 = no maximum. - - - - - Action that should be taken if the will be more connections than . - - - - - Action that should be taken if the message is larger than maxMessageSize. - - - - - Indicates whether to keep connection open whenever possible. - - - - - NDLC item separator. - - - - - Indicates whether to include source info (file name and line number) in the information sent over the network. - - - - - Renderer for log4j:event logger-xml-attribute (Default ${logger}) - - - - - Indicates whether to include NLog-specific extensions to log4j schema. - - - - - Indicates whether to include contents of the stack. - - - - - Indicates whether to include stack contents. - - - - - Indicates whether to include dictionary contents. - - - - - Indicates whether to include dictionary contents. - - - - - Indicates whether to include call site (class and method name) in the information sent over the network. - - - - - Option to include all properties from the log events - - - - - AppInfo field. By default it's the friendly name of the current AppDomain. - - - - - NDC item separator. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Layout that should be use to calculate the value for the parameter. - - - - - Viewer parameter name. - - - - - Whether an attribute with empty value should be included in the output - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Text to be rendered. - - - - - Header. - - - - - Footer. - - - - - Indicates whether to auto-check if the console is available. - Disables console writing if Environment.UserInteractive = False (Windows Service) - Disables console writing if Console Standard Input is not available (Non-Console-App) - - - - - Enables output using ANSI Color Codes - - - - - The encoding for writing messages to the . - - - - - Indicates whether the error stream (stderr) should be used instead of the output stream (stdout). - - - - - Indicates whether to auto-flush after - - - - - Indicates whether to auto-check if the console has been redirected to file - Disables coloring logic when System.Console.IsOutputRedirected = true - - - - - Indicates whether to use default row highlighting rules. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Condition that must be met in order to set the specified foreground and background color. - - - - - Background color. - - - - - Foreground color. - - - - - - - - - - - - - - - - - Compile the ? This can improve the performance, but at the costs of more memory usage. If false, the Regex Cache is used. - - - - - Condition that must be met before scanning the row for highlight of words - - - - - Indicates whether to ignore case when comparing texts. - - - - - Regular expression to be matched. You must specify either text or regex. - - - - - Text to be matched. You must specify either text or regex. - - - - - Indicates whether to match whole words only. - - - - - Background color. - - - - - Foreground color. - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Text to be rendered. - - - - - Header. - - - - - Footer. - - - - - Indicates whether to auto-flush after - - - - - Indicates whether to auto-check if the console is available - Disables console writing if Environment.UserInteractive = False (Windows Service) - Disables console writing if Console Standard Input is not available (Non-Console-App) - - - - - The encoding for writing messages to the . - - - - - Indicates whether to send the log messages to the standard error instead of the standard output. - - - - - Whether to enable batch writing using char[]-buffers, instead of using - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Obsolete - value will be ignored! The logging code always runs outside of transaction. Gets or sets a value indicating whether to use database transactions. Some data providers require this. - - - - - Indicates whether to keep the database connection open between the log events. - - - - - Name of the database provider. - - - - - Database password. If the ConnectionString is not provided this value will be used to construct the "Password=" part of the connection string. - - - - - Database host name. If the ConnectionString is not provided this value will be used to construct the "Server=" part of the connection string. - - - - - Database user name. If the ConnectionString is not provided this value will be used to construct the "User ID=" part of the connection string. - - - - - Name of the connection string (as specified in <connectionStrings> configuration section. - - - - - Connection string. When provided, it overrides the values specified in DBHost, DBUserName, DBPassword, DBDatabase. - - - - - Database name. If the ConnectionString is not provided this value will be used to construct the "Database=" part of the connection string. - - - - - Connection string using for installation and uninstallation. If not provided, regular ConnectionString is being used. - - - - - Configures isolated transaction batch writing. If supported by the database, then it will improve insert performance. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - Text of the SQL command to be run on each log level. - - - - - Type of the SQL command to be run on each log level. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Convert format of the property value - - - - - Culture used for parsing property string-value for type-conversion - - - - - Value to assign on the object-property - - - - - Name for the object-property - - - - - Type of the object-property - - - - - - - - - - - - - - Type of the command. - - - - - Connection string to run the command against. If not provided, connection string from the target is used. - - - - - Indicates whether to ignore failures. - - - - - Command text. - - - - - - - - - - - - - - - - - - - Database parameter name. - - - - - Layout that should be use to calculate the value for the parameter. - - - - - Database parameter DbType. - - - - - Database parameter size. - - - - - Database parameter precision. - - - - - Database parameter scale. - - - - - Type of the parameter. - - - - - Whether empty value should translate into DbNull. Requires database column to allow NULL values. - - - - - Convert format of the database parameter value. - - - - - Culture used for parsing parameter string-value for type-conversion - - - - - - - - - - - - - - - - Name of the target. - - - - - Text to be rendered. - - - - - Header. - - - - - Footer. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - Name of the target. - - - - - Layout used to format log messages. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Layout used to format log messages. - - - - - Layout that renders event Category. - - - - - Optional entry type. When not set, or when not convertible to then determined by - - - - - Layout that renders event ID. - - - - - Name of the Event Log to write to. This can be System, Application or any user-defined name. - - - - - Name of the machine on which Event Log service is running. - - - - - Maximum Event log size in kilobytes. - - - - - Message length limit to write to the Event Log. - - - - - Value to be used as the event Source. - - - - - Action to take if the message is larger than the option. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Indicates whether to return to the first target after any successful write. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Text to be rendered. - - - - - Header. - - - - - Footer. - - - - - File encoding. - - - - - Line ending mode. - - - - - Maximum days of archive files that should be kept. - - - - - Indicates whether to compress archive files into the zip archive format. - - - - - Way file archives are numbered. - - - - - Name of the file to be used for an archive. - - - - - Is the an absolute or relative path? - - - - - Indicates whether to automatically archive log files every time the specified time passes. - - - - - Size in bytes above which log files will be automatically archived. Warning: combining this with isn't supported. We cannot create multiple archive files, if they should have the same name. Choose: - - - - - Maximum number of archive files that should be kept. - - - - - Indicates whether the footer should be written only when the file is archived. - - - - - Maximum number of log file names that should be stored as existing. - - - - - Indicates whether to delete old log file on startup. - - - - - File attributes (Windows only). - - - - - Indicates whether to create directories if they do not exist. - - - - - Cleanup invalid values in a filename, e.g. slashes in a filename. If set to true, this can impact the performance of massive writes. If set to false, nothing gets written when the filename is wrong. - - - - - Value of the file size threshold to archive old log file on startup. - - - - - Indicates whether to archive old log file on startup. - - - - - Value specifying the date format to use when archiving files. - - - - - Indicates whether to enable log file(s) to be deleted. - - - - - Indicates whether to write BOM (byte order mark) in created files - - - - - Indicates whether to replace file contents on each write instead of appending log message at the end. - - - - - Indicates whether file creation calls should be synchronized by a system global mutex. - - - - - Gets or set a value indicating whether a managed file stream is forced, instead of using the native implementation. - - - - - Is the an absolute or relative path? - - - - - Name of the file to write to. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - Indicates whether concurrent writes to the log file by multiple processes on different network hosts. - - - - - Maximum number of seconds that files are kept open. If this number is negative the files are not automatically closed after a period of inactivity. - - - - - Number of files to be kept open. Setting this to a higher value may improve performance in a situation where a single File target is writing to many files (such as splitting by level or by logger). - - - - - Indicates whether to keep log file open instead of opening and closing it on each logging event. - - - - - Whether or not this target should just discard all data that its asked to write. Mostly used for when testing NLog Stack except final write - - - - - Indicates whether concurrent writes to the log file by multiple processes on the same host. - - - - - Number of times the write is appended on the file before NLog discards the log message. - - - - - Delay in milliseconds to wait before attempting to write to the file again. - - - - - Log file buffer size in bytes. - - - - - Maximum number of seconds before open files are flushed. If this number is negative or zero the files are not flushed by timer. - - - - - Indicates whether to automatically flush the file buffers after each log message. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Condition expression. Log events who meet this condition will be forwarded to the wrapped target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Windows domain name to change context to. - - - - - Required impersonation level. - - - - - Type of the logon provider. - - - - - Logon Type. - - - - - User account password. - - - - - Indicates whether to revert to the credentials of the process instead of impersonating another user. - - - - - Username to change context to. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Interval in which messages will be written up to the number of messages. - - - - - Maximum allowed number of messages written per . - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Endpoint address. - - - - - Name of the endpoint configuration in WCF configuration file. - - - - - Indicates whether to use a WCF service contract that is one way (fire and forget) or two way (request-reply) - - - - - Client ID. - - - - - Indicates whether to include per-event properties in the payload sent to the server. - - - - - Indicates whether to use binary message encoding. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - Layout that should be use to calculate the value for the parameter. - - - - - Name of the parameter. - - - - - Type of the parameter. - - - - - Type of the parameter. Obsolete alias for - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Text to be rendered. - - - - - Header. - - - - - Footer. - - - - - Indicates whether NewLine characters in the body should be replaced with tags. - - - - - Priority used for sending mails. - - - - - Encoding to be used for sending e-mail. - - - - - BCC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). - - - - - CC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). - - - - - Indicates whether to add new lines between log entries. - - - - - Indicates whether to send message as HTML instead of plain text. - - - - - Sender's email address (e.g. joe@domain.com). - - - - - Mail message body (repeated for each log message send in one mail). - - - - - Mail subject. - - - - - Recipients' email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - Indicates the SMTP client timeout. - - - - - SMTP Server to be used for sending. - - - - - SMTP Authentication mode. - - - - - Username used to connect to SMTP server (used when SmtpAuthentication is set to "basic"). - - - - - Password used to authenticate against SMTP server (used when SmtpAuthentication is set to "basic"). - - - - - Indicates whether SSL (secure sockets layer) should be used when communicating with SMTP server. - - - - - Port number that SMTP Server is listening on. - - - - - Indicates whether the default Settings from System.Net.MailSettings should be used. - - - - - Folder where applications save mail messages to be processed by the local SMTP server. - - - - - Specifies how outgoing email messages will be handled. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Layout used to format log messages. - - - - - Max number of items to have in memory - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Class name. - - - - - Method name. The method must be public and static. Use the AssemblyQualifiedName , https://msdn.microsoft.com/en-us/library/system.type.assemblyqualifiedname(v=vs.110).aspx e.g. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Layout used to format log messages. - - - - - Encoding to be used. - - - - - End of line value if a newline is appended at the end of log message . - - - - - Maximum message size in bytes. - - - - - Indicates whether to append newline at the end of log message. - - - - - Network address. - - - - - Size of the connection cache (number of connections which are kept alive). - - - - - The number of seconds a connection will remain idle before the first keep-alive probe is sent - - - - - Indicates whether to keep connection open whenever possible. - - - - - Maximum current connections. 0 = no maximum. - - - - - Maximum queue size. - - - - - Action that should be taken if the will be more connections than . - - - - - Action that should be taken if the message is larger than maxMessageSize. - - - - - Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Encoding to be used. - - - - - Instance of that is used to format log messages. - - - - - End of line value if a newline is appended at the end of log message . - - - - - Maximum message size in bytes. - - - - - Indicates whether to append newline at the end of log message. - - - - - Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. - - - - - Network address. - - - - - Size of the connection cache (number of connections which are kept alive). - - - - - The number of seconds a connection will remain idle before the first keep-alive probe is sent - - - - - Maximum queue size. - - - - - Maximum current connections. 0 = no maximum. - - - - - Action that should be taken if the will be more connections than . - - - - - Action that should be taken if the message is larger than maxMessageSize. - - - - - Indicates whether to keep connection open whenever possible. - - - - - NDLC item separator. - - - - - Indicates whether to include source info (file name and line number) in the information sent over the network. - - - - - Renderer for log4j:event logger-xml-attribute (Default ${logger}) - - - - - Indicates whether to include NLog-specific extensions to log4j schema. - - - - - Indicates whether to include contents of the stack. - - - - - Indicates whether to include stack contents. - - - - - Indicates whether to include dictionary contents. - - - - - Indicates whether to include dictionary contents. - - - - - Indicates whether to include call site (class and method name) in the information sent over the network. - - - - - Option to include all properties from the log events - - - - - AppInfo field. By default it's the friendly name of the current AppDomain. - - - - - NDC item separator. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - Name of the target. - - - - - Layout used to format log messages. - - - - - Indicates whether to perform layout calculation. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - Name of the target. - - - - - Layout used to format log messages. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Indicates whether performance counter should be automatically created. - - - - - Name of the performance counter category. - - - - - Counter help text. - - - - - Name of the performance counter. - - - - - Performance counter type. - - - - - The value by which to increment the counter. - - - - - Performance counter instance name. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Default filter to be applied when no specific rule matches. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - Condition to be tested. - - - - - Resulting filter to be applied when the condition matches. - - - - - - - - - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - Number of times to repeat each log message. - - - - - - - - - - - - - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - Number of retries that should be attempted on the wrapped target in case of a failure. - - - - - Time to wait between retries in milliseconds. - - - - - - - - - - - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Layout used to format log messages. - - - - - Forward to (Instead of ) - - - - - Always use independent of - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - Should we include the BOM (Byte-order-mark) for UTF? Influences the property. This will only work for UTF-8. - - - - - Web service method name. Only used with Soap. - - - - - Web service namespace. Only used with Soap. - - - - - Protocol to be used when calling web service. - - - - - Custom proxy address, include port separated by a colon - - - - - Encoding. - - - - - Web service URL. - - - - - Value whether escaping be done according to the old NLog style (Very non-standard) - - - - - Value whether escaping be done according to Rfc3986 (Supports Internationalized Resource Identifiers - IRIs) - - - - - Indicates whether to pre-authenticate the HttpWebRequest (Requires 'Authorization' in parameters) - - - - - Name of the root XML element, if POST of XML document chosen. If so, this property must not be null. (see and ). - - - - - (optional) root namespace of the XML document, if POST of XML document chosen. (see and ). - - - - - Proxy configuration when calling web service - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Footer layout. - - - - - Header layout. - - - - - Body layout (can be repeated multiple times). - - - - - Custom column delimiter value (valid when ColumnDelimiter is set to 'Custom'). - - - - - Column delimiter. - - - - - Quote Character. - - - - - Quoting mode. - - - - - Indicates whether CVS should include header. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Layout of the column. - - - - - Name of the column. - - - - - Override of Quoting mode - - - - - - - - - - - - - - - - - - - - - Should forward slashes be escaped? If true, / will be converted to \/ - - - - - Option to render the empty object value {} - - - - - Option to suppress the extra spaces in the output json - - - - - List of property names to exclude when is true - - - - - Option to include all properties from the log event (as JSON) - - - - - Indicates whether to include contents of the dictionary. - - - - - Indicates whether to include contents of the dictionary. - - - - - Indicates whether to include contents of the dictionary. - - - - - How far should the JSON serializer follow object references before backing off - - - - - - - - - - - - - - - - - Layout that will be rendered as the attribute's value. - - - - - Name of the attribute. - - - - - Determines whether or not this attribute will be Json encoded. - - - - - Should forward slashes be escaped? If true, / will be converted to \/ - - - - - Indicates whether to escape non-ascii characters - - - - - Whether an attribute with empty value should be included in the output - - - - - - - - - - - - - - Footer layout. - - - - - Header layout. - - - - - Body layout (can be repeated multiple times). - - - - - - - - - - - - - - - - - - - - - Option to include all properties from the log events - - - - - Indicates whether to include call site (class and method name) in the information sent over the network. - - - - - Indicates whether to include contents of the dictionary. - - - - - Indicates whether to include contents of the dictionary. - - - - - Indicates whether to include contents of the stack. - - - - - Indicates whether to include contents of the stack. - - - - - Indicates whether to include source info (file name and line number) in the information sent over the network. - - - - - - - - - - - - - - Layout text. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - List of property names to exclude when is true - - - - - Option to include all properties from the log event (as XML) - - - - - Indicates whether to include contents of the dictionary. - - - - - Indicates whether to include contents of the dictionary. - - - - - How far should the XML serializer follow object references before backing off - - - - - XML element name to use for rendering IList-collections items - - - - - XML attribute name to use when rendering property-key When null (or empty) then key-attribute is not included - - - - - XML element name to use when rendering properties - - - - - XML attribute name to use when rendering property-value When null (or empty) then value-attribute is not included and value is formatted as XML-element-value - - - - - Name of the root XML element - - - - - Value inside the root XML element - - - - - Whether a ElementValue with empty value should be included in the output - - - - - Auto indent and create new lines - - - - - Determines whether or not this attribute will be Xml encoded. - - - - - - - - - - - - - - - Layout that will be rendered as the attribute's value. - - - - - Name of the attribute. - - - - - Determines whether or not this attribute will be Xml encoded. - - - - - Whether an attribute with empty value should be included in the output - - - - - - - - - - - - - - - - - - - - - - - - - Determines whether or not this attribute will be Xml encoded. - - - - - Name of the element - - - - - Value inside the element - - - - - Whether a ElementValue with empty value should be included in the output - - - - - Auto indent and create new lines - - - - - List of property names to exclude when is true - - - - - Option to include all properties from the log event (as XML) - - - - - Indicates whether to include contents of the dictionary. - - - - - Indicates whether to include contents of the dictionary. - - - - - How far should the XML serializer follow object references before backing off - - - - - XML element name to use for rendering IList-collections items - - - - - XML attribute name to use when rendering property-key When null (or empty) then key-attribute is not included - - - - - XML element name to use when rendering properties - - - - - XML attribute name to use when rendering property-value When null (or empty) then value-attribute is not included and value is formatted as XML-element-value - - - - - - - - - - - - - Action to be taken when filter matches. - - - - - Condition expression. - - - - - - - - - - - - - - - - - - - - - - - - - - Action to be taken when filter matches. - - - - - Indicates whether to ignore case when comparing strings. - - - - - Layout to be used to filter log messages. - - - - - Substring to be matched. - - - - - - - - - - - - - - - - - Action to be taken when filter matches. - - - - - String to compare the layout to. - - - - - Indicates whether to ignore case when comparing strings. - - - - - Layout to be used to filter log messages. - - - - - - - - - - - - - - - - - Action to be taken when filter matches. - - - - - Indicates whether to ignore case when comparing strings. - - - - - Layout to be used to filter log messages. - - - - - Substring to be matched. - - - - - - - - - - - - - - - - - Action to be taken when filter matches. - - - - - String to compare the layout to. - - - - - Indicates whether to ignore case when comparing strings. - - - - - Layout to be used to filter log messages. - - - - - - - - - - - - - - - - - - - - - - - - Action to be taken when filter matches. - - - - - Default number of unique filter values to expect, will automatically increase if needed - - - - - Applies the configured action to the initial logevent that starts the timeout period. Used to configure that it should ignore all events until timeout. - - - - - Layout to be used to filter log messages. - - - - - Max number of unique filter values to expect simultaneously - - - - - Max length of filter values, will truncate if above limit - - - - - How long before a filter expires, and logging is accepted again - - - - - Default buffer size for the internal buffers - - - - - Reuse internal buffers, and doesn't have to constantly allocate new buffers - - - - - Append FilterCount to the when an event is no longer filtered - - - - - Insert FilterCount value into when an event is no longer filtered - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Properties/AssemblyInfo.cs b/DI/Lab.MsDI/Mvc5Net48/Properties/AssemblyInfo.cs deleted file mode 100644 index fecc934b..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Mvc5Net48")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Mvc5Net48")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("91b2ac8d-1516-4d84-af08-d72a50854607")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DI/Lab.MsDI/Mvc5Net48/ServiceScopeHttpModule.cs b/DI/Lab.MsDI/Mvc5Net48/ServiceScopeHttpModule.cs deleted file mode 100644 index 7cdb30a7..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/ServiceScopeHttpModule.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Web; -using Microsoft.Extensions.DependencyInjection; - -namespace Mvc5Net48 -{ - internal class ServiceScopeHttpModule : IHttpModule - { - private static ServiceProvider s_serviceProvider; - - public static void SetServiceProvider(IServiceProvider serviceProvider) - { - s_serviceProvider = serviceProvider as ServiceProvider; - } - - public void Dispose() - { - s_serviceProvider?.Dispose(); - } - - public void Init(HttpApplication context) - { - context.BeginRequest += this.Context_BeginRequest; - context.EndRequest += this.Context_EndRequest; - } - - private void Context_BeginRequest(object sender, EventArgs e) - { - var context = ((HttpApplication) sender).Context; - context.Items[typeof(IServiceScope)] = s_serviceProvider.CreateScope(); - } - - private void Context_EndRequest(object sender, EventArgs e) - { - var context = ((HttpApplication) sender).Context; - if (context.Items[typeof(IServiceScope)] is IServiceScope scope) - { - scope.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Views/Default/Index.cshtml b/DI/Lab.MsDI/Mvc5Net48/Views/Default/Index.cshtml deleted file mode 100644 index 8ce20645..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Views/Default/Index.cshtml +++ /dev/null @@ -1,18 +0,0 @@ -@model dynamic - -@{ - Layout = null; -} - - - - - - title - - -
- @Html.Raw(HttpUtility.HtmlDecode(@ViewBag.Message)) -
- - \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Views/Default1/Index.cshtml b/DI/Lab.MsDI/Mvc5Net48/Views/Default1/Index.cshtml deleted file mode 100644 index 8ce20645..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Views/Default1/Index.cshtml +++ /dev/null @@ -1,18 +0,0 @@ -@model dynamic - -@{ - Layout = null; -} - - - - - - title - - -
- @Html.Raw(HttpUtility.HtmlDecode(@ViewBag.Message)) -
- - \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Views/web.config b/DI/Lab.MsDI/Mvc5Net48/Views/web.config deleted file mode 100644 index 3a3275a4..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Views/web.config +++ /dev/null @@ -1,42 +0,0 @@ - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DI/Lab.MsDI/Mvc5Net48/Web.Debug.config b/DI/Lab.MsDI/Mvc5Net48/Web.Debug.config deleted file mode 100644 index fae9cfef..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Web.Release.config b/DI/Lab.MsDI/Mvc5Net48/Web.Release.config deleted file mode 100644 index da6e960b..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Web.config b/DI/Lab.MsDI/Mvc5Net48/Web.config deleted file mode 100644 index 58fb7220..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/Web.config +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DI/Lab.MsDI/Mvc5Net48/packages.config b/DI/Lab.MsDI/Mvc5Net48/packages.config deleted file mode 100644 index 138971e0..00000000 --- a/DI/Lab.MsDI/Mvc5Net48/packages.config +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/App_Start/DefaultDependencyResolver.cs b/DI/Lab.MsDI/WebApiNet48/App_Start/DefaultDependencyResolver.cs deleted file mode 100644 index e9e64035..00000000 --- a/DI/Lab.MsDI/WebApiNet48/App_Start/DefaultDependencyResolver.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web.Http.Dependencies; -using Microsoft.Extensions.DependencyInjection; - -namespace WebApiNet48 -{ - public class DefaultDependencyResolver : IDependencyResolver - { - private readonly IServiceProvider _serviceProvider; - private IServiceScope _serviceScope; - - public DefaultDependencyResolver(IServiceProvider serviceProvider, IServiceScope serviceScope = null) - { - this._serviceProvider = serviceProvider; - this._serviceScope = serviceScope; - } - - public object GetService(Type serviceType) - { - return this._serviceProvider.GetService(serviceType); - } - - public IEnumerable GetServices(Type serviceType) - { - return this._serviceProvider.GetServices(serviceType); - } - - public IDependencyScope BeginScope() - { - this._serviceScope = this._serviceProvider.CreateScope(); - return new DefaultDependencyResolver(this._serviceScope.ServiceProvider,this._serviceScope); - } - - public void Dispose() - { - this._serviceScope?.Dispose(); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/App_Start/DependencyInjectionConfig.cs b/DI/Lab.MsDI/WebApiNet48/App_Start/DependencyInjectionConfig.cs deleted file mode 100644 index fee3990d..00000000 --- a/DI/Lab.MsDI/WebApiNet48/App_Start/DependencyInjectionConfig.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Linq; -using System.Web.Http; -using System.Web.Http.Controllers; -using Microsoft.Extensions.DependencyInjection; - -namespace WebApiNet48 -{ - public class DependencyInjectionConfig - { - public static void Register(HttpConfiguration config) - { - var services = ConfigureServices(); - - var provider = services.BuildServiceProvider(); - - var resolver = new DefaultDependencyResolver(provider); - config.DependencyResolver = resolver; - } - - /// - /// 使用 MS DI 註冊 - /// - /// - private static ServiceCollection ConfigureServices() - { - var services = new ServiceCollection(); - - //使用 Microsoft.Extensions.DependencyInjection 註冊 - services.AddControllersAsServices(typeof(DependencyInjectionConfig).Assembly.GetExportedTypes()); - - services.AddScoped(); - - services.AddTransient() - .AddSingleton() - .AddScoped(); - return services; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/App_Start/ServiceProviderExtensions.cs b/DI/Lab.MsDI/WebApiNet48/App_Start/ServiceProviderExtensions.cs deleted file mode 100644 index ae5524bc..00000000 --- a/DI/Lab.MsDI/WebApiNet48/App_Start/ServiceProviderExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Http.Controllers; -using Microsoft.Extensions.DependencyInjection; - -namespace WebApiNet48 -{ - public static class ServiceProviderExtensions - { - public static IServiceCollection AddControllersAsServices(this IServiceCollection services, - IEnumerable controllerTypes) - { - var filter = controllerTypes.Where(t => !t.IsAbstract - && !t.IsGenericTypeDefinition) - .Where(t => typeof(IHttpController).IsAssignableFrom(t) - || t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)); - - foreach (var type in filter) - { - services.AddTransient(type); - } - - return services; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/App_Start/WebApiConfig.cs b/DI/Lab.MsDI/WebApiNet48/App_Start/WebApiConfig.cs deleted file mode 100644 index 19e9c7a4..00000000 --- a/DI/Lab.MsDI/WebApiNet48/App_Start/WebApiConfig.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using System.Web.Http; - -namespace WebApiNet48 -{ - public static class WebApiConfig - { - public static void Register(HttpConfiguration config) - { - // Web API configuration and services - config.Filters.Add(new LogFilterAttribute()); - - // Web API routes - config.MapHttpAttributeRoutes(); - - config.Routes.MapHttpRoute( - name: "DefaultApi", - routeTemplate: "api/{controller}/{id}", - defaults: new { id = RouteParameter.Optional } - ); - } - } -} diff --git a/DI/Lab.MsDI/WebApiNet48/Controllers/Default1Controller.cs b/DI/Lab.MsDI/WebApiNet48/Controllers/Default1Controller.cs deleted file mode 100644 index ea631cce..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Controllers/Default1Controller.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Net.Http; -using System.Web.Http; -using NLog; - -namespace WebApiNet48.Controllers -{ - public class Default1Controller : ApiController - { - [HttpGet] - public IHttpActionResult Get() - { - var logger = LogManager.GetCurrentClassLogger(); - var requestScope = this.Request.GetDependencyScope(); - var transient = requestScope.GetService(typeof(ITransientMessager)) as ITransientMessager; - var scope = requestScope.GetService(typeof(IScopeMessager)) as IScopeMessager; - var single = requestScope.GetService(typeof(ISingleMessager)) as ISingleMessager; - var content = "我在 Controller.Get Action\r\n" + - $"transient:{transient.OperationId}\r\n" + - $"scope:{scope.OperationId}\r\n" + - $"single:{single.OperationId}"; - Console.WriteLine(content); - logger.Info(content); - return this.Ok(content); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Controllers/DefaultController.cs b/DI/Lab.MsDI/WebApiNet48/Controllers/DefaultController.cs deleted file mode 100644 index 51985f63..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Controllers/DefaultController.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Web.Http; -using NLog; - -namespace WebApiNet48.Controllers -{ - public class DefaultController : ApiController - { - private IMessager Transient { get; } - - private IMessager Scope { get; } - - private IMessager Single { get; } - - public DefaultController(ITransientMessager transient, - IScopeMessager scope, - ISingleMessager single) - { - this.Transient = transient; - this.Scope = scope; - this.Single = single; - } - - [HttpGet] - public IHttpActionResult Get() - { - var logger = LogManager.GetCurrentClassLogger(); - - var content = "我在 Controller.Get Action\r\n" + - $"transient:{this.Transient.OperationId}\r\n" + - $"scope:{this.Scope.OperationId}\r\n" + - $"single:{this.Single.OperationId}"; - Console.WriteLine(content); - logger.Info(content); - - //this._logger.LogInformation("transient = {transient},scope = {scope},single = {single}", - // this.Transient.OperationId, - // this.Scope.OperationId, - // this.Single.OperationId); - return this.Ok(content); - } - - - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Global.asax b/DI/Lab.MsDI/WebApiNet48/Global.asax deleted file mode 100644 index 593fb3aa..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Global.asax +++ /dev/null @@ -1 +0,0 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="WebApiNet48.WebApiApplication" Language="C#" %> diff --git a/DI/Lab.MsDI/WebApiNet48/Global.asax.cs b/DI/Lab.MsDI/WebApiNet48/Global.asax.cs deleted file mode 100644 index 3cb9d613..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Global.asax.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Web; -using System.Web.Http; - -namespace WebApiNet48 -{ - public class WebApiApplication : HttpApplication - { - protected void Application_Start() - { - GlobalConfiguration.Configure(WebApiConfig.Register); - GlobalConfiguration.Configure(DependencyInjectionConfig.Register); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/LogFilterAttribute.cs b/DI/Lab.MsDI/WebApiNet48/LogFilterAttribute.cs deleted file mode 100644 index f2055f34..00000000 --- a/DI/Lab.MsDI/WebApiNet48/LogFilterAttribute.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Net.Http; -using System.Web.Http.Controllers; -using System.Web.Http.Filters; -using NLog; - -namespace WebApiNet48 -{ - public class LogFilterAttribute : ActionFilterAttribute - { - public override void OnActionExecuting(HttpActionContext actionContext) - { - var requestScope = actionContext.Request.GetDependencyScope(); - - var transient = requestScope.GetService(typeof(ITransientMessager)) as MultiMessager; - var scope = requestScope.GetService(typeof(IScopeMessager)) as MultiMessager; - var single = requestScope.GetService(typeof(ISingleMessager)) as MultiMessager; - - var logger = LogManager.GetCurrentClassLogger(); - var content = "我在 LogFilterAttribute.OnActionExecuting\r\n" + - $"transient:{transient.OperationId}\r\n" + - $"scope:{scope.OperationId}\r\n" + - $"single:{single.OperationId}"; - Console.WriteLine(content); - logger.Info(content); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs deleted file mode 100644 index 9883346c..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace WebApiNet48 -{ - public interface IMessager:IDisposable - { - string OperationId { get; } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/IScopeMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/IScopeMessager.cs deleted file mode 100644 index a29d7677..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Message/IScopeMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace WebApiNet48 -{ - public interface IScopeMessager : IMessager - { - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/ISingleMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/ISingleMessager.cs deleted file mode 100644 index 27284507..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Message/ISingleMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace WebApiNet48 -{ - public interface ISingleMessager : IMessager - { - } -} diff --git a/DI/Lab.MsDI/WebApiNet48/Message/ITransientMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/ITransientMessager.cs deleted file mode 100644 index d9314497..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Message/ITransientMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace WebApiNet48 -{ - public interface ITransientMessager : IMessager - { - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/LogMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/LogMessager.cs deleted file mode 100644 index a73f42f4..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Message/LogMessager.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace WebApiNet48 -{ - internal class LogMessager : IMessager - { - public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; - - public void Dispose() - { - Console.WriteLine($"{nameof(LogMessager)} GC"); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/MachineMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/MachineMessager.cs deleted file mode 100644 index 8aed0c19..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Message/MachineMessager.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace WebApiNet48 -{ - internal class MachineMessager : IMessager - { - public string OperationId { get; } = $"機器-{Guid.NewGuid()}"; - - public void Dispose() - { - Console.WriteLine($"{nameof(MachineMessager)} GC"); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/MultiMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/MultiMessager.cs deleted file mode 100644 index 7e3edf7b..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Message/MultiMessager.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace WebApiNet48 -{ - public class MultiMessager : IScopeMessager, ISingleMessager, ITransientMessager - { - public string OperationId { get; } = $"多個接口-{Guid.NewGuid()}"; - - public void Dispose() - { - Console.WriteLine($"{nameof(MultiMessager)} GC"); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/NLog.config b/DI/Lab.MsDI/WebApiNet48/NLog.config deleted file mode 100644 index df272249..00000000 --- a/DI/Lab.MsDI/WebApiNet48/NLog.config +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DI/Lab.MsDI/WebApiNet48/NLog.xsd b/DI/Lab.MsDI/WebApiNet48/NLog.xsd deleted file mode 100644 index 32246a71..00000000 --- a/DI/Lab.MsDI/WebApiNet48/NLog.xsd +++ /dev/null @@ -1,3644 +0,0 @@ - - - - - - - - - - - - - - - Watch config file for changes and reload automatically. - - - - - Print internal NLog messages to the console. Default value is: false - - - - - Print internal NLog messages to the console error output. Default value is: false - - - - - Write internal NLog messages to the specified file. - - - - - Log level threshold for internal log messages. Default value is: Info. - - - - - Global log level threshold for application log messages. Messages below this level won't be logged. - - - - - Throw an exception when there is an internal error. Default value is: false. Not recommend to set to true in production! - - - - - Throw an exception when there is a configuration error. If not set, determined by throwExceptions. - - - - - Gets or sets a value indicating whether Variables should be kept on configuration reload. Default value is: false. - - - - - Write internal NLog messages to the System.Diagnostics.Trace. Default value is: false. - - - - - Write timestamps for internal NLog messages. Default value is: true. - - - - - Use InvariantCulture as default culture instead of CurrentCulture. Default value is: false. - - - - - Perform message template parsing and formatting of LogEvent messages (true = Always, false = Never, empty = Auto Detect). Default value is: empty. - - - - - - - - - - - - - - Make all targets within this section asynchronous (creates additional threads but the calling thread isn't blocked by any target writes). - - - - - - - - - - - - - - - - - Prefix for targets/layout renderers/filters/conditions loaded from this assembly. - - - - - Load NLog extensions from the specified file (*.dll) - - - - - Load NLog extensions from the specified assembly. Assembly name should be fully qualified. - - - - - - - - - - Filter on the name of the logger. May include wildcard characters ('*' or '?'). - - - - - Comma separated list of levels that this rule matches. - - - - - Minimum level that this rule matches. - - - - - Maximum level that this rule matches. - - - - - Level that this rule matches. - - - - - Comma separated list of target names. - - - - - Ignore further rules if this one matches. - - - - - Enable this rule. Note: disabled rules aren't available from the API. - - - - - Rule identifier to allow rule lookup with Configuration.FindRuleByName and Configuration.RemoveRuleByName. - - - - - - - - - - - - - - - Default action if none of the filters match. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the file to be included. You could use * wildcard. The name is relative to the name of the current config file. - - - - - Ignore any errors in the include file. - - - - - - - - Variable value. Note, the 'value' attribute has precedence over this one. - - - - - - Variable name. - - - - - Variable value. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Number of log events that should be processed in a batch by the lazy writer thread. - - - - - Whether to use the locking queue, instead of a lock-free concurrent queue The locking queue is less concurrent when many logger threads, but reduces memory allocation - - - - - Limit of full s to write before yielding into Performance is better when writing many small batches, than writing a single large batch - - - - - Action to be taken when the lazy writer thread request queue count exceeds the set limit. - - - - - Limit on the number of requests in the lazy writer thread request queue. - - - - - Time in milliseconds to sleep between batches. (1 or less means trigger on new activity) - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - Delay the flush until the LogEvent has been confirmed as written - - - - - Condition expression. Log events who meet this condition will cause a flush on the wrapped target. - - - - - Only flush when LogEvent matches condition. Ignore explicit-flush, config-reload-flush and shutdown-flush - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Number of log events to be buffered. - - - - - Timeout (in milliseconds) after which the contents of buffer will be flushed if there's no write in the specified period of time. Use -1 to disable timed flushes. - - - - - Action to take if the buffer overflows. - - - - - Indicates whether to use sliding timeout. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Encoding to be used. - - - - - Instance of that is used to format log messages. - - - - - End of line value if a newline is appended at the end of log message . - - - - - Maximum message size in bytes. - - - - - Indicates whether to append newline at the end of log message. - - - - - Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. - - - - - Network address. - - - - - Size of the connection cache (number of connections which are kept alive). - - - - - The number of seconds a connection will remain idle before the first keep-alive probe is sent - - - - - Maximum queue size. - - - - - Maximum current connections. 0 = no maximum. - - - - - Action that should be taken if the will be more connections than . - - - - - Action that should be taken if the message is larger than maxMessageSize. - - - - - Indicates whether to keep connection open whenever possible. - - - - - NDLC item separator. - - - - - Indicates whether to include source info (file name and line number) in the information sent over the network. - - - - - Renderer for log4j:event logger-xml-attribute (Default ${logger}) - - - - - Indicates whether to include NLog-specific extensions to log4j schema. - - - - - Indicates whether to include contents of the stack. - - - - - Indicates whether to include stack contents. - - - - - Indicates whether to include dictionary contents. - - - - - Indicates whether to include dictionary contents. - - - - - Indicates whether to include call site (class and method name) in the information sent over the network. - - - - - Option to include all properties from the log events - - - - - AppInfo field. By default it's the friendly name of the current AppDomain. - - - - - NDC item separator. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Layout that should be use to calculate the value for the parameter. - - - - - Viewer parameter name. - - - - - Whether an attribute with empty value should be included in the output - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Text to be rendered. - - - - - Header. - - - - - Footer. - - - - - Indicates whether to auto-check if the console is available. - Disables console writing if Environment.UserInteractive = False (Windows Service) - Disables console writing if Console Standard Input is not available (Non-Console-App) - - - - - Enables output using ANSI Color Codes - - - - - The encoding for writing messages to the . - - - - - Indicates whether the error stream (stderr) should be used instead of the output stream (stdout). - - - - - Indicates whether to auto-flush after - - - - - Indicates whether to auto-check if the console has been redirected to file - Disables coloring logic when System.Console.IsOutputRedirected = true - - - - - Indicates whether to use default row highlighting rules. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Condition that must be met in order to set the specified foreground and background color. - - - - - Background color. - - - - - Foreground color. - - - - - - - - - - - - - - - - - Compile the ? This can improve the performance, but at the costs of more memory usage. If false, the Regex Cache is used. - - - - - Condition that must be met before scanning the row for highlight of words - - - - - Indicates whether to ignore case when comparing texts. - - - - - Regular expression to be matched. You must specify either text or regex. - - - - - Text to be matched. You must specify either text or regex. - - - - - Indicates whether to match whole words only. - - - - - Background color. - - - - - Foreground color. - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Text to be rendered. - - - - - Header. - - - - - Footer. - - - - - Indicates whether to auto-flush after - - - - - Indicates whether to auto-check if the console is available - Disables console writing if Environment.UserInteractive = False (Windows Service) - Disables console writing if Console Standard Input is not available (Non-Console-App) - - - - - The encoding for writing messages to the . - - - - - Indicates whether to send the log messages to the standard error instead of the standard output. - - - - - Whether to enable batch writing using char[]-buffers, instead of using - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Obsolete - value will be ignored! The logging code always runs outside of transaction. Gets or sets a value indicating whether to use database transactions. Some data providers require this. - - - - - Indicates whether to keep the database connection open between the log events. - - - - - Name of the database provider. - - - - - Database password. If the ConnectionString is not provided this value will be used to construct the "Password=" part of the connection string. - - - - - Database host name. If the ConnectionString is not provided this value will be used to construct the "Server=" part of the connection string. - - - - - Database user name. If the ConnectionString is not provided this value will be used to construct the "User ID=" part of the connection string. - - - - - Name of the connection string (as specified in <connectionStrings> configuration section. - - - - - Connection string. When provided, it overrides the values specified in DBHost, DBUserName, DBPassword, DBDatabase. - - - - - Database name. If the ConnectionString is not provided this value will be used to construct the "Database=" part of the connection string. - - - - - Connection string using for installation and uninstallation. If not provided, regular ConnectionString is being used. - - - - - Configures isolated transaction batch writing. If supported by the database, then it will improve insert performance. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - Text of the SQL command to be run on each log level. - - - - - Type of the SQL command to be run on each log level. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Convert format of the property value - - - - - Culture used for parsing property string-value for type-conversion - - - - - Value to assign on the object-property - - - - - Name for the object-property - - - - - Type of the object-property - - - - - - - - - - - - - - Type of the command. - - - - - Connection string to run the command against. If not provided, connection string from the target is used. - - - - - Indicates whether to ignore failures. - - - - - Command text. - - - - - - - - - - - - - - - - - - - Database parameter name. - - - - - Layout that should be use to calculate the value for the parameter. - - - - - Database parameter DbType. - - - - - Database parameter size. - - - - - Database parameter precision. - - - - - Database parameter scale. - - - - - Type of the parameter. - - - - - Whether empty value should translate into DbNull. Requires database column to allow NULL values. - - - - - Convert format of the database parameter value. - - - - - Culture used for parsing parameter string-value for type-conversion - - - - - - - - - - - - - - - - Name of the target. - - - - - Text to be rendered. - - - - - Header. - - - - - Footer. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - Name of the target. - - - - - Layout used to format log messages. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Layout used to format log messages. - - - - - Layout that renders event Category. - - - - - Optional entry type. When not set, or when not convertible to then determined by - - - - - Layout that renders event ID. - - - - - Name of the Event Log to write to. This can be System, Application or any user-defined name. - - - - - Name of the machine on which Event Log service is running. - - - - - Maximum Event log size in kilobytes. - - - - - Message length limit to write to the Event Log. - - - - - Value to be used as the event Source. - - - - - Action to take if the message is larger than the option. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Indicates whether to return to the first target after any successful write. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Text to be rendered. - - - - - Header. - - - - - Footer. - - - - - File encoding. - - - - - Line ending mode. - - - - - Maximum days of archive files that should be kept. - - - - - Indicates whether to compress archive files into the zip archive format. - - - - - Way file archives are numbered. - - - - - Name of the file to be used for an archive. - - - - - Is the an absolute or relative path? - - - - - Indicates whether to automatically archive log files every time the specified time passes. - - - - - Size in bytes above which log files will be automatically archived. Warning: combining this with isn't supported. We cannot create multiple archive files, if they should have the same name. Choose: - - - - - Maximum number of archive files that should be kept. - - - - - Indicates whether the footer should be written only when the file is archived. - - - - - Maximum number of log file names that should be stored as existing. - - - - - Indicates whether to delete old log file on startup. - - - - - File attributes (Windows only). - - - - - Indicates whether to create directories if they do not exist. - - - - - Cleanup invalid values in a filename, e.g. slashes in a filename. If set to true, this can impact the performance of massive writes. If set to false, nothing gets written when the filename is wrong. - - - - - Value of the file size threshold to archive old log file on startup. - - - - - Indicates whether to archive old log file on startup. - - - - - Value specifying the date format to use when archiving files. - - - - - Indicates whether to enable log file(s) to be deleted. - - - - - Indicates whether to write BOM (byte order mark) in created files - - - - - Indicates whether to replace file contents on each write instead of appending log message at the end. - - - - - Indicates whether file creation calls should be synchronized by a system global mutex. - - - - - Gets or set a value indicating whether a managed file stream is forced, instead of using the native implementation. - - - - - Is the an absolute or relative path? - - - - - Name of the file to write to. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - Indicates whether concurrent writes to the log file by multiple processes on different network hosts. - - - - - Maximum number of seconds that files are kept open. If this number is negative the files are not automatically closed after a period of inactivity. - - - - - Number of files to be kept open. Setting this to a higher value may improve performance in a situation where a single File target is writing to many files (such as splitting by level or by logger). - - - - - Indicates whether to keep log file open instead of opening and closing it on each logging event. - - - - - Whether or not this target should just discard all data that its asked to write. Mostly used for when testing NLog Stack except final write - - - - - Indicates whether concurrent writes to the log file by multiple processes on the same host. - - - - - Number of times the write is appended on the file before NLog discards the log message. - - - - - Delay in milliseconds to wait before attempting to write to the file again. - - - - - Log file buffer size in bytes. - - - - - Maximum number of seconds before open files are flushed. If this number is negative or zero the files are not flushed by timer. - - - - - Indicates whether to automatically flush the file buffers after each log message. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Condition expression. Log events who meet this condition will be forwarded to the wrapped target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Windows domain name to change context to. - - - - - Required impersonation level. - - - - - Type of the logon provider. - - - - - Logon Type. - - - - - User account password. - - - - - Indicates whether to revert to the credentials of the process instead of impersonating another user. - - - - - Username to change context to. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Interval in which messages will be written up to the number of messages. - - - - - Maximum allowed number of messages written per . - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Endpoint address. - - - - - Name of the endpoint configuration in WCF configuration file. - - - - - Indicates whether to use a WCF service contract that is one way (fire and forget) or two way (request-reply) - - - - - Client ID. - - - - - Indicates whether to include per-event properties in the payload sent to the server. - - - - - Indicates whether to use binary message encoding. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - Layout that should be use to calculate the value for the parameter. - - - - - Name of the parameter. - - - - - Type of the parameter. - - - - - Type of the parameter. Obsolete alias for - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Text to be rendered. - - - - - Header. - - - - - Footer. - - - - - Indicates whether NewLine characters in the body should be replaced with tags. - - - - - Priority used for sending mails. - - - - - Encoding to be used for sending e-mail. - - - - - BCC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). - - - - - CC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). - - - - - Indicates whether to add new lines between log entries. - - - - - Indicates whether to send message as HTML instead of plain text. - - - - - Sender's email address (e.g. joe@domain.com). - - - - - Mail message body (repeated for each log message send in one mail). - - - - - Mail subject. - - - - - Recipients' email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - Indicates the SMTP client timeout. - - - - - SMTP Server to be used for sending. - - - - - SMTP Authentication mode. - - - - - Username used to connect to SMTP server (used when SmtpAuthentication is set to "basic"). - - - - - Password used to authenticate against SMTP server (used when SmtpAuthentication is set to "basic"). - - - - - Indicates whether SSL (secure sockets layer) should be used when communicating with SMTP server. - - - - - Port number that SMTP Server is listening on. - - - - - Indicates whether the default Settings from System.Net.MailSettings should be used. - - - - - Folder where applications save mail messages to be processed by the local SMTP server. - - - - - Specifies how outgoing email messages will be handled. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Layout used to format log messages. - - - - - Max number of items to have in memory - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Class name. - - - - - Method name. The method must be public and static. Use the AssemblyQualifiedName , https://msdn.microsoft.com/en-us/library/system.type.assemblyqualifiedname(v=vs.110).aspx e.g. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Layout used to format log messages. - - - - - Encoding to be used. - - - - - End of line value if a newline is appended at the end of log message . - - - - - Maximum message size in bytes. - - - - - Indicates whether to append newline at the end of log message. - - - - - Network address. - - - - - Size of the connection cache (number of connections which are kept alive). - - - - - The number of seconds a connection will remain idle before the first keep-alive probe is sent - - - - - Indicates whether to keep connection open whenever possible. - - - - - Maximum current connections. 0 = no maximum. - - - - - Maximum queue size. - - - - - Action that should be taken if the will be more connections than . - - - - - Action that should be taken if the message is larger than maxMessageSize. - - - - - Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Encoding to be used. - - - - - Instance of that is used to format log messages. - - - - - End of line value if a newline is appended at the end of log message . - - - - - Maximum message size in bytes. - - - - - Indicates whether to append newline at the end of log message. - - - - - Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. - - - - - Network address. - - - - - Size of the connection cache (number of connections which are kept alive). - - - - - The number of seconds a connection will remain idle before the first keep-alive probe is sent - - - - - Maximum queue size. - - - - - Maximum current connections. 0 = no maximum. - - - - - Action that should be taken if the will be more connections than . - - - - - Action that should be taken if the message is larger than maxMessageSize. - - - - - Indicates whether to keep connection open whenever possible. - - - - - NDLC item separator. - - - - - Indicates whether to include source info (file name and line number) in the information sent over the network. - - - - - Renderer for log4j:event logger-xml-attribute (Default ${logger}) - - - - - Indicates whether to include NLog-specific extensions to log4j schema. - - - - - Indicates whether to include contents of the stack. - - - - - Indicates whether to include stack contents. - - - - - Indicates whether to include dictionary contents. - - - - - Indicates whether to include dictionary contents. - - - - - Indicates whether to include call site (class and method name) in the information sent over the network. - - - - - Option to include all properties from the log events - - - - - AppInfo field. By default it's the friendly name of the current AppDomain. - - - - - NDC item separator. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - Name of the target. - - - - - Layout used to format log messages. - - - - - Indicates whether to perform layout calculation. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - Name of the target. - - - - - Layout used to format log messages. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Indicates whether performance counter should be automatically created. - - - - - Name of the performance counter category. - - - - - Counter help text. - - - - - Name of the performance counter. - - - - - Performance counter type. - - - - - The value by which to increment the counter. - - - - - Performance counter instance name. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Default filter to be applied when no specific rule matches. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - Condition to be tested. - - - - - Resulting filter to be applied when the condition matches. - - - - - - - - - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - Number of times to repeat each log message. - - - - - - - - - - - - - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - Number of retries that should be attempted on the wrapped target in case of a failure. - - - - - Time to wait between retries in milliseconds. - - - - - - - - - - - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Layout used to format log messages. - - - - - Forward to (Instead of ) - - - - - Always use independent of - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name of the target. - - - - - Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit - - - - - Should we include the BOM (Byte-order-mark) for UTF? Influences the property. This will only work for UTF-8. - - - - - Web service method name. Only used with Soap. - - - - - Web service namespace. Only used with Soap. - - - - - Protocol to be used when calling web service. - - - - - Custom proxy address, include port separated by a colon - - - - - Encoding. - - - - - Web service URL. - - - - - Value whether escaping be done according to the old NLog style (Very non-standard) - - - - - Value whether escaping be done according to Rfc3986 (Supports Internationalized Resource Identifiers - IRIs) - - - - - Indicates whether to pre-authenticate the HttpWebRequest (Requires 'Authorization' in parameters) - - - - - Name of the root XML element, if POST of XML document chosen. If so, this property must not be null. (see and ). - - - - - (optional) root namespace of the XML document, if POST of XML document chosen. (see and ). - - - - - Proxy configuration when calling web service - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Footer layout. - - - - - Header layout. - - - - - Body layout (can be repeated multiple times). - - - - - Custom column delimiter value (valid when ColumnDelimiter is set to 'Custom'). - - - - - Column delimiter. - - - - - Quote Character. - - - - - Quoting mode. - - - - - Indicates whether CVS should include header. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Layout of the column. - - - - - Name of the column. - - - - - Override of Quoting mode - - - - - - - - - - - - - - - - - - - - - Should forward slashes be escaped? If true, / will be converted to \/ - - - - - Option to render the empty object value {} - - - - - Option to suppress the extra spaces in the output json - - - - - List of property names to exclude when is true - - - - - Option to include all properties from the log event (as JSON) - - - - - Indicates whether to include contents of the dictionary. - - - - - Indicates whether to include contents of the dictionary. - - - - - Indicates whether to include contents of the dictionary. - - - - - How far should the JSON serializer follow object references before backing off - - - - - - - - - - - - - - - - - Layout that will be rendered as the attribute's value. - - - - - Name of the attribute. - - - - - Determines whether or not this attribute will be Json encoded. - - - - - Should forward slashes be escaped? If true, / will be converted to \/ - - - - - Indicates whether to escape non-ascii characters - - - - - Whether an attribute with empty value should be included in the output - - - - - - - - - - - - - - Footer layout. - - - - - Header layout. - - - - - Body layout (can be repeated multiple times). - - - - - - - - - - - - - - - - - - - - - Option to include all properties from the log events - - - - - Indicates whether to include call site (class and method name) in the information sent over the network. - - - - - Indicates whether to include contents of the dictionary. - - - - - Indicates whether to include contents of the dictionary. - - - - - Indicates whether to include contents of the stack. - - - - - Indicates whether to include contents of the stack. - - - - - Indicates whether to include source info (file name and line number) in the information sent over the network. - - - - - - - - - - - - - - Layout text. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - List of property names to exclude when is true - - - - - Option to include all properties from the log event (as XML) - - - - - Indicates whether to include contents of the dictionary. - - - - - Indicates whether to include contents of the dictionary. - - - - - How far should the XML serializer follow object references before backing off - - - - - XML element name to use for rendering IList-collections items - - - - - XML attribute name to use when rendering property-key When null (or empty) then key-attribute is not included - - - - - XML element name to use when rendering properties - - - - - XML attribute name to use when rendering property-value When null (or empty) then value-attribute is not included and value is formatted as XML-element-value - - - - - Name of the root XML element - - - - - Value inside the root XML element - - - - - Whether a ElementValue with empty value should be included in the output - - - - - Auto indent and create new lines - - - - - Determines whether or not this attribute will be Xml encoded. - - - - - - - - - - - - - - - Layout that will be rendered as the attribute's value. - - - - - Name of the attribute. - - - - - Determines whether or not this attribute will be Xml encoded. - - - - - Whether an attribute with empty value should be included in the output - - - - - - - - - - - - - - - - - - - - - - - - - Determines whether or not this attribute will be Xml encoded. - - - - - Name of the element - - - - - Value inside the element - - - - - Whether a ElementValue with empty value should be included in the output - - - - - Auto indent and create new lines - - - - - List of property names to exclude when is true - - - - - Option to include all properties from the log event (as XML) - - - - - Indicates whether to include contents of the dictionary. - - - - - Indicates whether to include contents of the dictionary. - - - - - How far should the XML serializer follow object references before backing off - - - - - XML element name to use for rendering IList-collections items - - - - - XML attribute name to use when rendering property-key When null (or empty) then key-attribute is not included - - - - - XML element name to use when rendering properties - - - - - XML attribute name to use when rendering property-value When null (or empty) then value-attribute is not included and value is formatted as XML-element-value - - - - - - - - - - - - - Action to be taken when filter matches. - - - - - Condition expression. - - - - - - - - - - - - - - - - - - - - - - - - - - Action to be taken when filter matches. - - - - - Indicates whether to ignore case when comparing strings. - - - - - Layout to be used to filter log messages. - - - - - Substring to be matched. - - - - - - - - - - - - - - - - - Action to be taken when filter matches. - - - - - String to compare the layout to. - - - - - Indicates whether to ignore case when comparing strings. - - - - - Layout to be used to filter log messages. - - - - - - - - - - - - - - - - - Action to be taken when filter matches. - - - - - Indicates whether to ignore case when comparing strings. - - - - - Layout to be used to filter log messages. - - - - - Substring to be matched. - - - - - - - - - - - - - - - - - Action to be taken when filter matches. - - - - - String to compare the layout to. - - - - - Indicates whether to ignore case when comparing strings. - - - - - Layout to be used to filter log messages. - - - - - - - - - - - - - - - - - - - - - - - - Action to be taken when filter matches. - - - - - Default number of unique filter values to expect, will automatically increase if needed - - - - - Applies the configured action to the initial logevent that starts the timeout period. Used to configure that it should ignore all events until timeout. - - - - - Layout to be used to filter log messages. - - - - - Max number of unique filter values to expect simultaneously - - - - - Max length of filter values, will truncate if above limit - - - - - How long before a filter expires, and logging is accepted again - - - - - Default buffer size for the internal buffers - - - - - Reuse internal buffers, and doesn't have to constantly allocate new buffers - - - - - Append FilterCount to the when an event is no longer filtered - - - - - Insert FilterCount value into when an event is no longer filtered - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Properties/AssemblyInfo.cs b/DI/Lab.MsDI/WebApiNet48/Properties/AssemblyInfo.cs deleted file mode 100644 index f5b17259..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("WebApiNet48")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("WebApiNet48")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("429d865c-ede7-4ea1-bc51-9a5bcab26bb8")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DI/Lab.MsDI/WebApiNet48/ServiceProviderDependencyResolver.cs b/DI/Lab.MsDI/WebApiNet48/ServiceProviderDependencyResolver.cs deleted file mode 100644 index ccb1df5c..00000000 --- a/DI/Lab.MsDI/WebApiNet48/ServiceProviderDependencyResolver.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web; -using System.Web.Http.Dependencies; -using Microsoft.Extensions.DependencyInjection; - -namespace WebApiNet48 -{ - internal class ServiceProviderDependencyResolver : IDependencyResolver - { - protected IServiceProvider ServiceProvider { get; set; } - - public ServiceProviderDependencyResolver(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public object GetService(Type serviceType) - { - if (HttpContext.Current?.Items[typeof(IServiceScope)] is IServiceScope scope) - { - return scope.ServiceProvider.GetService(serviceType); - } - - return this.ServiceProvider.GetService(serviceType); - throw new InvalidOperationException("IServiceScope not provided"); - } - - public IEnumerable GetServices(Type serviceType) - { - if (HttpContext.Current?.Items[typeof(IServiceScope)] is IServiceScope scope) - { - return scope.ServiceProvider.GetServices(serviceType); - } - - return this.ServiceProvider.GetServices(serviceType); - throw new InvalidOperationException("IServiceScope not provided"); - } - - public IDependencyScope BeginScope() - { - return new DefaultDependencyResolver(this.ServiceProvider.CreateScope().ServiceProvider); - } - - public void Dispose() - { - //throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/ServiceScopeModule.cs b/DI/Lab.MsDI/WebApiNet48/ServiceScopeModule.cs deleted file mode 100644 index d326414f..00000000 --- a/DI/Lab.MsDI/WebApiNet48/ServiceScopeModule.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Web; -using Microsoft.Extensions.DependencyInjection; - -namespace WebApiNet48 -{ - internal class ServiceScopeModule : IHttpModule - { - private static ServiceProvider _serviceProvider; - - public void Dispose() - { - - } - - public void Init(HttpApplication context) - { - context.BeginRequest += this.Context_BeginRequest; - context.EndRequest += this.Context_EndRequest; - } - - private void Context_EndRequest(object sender, EventArgs e) - { - var context = ((HttpApplication)sender).Context; - if (context.Items[typeof(IServiceScope)] is IServiceScope scope) - { - scope.Dispose(); - } - } - - private void Context_BeginRequest(object sender, EventArgs e) - { - var context = ((HttpApplication)sender).Context; - context.Items[typeof(IServiceScope)] = _serviceProvider.CreateScope(); - } - - public static void SetServiceProvider(ServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Web.Debug.config b/DI/Lab.MsDI/WebApiNet48/Web.Debug.config deleted file mode 100644 index fae9cfef..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Web.Release.config b/DI/Lab.MsDI/WebApiNet48/Web.Release.config deleted file mode 100644 index da6e960b..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Web.config b/DI/Lab.MsDI/WebApiNet48/Web.config deleted file mode 100644 index 491d448d..00000000 --- a/DI/Lab.MsDI/WebApiNet48/Web.config +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj b/DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj deleted file mode 100644 index 32461adc..00000000 --- a/DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj +++ /dev/null @@ -1,186 +0,0 @@ - - - - - Debug - AnyCPU - - - 2.0 - {429D865C-EDE7-4EA1-BC51-9A5BCAB26BB8} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - WebApiNet48 - WebApiNet48 - v4.8 - true - - 44304 - - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - true - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll - - - - ..\packages\Microsoft.Extensions.DependencyInjection.3.1.9\lib\net461\Microsoft.Extensions.DependencyInjection.dll - - - ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - - ..\packages\NLog.4.7.5\lib\net45\NLog.dll - - - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll - - - - - ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - - - - - - - - - - - - - - - - - - - - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll - - - ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - - - - - - - - - - - - - - Global.asax - - - - - - - - - - - - - - - - PreserveNewest - - - Designer - - - - Web.config - - - Web.config - - - - - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - True - True - 50548 - / - https://localhost:44304/ - False - False - - - False - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj.DotSettings b/DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj.DotSettings deleted file mode 100644 index 8a5228f0..00000000 --- a/DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/packages.config b/DI/Lab.MsDI/WebApiNet48/packages.config deleted file mode 100644 index e8fcf0e2..00000000 --- a/DI/Lab.MsDI/WebApiNet48/packages.config +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Controllers/Default1Controller.cs b/DI/Lab.MsDI/WebApiNetCore31/Controllers/Default1Controller.cs deleted file mode 100644 index 38dc48ee..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/Controllers/Default1Controller.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace WebApiNetCore31.Controllers -{ - [ApiController] - [Route("[controller]")] - public class Default1Controller : ControllerBase - { - private readonly ILogger _logger; - - public Default1Controller(ILogger logger) - { - this._logger = logger; - } - - [HttpGet] - public IActionResult Get() - { - var serviceProvider = this.HttpContext.RequestServices; - var transient = serviceProvider.GetService(typeof(ITransientMessager)) as ITransientMessager; - var scope = serviceProvider.GetService(typeof(IScopeMessager)) as IScopeMessager; - var single = serviceProvider.GetService(typeof(ISingleMessager)) as ISingleMessager; - var content = $"transient:{transient.OperationId}\r\n" + - $"scope:{scope.OperationId}\r\n" + - $"single:{single.OperationId}"; - - this._logger.LogInformation("transient = {transient},scope = {scope},single = {single}", - transient.OperationId, - scope.OperationId, - single.OperationId); - return this.Ok(content); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Controllers/DefaultController.cs b/DI/Lab.MsDI/WebApiNetCore31/Controllers/DefaultController.cs deleted file mode 100644 index 2fe6ac31..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/Controllers/DefaultController.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace WebApiNetCore31.Controllers -{ - [ApiController] - [Route("[controller]")] - public class DefaultController : ControllerBase - { - private IMessager Transient { get; } - - private IMessager Scope { get; } - - private IMessager Single { get; } - - private readonly ILogger _logger; - - public DefaultController(ILogger logger, - ITransientMessager transient, - IScopeMessager scope, - ISingleMessager single) - { - this._logger = logger; - - this.Transient = transient; - this.Scope = scope; - this.Single = single; - } - - [HttpGet] - public IActionResult Get() - { - var content = "我在 DefaultController.Get \r\n" + - $"transient:{this.Transient.OperationId}\r\n" + - $"scope:{this.Scope.OperationId}\r\n" + - $"single:{this.Single.OperationId}"; - this._logger.LogInformation("我在 DefaultController.Get ,transient = {transient},scope = {scope},single = {single}", - this.Transient.OperationId, - this.Scope.OperationId, - this.Single.OperationId); - return this.Ok(content); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/LogFilterAttribute.cs b/DI/Lab.MsDI/WebApiNetCore31/LogFilterAttribute.cs deleted file mode 100644 index 72b0c840..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/LogFilterAttribute.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace WebApiNetCore31 -{ - public class LogFilterAttribute : ActionFilterAttribute - { - public LogFilterAttribute() - { - - } - public override void OnActionExecuting(ActionExecutingContext context) - { - var transient = context.HttpContext.RequestServices.GetService(); - var scope = context.HttpContext.RequestServices.GetService(); - var single = context.HttpContext.RequestServices.GetService(); - var logger = context.HttpContext.RequestServices.GetService>(); - logger.LogInformation("我在 LogFilterAttribute ,transient = {transient},scope = {scope},single = {single}", - transient.OperationId, - scope.OperationId, - single.OperationId); - - //Console.WriteLine(content); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/IMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/IMessager.cs deleted file mode 100644 index 479144a9..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/Message/IMessager.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace WebApiNetCore31 -{ - public interface IMessager:IDisposable - { - string OperationId { get; } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/IScopeMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/IScopeMessager.cs deleted file mode 100644 index 101c08fa..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/Message/IScopeMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace WebApiNetCore31 -{ - public interface IScopeMessager : IMessager - { - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/ISingleMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/ISingleMessager.cs deleted file mode 100644 index e803b8c9..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/Message/ISingleMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace WebApiNetCore31 -{ - public interface ISingleMessager : IMessager - { - } -} diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/ITransientMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/ITransientMessager.cs deleted file mode 100644 index b152a276..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/Message/ITransientMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace WebApiNetCore31 -{ - public interface ITransientMessager : IMessager - { - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/LogMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/LogMessager.cs deleted file mode 100644 index 9819ac66..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/Message/LogMessager.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace WebApiNetCore31 -{ - internal class LogMessager : IMessager - { - public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; - - public void Dispose() - { - Console.WriteLine($"{nameof(LogMessager)} GC"); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/MachineMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/MachineMessager.cs deleted file mode 100644 index ed49b5dd..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/Message/MachineMessager.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace WebApiNetCore31 -{ - internal class MachineMessager : IMessager - { - public string OperationId { get; } = $"機器-{Guid.NewGuid()}"; - public void Dispose() - { - Console.WriteLine($"{nameof(MachineMessager)} GC"); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/MultiMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/MultiMessager.cs deleted file mode 100644 index 68182ed0..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/Message/MultiMessager.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace WebApiNetCore31 -{ - public class MultiMessager : IScopeMessager, ISingleMessager, ITransientMessager - { - public string OperationId { get; } = $"多個接口-{Guid.NewGuid()}"; - - public void Dispose() - { - Console.WriteLine($"{nameof(MultiMessager)} GC"); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Program.cs b/DI/Lab.MsDI/WebApiNetCore31/Program.cs deleted file mode 100644 index aba702a0..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/Program.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace WebApiNetCore31 -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} diff --git a/DI/Lab.MsDI/WebApiNetCore31/Properties/launchSettings.json b/DI/Lab.MsDI/WebApiNetCore31/Properties/launchSettings.json deleted file mode 100644 index e27ff49d..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/Properties/launchSettings.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:55458", - "sslPort": 44311 - } - }, - "$schema": "http://json.schemastore.org/launchsettings.json", - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "default", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "WebApiNetCore31": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "weatherforecast", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Startup.cs b/DI/Lab.MsDI/WebApiNetCore31/Startup.cs deleted file mode 100644 index 60390753..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/Startup.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace WebApiNetCore31 -{ - public class Startup - { - public IConfiguration Configuration { get; } - - public Startup(IConfiguration configuration) - { - this.Configuration = configuration; - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - app.UseHttpsRedirection(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); - } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - //services.AddControllers(); - services.AddControllers(options => options.Filters.Add(new LogFilterAttribute())); - - services.AddTransient(p => new Worker(new LogMessager())); - services.AddTransient(p => new Worker2(new MachineMessager())); - - services.AddTransient() - .AddSingleton() - .AddScoped() - ; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/WeatherForecast.cs b/DI/Lab.MsDI/WebApiNetCore31/WeatherForecast.cs deleted file mode 100644 index 1bb9e27c..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/WeatherForecast.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace WebApiNetCore31 -{ - public class WeatherForecast - { - public DateTime Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string Summary { get; set; } - } -} diff --git a/DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj b/DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj deleted file mode 100644 index d12c450b..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - netcoreapp3.1 - - - - diff --git a/DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings b/DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings deleted file mode 100644 index 8a5228f0..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Worker.cs b/DI/Lab.MsDI/WebApiNetCore31/Worker.cs deleted file mode 100644 index f069f7e4..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/Worker.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace WebApiNetCore31 -{ - public class Worker - { - public IMessager Messager { get; set; } - - public Worker(IMessager messager) - { - this.Messager = messager; - } - } - - public class Worker2 - { - public IMessager Messager { get; set; } - - public Worker2(IMessager messager) - { - this.Messager = messager; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/appsettings.Development.json b/DI/Lab.MsDI/WebApiNetCore31/appsettings.Development.json deleted file mode 100644 index 8983e0fc..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/DI/Lab.MsDI/WebApiNetCore31/appsettings.json b/DI/Lab.MsDI/WebApiNetCore31/appsettings.json deleted file mode 100644 index d9d9a9bf..00000000 --- a/DI/Lab.MsDI/WebApiNetCore31/appsettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*" -} diff --git a/DI/Lab.MsDI/WebApiOwinNet48/App.config b/DI/Lab.MsDI/WebApiOwinNet48/App.config deleted file mode 100644 index 4a79e878..00000000 --- a/DI/Lab.MsDI/WebApiOwinNet48/App.config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/AppSetting.cs b/DI/Lab.MsDI/WebApiOwinNet48/AppSetting.cs deleted file mode 100644 index cf2c1518..00000000 --- a/DI/Lab.MsDI/WebApiOwinNet48/AppSetting.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace WebApiOwinNet48 -{ - public class ServerSetting - { - public const string HostEndpoint = "http://localhost:8001"; - - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/App_Start/DefaultDependencyResolver.cs b/DI/Lab.MsDI/WebApiOwinNet48/App_Start/DefaultDependencyResolver.cs deleted file mode 100644 index d4d14983..00000000 --- a/DI/Lab.MsDI/WebApiOwinNet48/App_Start/DefaultDependencyResolver.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web.Http.Dependencies; -using Microsoft.Extensions.DependencyInjection; - -namespace WebApiOwinNet48 -{ - public class DefaultDependencyResolver : IDependencyResolver - { - protected IServiceProvider ServiceProvider { get; set; } - - public DefaultDependencyResolver(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public object GetService(Type serviceType) - { - return this.ServiceProvider.GetService(serviceType); - } - - public IEnumerable GetServices(Type serviceType) - { - return this.ServiceProvider.GetServices(serviceType); - } - - public IDependencyScope BeginScope() - { - return new DefaultDependencyResolver(this.ServiceProvider.CreateScope().ServiceProvider); - } - - public void Dispose() - { - // you can implement this interface just when you use .net core 2.0 - // this.ServiceProvider.Dispose(); - ((ServiceProvider) this.ServiceProvider).Dispose(); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/App_Start/DependencyInjectionConfig.cs b/DI/Lab.MsDI/WebApiOwinNet48/App_Start/DependencyInjectionConfig.cs deleted file mode 100644 index 0bb16012..00000000 --- a/DI/Lab.MsDI/WebApiOwinNet48/App_Start/DependencyInjectionConfig.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Web.Http; -using Microsoft.Extensions.DependencyInjection; - -namespace WebApiOwinNet48 -{ - public class DependencyInjectionConfig - { - public static void Register(HttpConfiguration config) - { - var services = ConfigureServices(); - - var provider = services.BuildServiceProvider(); - - var resolver = new DefaultDependencyResolver(provider); - config.DependencyResolver = resolver; - } - - /// - /// 使用 MS DI 註冊 - /// - /// - private static ServiceCollection ConfigureServices() - { - var services = new ServiceCollection(); - - //使用 Microsoft.Extensions.DependencyInjection 註冊 - services.AddControllersAsServices(typeof(DependencyInjectionConfig).Assembly.GetExportedTypes()); - - services.AddScoped(); - - return services; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/App_Start/ServiceProviderExtensions.cs b/DI/Lab.MsDI/WebApiOwinNet48/App_Start/ServiceProviderExtensions.cs deleted file mode 100644 index ffb8667b..00000000 --- a/DI/Lab.MsDI/WebApiOwinNet48/App_Start/ServiceProviderExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Http.Controllers; -using Microsoft.Extensions.DependencyInjection; - -namespace WebApiOwinNet48 -{ - public static class ServiceProviderExtensions - { - public static IServiceCollection AddControllersAsServices(this IServiceCollection services, - IEnumerable controllerTypes) - { - var filter = controllerTypes.Where(t => !t.IsAbstract - && !t.IsGenericTypeDefinition) - .Where(t => typeof(IHttpController).IsAssignableFrom(t) - || t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)); - - foreach (var type in filter) - { - services.AddTransient(type); - } - - return services; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/App_Start/WebApiConfig.cs b/DI/Lab.MsDI/WebApiOwinNet48/App_Start/WebApiConfig.cs deleted file mode 100644 index 3a04e4e2..00000000 --- a/DI/Lab.MsDI/WebApiOwinNet48/App_Start/WebApiConfig.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Web.Http; - -namespace WebApiOwinNet48 -{ - public static class WebApiConfig - { - public static void Register(HttpConfiguration config) - { - // Web API configuration and services - // config.Filters.Add(new LogFilterAttribute()); - - // Web API routes - config.MapHttpAttributeRoutes(); - - config.Routes.MapHttpRoute( - name: "DefaultApi", - routeTemplate: "api/{controller}/{id}", - defaults: new { id = RouteParameter.Optional } - ); - - } - } -} diff --git a/DI/Lab.MsDI/WebApiOwinNet48/Commander.cs b/DI/Lab.MsDI/WebApiOwinNet48/Commander.cs deleted file mode 100644 index 7a1b5afa..00000000 --- a/DI/Lab.MsDI/WebApiOwinNet48/Commander.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace WebApiOwinNet48 -{ - public class Commander - { - public Guid Id { get; set; } = Guid.NewGuid(); - public string Get() - { - var msg = "GG"; - Console.WriteLine(msg); - return msg; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/Controllers/DefaultController.cs b/DI/Lab.MsDI/WebApiOwinNet48/Controllers/DefaultController.cs deleted file mode 100644 index 99e3277a..00000000 --- a/DI/Lab.MsDI/WebApiOwinNet48/Controllers/DefaultController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Web.Http; - -namespace WebApiOwinNet48.Controllers -{ - public class DefaultController : ApiController - { - private Commander _cmder; - - public DefaultController(Commander cmder) - { - this._cmder = cmder; - } - - // GET - public async Task Get() - { - // this._cmder.Get(); - Console.WriteLine($"我在 DefaultController.Get ,Command.Id = {this._cmder.Id}"); - return this.Ok(this._cmder); - } - - private class Member - { - public Guid Id { get; set; } - - public int Age { get; set; } - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/LabMiddleware.cs b/DI/Lab.MsDI/WebApiOwinNet48/LabMiddleware.cs deleted file mode 100644 index 3811e5f3..00000000 --- a/DI/Lab.MsDI/WebApiOwinNet48/LabMiddleware.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading.Tasks; -using Microsoft.Owin; -using AppFunc = System.Func, System.Threading.Tasks.Task>; - -namespace WebApiOwinNet48 -{ - public class LabMiddleware1 : OwinMiddleware - { - private readonly Commander _cmder; - - public LabMiddleware1(OwinMiddleware next, Commander cmder) : base(next) - { - this._cmder = this._cmder; - } - - // public LabMiddleware(OwinMiddleware next) : base(next) - // { - // } - - public override Task Invoke(IOwinContext context) - { - Console.WriteLine("我在 LabMiddleware.Invoke"); - - // Console.WriteLine($"我在 LabMiddleware.Invoke ,Command.Id = {this._cmder.Id}"); - return this.Next.Invoke(context); - } - } - - - public class LabMiddleware - { - private readonly AppFunc _next; - - public LabMiddleware(AppFunc next,Commander cmder) - { - if (next == null) - { - throw new ArgumentNullException("next"); - } - - this._next = next; - } - - public async Task Invoke(IDictionary environment) - { - try - { - await this._next(environment); - } - catch (Exception ex) - { - var owinContext = new OwinContext(environment); - - this.ErrorHandle(ex, owinContext); - } - } - - private void ErrorHandle(Exception ex, IOwinContext context) - { - context.Response.StatusCode = (int) HttpStatusCode.InternalServerError; - context.Response.ReasonPhrase = "Internal Server Error"; - context.Response.ContentType = "application/json"; - context.Response.Write(ex.Message); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/Program.cs b/DI/Lab.MsDI/WebApiOwinNet48/Program.cs deleted file mode 100644 index c2b4659a..00000000 --- a/DI/Lab.MsDI/WebApiOwinNet48/Program.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using Microsoft.Owin.Hosting; - -namespace WebApiOwinNet48 -{ - internal class Program - { - private static void Main(string[] args) - { - using (WebApp.Start(ServerSetting.HostEndpoint)) - { - Console.WriteLine($"伺服器已啟動, 位置:{ServerSetting.HostEndpoint}"); - Console.WriteLine("按下任意建離開應用程式"); - Console.ReadLine(); - } - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/Properties/AssemblyInfo.cs b/DI/Lab.MsDI/WebApiOwinNet48/Properties/AssemblyInfo.cs deleted file mode 100644 index 8a1889ce..00000000 --- a/DI/Lab.MsDI/WebApiOwinNet48/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("WebApiOwinNet48")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("WebApiOwinNet48")] -[assembly: AssemblyCopyright("Copyright © 2021")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("680ec3cf-cfe2-48f9-8006-f6a3d9b5dc42")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DI/Lab.MsDI/WebApiOwinNet48/Startup.cs b/DI/Lab.MsDI/WebApiOwinNet48/Startup.cs deleted file mode 100644 index b76ef16c..00000000 --- a/DI/Lab.MsDI/WebApiOwinNet48/Startup.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Web.Http; -using Owin; - -namespace WebApiOwinNet48 -{ - public class Startup - { - public void Configuration(IAppBuilder app) - { - var httpConfig = new HttpConfiguration(); - DependencyInjectionConfig.Register(httpConfig); - WebApiConfig.Register(httpConfig); - //app.UseErrorPage(); - //app.UseWelcomePage("/Welcome"); - // app.Use(); - app.UseWebApi(httpConfig); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/WebApiOwinNet48.csproj b/DI/Lab.MsDI/WebApiOwinNet48/WebApiOwinNet48.csproj deleted file mode 100644 index dbd189c3..00000000 --- a/DI/Lab.MsDI/WebApiOwinNet48/WebApiOwinNet48.csproj +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Debug - AnyCPU - {680EC3CF-CFE2-48F9-8006-F6A3D9B5DC42} - Exe - WebApiOwinNet48 - WebApiOwinNet48 - v4.8 - 512 - true - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll - True - - - ..\packages\Microsoft.Extensions.DependencyInjection.3.1.9\lib\net461\Microsoft.Extensions.DependencyInjection.dll - True - - - ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - True - - - ..\packages\Microsoft.Owin.4.1.1\lib\net45\Microsoft.Owin.dll - - - ..\packages\Microsoft.Owin.Diagnostics.4.1.1\lib\net45\Microsoft.Owin.Diagnostics.dll - - - ..\packages\Microsoft.Owin.Host.HttpListener.2.0.2\lib\net45\Microsoft.Owin.Host.HttpListener.dll - - - ..\packages\Microsoft.Owin.Hosting.2.0.2\lib\net45\Microsoft.Owin.Hosting.dll - - - - ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - - - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll - True - - - ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - True - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.Owin.5.2.7\lib\net45\System.Web.Http.Owin.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/packages.config b/DI/Lab.MsDI/WebApiOwinNet48/packages.config deleted file mode 100644 index 4d964e03..00000000 --- a/DI/Lab.MsDI/WebApiOwinNet48/packages.config +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/App.config b/DI/Lab.MsDI/WinFormNet48/App.config deleted file mode 100644 index 1a57e048..00000000 --- a/DI/Lab.MsDI/WinFormNet48/App.config +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/DependencyInjectionConfig.cs b/DI/Lab.MsDI/WinFormNet48/DependencyInjectionConfig.cs deleted file mode 100644 index 00209ab0..00000000 --- a/DI/Lab.MsDI/WinFormNet48/DependencyInjectionConfig.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; - -namespace WinFormNet48 -{ - internal class DependencyInjectionConfig - { - public static IServiceProvider ServiceProvider { get; set; } - - public static IServiceProvider Register() - { - var services = new ServiceCollection(); - ConfigureServices(services); - ServiceProvider = services.BuildServiceProvider(); - return ServiceProvider; - } - - /// - /// 使用 MS DI 註冊 - /// - private static IServiceCollection ConfigureServices(IServiceCollection services) - { - return services.AddSingleton() - .AddTransient() - .AddTransient() - .AddSingleton() - .AddScoped() - - //.AddTransient(provider => - // { - // var operation = provider.GetRequiredService(); - // return new Worker(operation); - // }) - //.AddTransient(provider => - // { - // var operation = provider.GetRequiredService(); - // return new Workflow(operation); - // }) - - //.AddTransient() - //.AddTransient() - //.AddLogging(loggingBuilder => - // { - // // configure Logging with NLog - // loggingBuilder.ClearProviders(); - // loggingBuilder.SetMinimumLevel(LogLevel.Trace); - // loggingBuilder.AddNLog(config); - // }) - ; - - ; - } - - //private static IConfiguration CreateConfig() - //{ - // var config = new ConfigurationBuilder() - // .SetBasePath(System.IO.Directory - // .GetCurrentDirectory()) //From NuGet Package Microsoft.Extensions.Configuration.Json - // .AddJsonFile("appsettings.json", true, true) - // .Build(); - // return config; - //} - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Form1.Designer.cs b/DI/Lab.MsDI/WinFormNet48/Form1.Designer.cs deleted file mode 100644 index abde6f86..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Form1.Designer.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace WinFormNet48 -{ - partial class Form1 - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.button1 = new System.Windows.Forms.Button(); - this.SuspendLayout(); - // - // button1 - // - this.button1.Location = new System.Drawing.Point(47, 64); - this.button1.Name = "button1"; - this.button1.Size = new System.Drawing.Size(75, 23); - this.button1.TabIndex = 0; - this.button1.Text = "button1"; - this.button1.UseVisualStyleBackColor = true; - this.button1.Click += new System.EventHandler(this.button1_Click); - // - // Form1 - // - this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(800, 450); - this.Controls.Add(this.button1); - this.Name = "Form1"; - this.Text = "Form1"; - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.Button button1; - } -} - diff --git a/DI/Lab.MsDI/WinFormNet48/Form1.cs b/DI/Lab.MsDI/WinFormNet48/Form1.cs deleted file mode 100644 index 3c3e1406..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Form1.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Windows.Forms; -using Microsoft.Extensions.DependencyInjection; - -namespace WinFormNet48 -{ - public partial class Form1 : Form - { - public Form1() - { - this.InitializeComponent(); - } - - private void button1_Click(object sender, EventArgs e) - { - var serviceProvider = DependencyInjectionConfig.ServiceProvider; - var work = serviceProvider.GetRequiredService(); - Console.WriteLine(work.Get()); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Form1.resx b/DI/Lab.MsDI/WinFormNet48/Form1.resx deleted file mode 100644 index 1af7de15..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Form1.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Message/IMessager.cs b/DI/Lab.MsDI/WinFormNet48/Message/IMessager.cs deleted file mode 100644 index 741af8b8..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Message/IMessager.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace WinFormNet48 -{ - public interface IMessager - { - string OperationId { get; } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Message/IScopeMessager.cs b/DI/Lab.MsDI/WinFormNet48/Message/IScopeMessager.cs deleted file mode 100644 index 0241069d..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Message/IScopeMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace WinFormNet48 -{ - public interface IScopeMessager : IMessager - { - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Message/ISingleMessager.cs b/DI/Lab.MsDI/WinFormNet48/Message/ISingleMessager.cs deleted file mode 100644 index 76e8fce5..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Message/ISingleMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace WinFormNet48 -{ - public interface ISingleMessager : IMessager - { - } -} diff --git a/DI/Lab.MsDI/WinFormNet48/Message/ITransientMessager.cs b/DI/Lab.MsDI/WinFormNet48/Message/ITransientMessager.cs deleted file mode 100644 index 0c0d795b..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Message/ITransientMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace WinFormNet48 -{ - public interface ITransientMessager : IMessager - { - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Message/LogMessager.cs b/DI/Lab.MsDI/WinFormNet48/Message/LogMessager.cs deleted file mode 100644 index 7e18917a..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Message/LogMessager.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace WinFormNet48 -{ - internal class LogMessager : IMessager - { - public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Message/MachineMessager.cs b/DI/Lab.MsDI/WinFormNet48/Message/MachineMessager.cs deleted file mode 100644 index 2d0c9de3..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Message/MachineMessager.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace WinFormNet48 -{ - internal class MachineMessager : IMessager - { - public string OperationId { get; } = $"機器-{Guid.NewGuid()}"; - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Message/MultiMessager.cs b/DI/Lab.MsDI/WinFormNet48/Message/MultiMessager.cs deleted file mode 100644 index 878b27bf..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Message/MultiMessager.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace WinFormNet48 -{ - public class MultiMessager : IScopeMessager, ISingleMessager, ITransientMessager - { - public string OperationId { get; } = $"多個接口-{Guid.NewGuid()}"; - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Program.cs b/DI/Lab.MsDI/WinFormNet48/Program.cs deleted file mode 100644 index 9f3dda6e..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Program.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Windows.Forms; -using Microsoft.Extensions.DependencyInjection; - -namespace WinFormNet48 -{ - internal static class Program - { - /// - /// The main entry point for the application. - /// - [STAThread] - private static void Main() - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - var serviceProvider = DependencyInjectionConfig.Register() as ServiceProvider; - - using (serviceProvider) - { - var form = serviceProvider.GetService(typeof(Form1)) as Form; - Application.Run(form); - } - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Properties/AssemblyInfo.cs b/DI/Lab.MsDI/WinFormNet48/Properties/AssemblyInfo.cs deleted file mode 100644 index b7aebc8e..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("WinFormNet48")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("WinFormNet48")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("fc5aa11a-0879-43d8-8e79-dfc37de75fb8")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DI/Lab.MsDI/WinFormNet48/Properties/Resources.Designer.cs b/DI/Lab.MsDI/WinFormNet48/Properties/Resources.Designer.cs deleted file mode 100644 index ca98cf3f..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Properties/Resources.Designer.cs +++ /dev/null @@ -1,71 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace WinFormNet48.Properties -{ - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WinFormNet48.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { - return resourceCulture; - } - set - { - resourceCulture = value; - } - } - } -} diff --git a/DI/Lab.MsDI/WinFormNet48/Properties/Resources.resx b/DI/Lab.MsDI/WinFormNet48/Properties/Resources.resx deleted file mode 100644 index af7dbebb..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Properties/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Properties/Settings.Designer.cs b/DI/Lab.MsDI/WinFormNet48/Properties/Settings.Designer.cs deleted file mode 100644 index 8411c43f..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Properties/Settings.Designer.cs +++ /dev/null @@ -1,30 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace WinFormNet48.Properties -{ - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { - return defaultInstance; - } - } - } -} diff --git a/DI/Lab.MsDI/WinFormNet48/Properties/Settings.settings b/DI/Lab.MsDI/WinFormNet48/Properties/Settings.settings deleted file mode 100644 index 39645652..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj b/DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj deleted file mode 100644 index 34b52830..00000000 --- a/DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj +++ /dev/null @@ -1,113 +0,0 @@ - - - - - Debug - AnyCPU - {FC5AA11A-0879-43D8-8E79-DFC37DE75FB8} - Exe - WinFormNet48 - WinFormNet48 - v4.8 - 512 - true - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll - - - ..\packages\Microsoft.Extensions.DependencyInjection.3.1.9\lib\net461\Microsoft.Extensions.DependencyInjection.dll - - - ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll - - - ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - - - - - - - - - - - - - - - - - - - Form - - - Form1.cs - - - - - - - - - - Form1.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj.DotSettings b/DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj.DotSettings deleted file mode 100644 index 8a5228f0..00000000 --- a/DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Worker.cs b/DI/Lab.MsDI/WinFormNet48/Worker.cs deleted file mode 100644 index d6bcf3f2..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Worker.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace WinFormNet48 -{ - public class Worker - { - private IMessager Transient { get; } - - private IMessager Scope { get; } - - private IMessager Single { get; } - - public Worker(ITransientMessager transient, IScopeMessager scope, ISingleMessager single) - { - this.Transient = transient; - this.Scope = scope; - this.Single = single; - } - - public string Get() - { - return $"transient:{this.Transient.OperationId}\r\n" + - $"scope:{this.Scope.OperationId}\r\n" + - $"single:{this.Single.OperationId}"; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Workflow.cs b/DI/Lab.MsDI/WinFormNet48/Workflow.cs deleted file mode 100644 index 8298237c..00000000 --- a/DI/Lab.MsDI/WinFormNet48/Workflow.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace WinFormNet48 -{ - public class Workflow - { - public Workflow(IMessager operation) - { - Console.WriteLine(operation.OperationId); - } - } -} diff --git a/DI/Lab.MsDI/WinFormNet48/packages.config b/DI/Lab.MsDI/WinFormNet48/packages.config deleted file mode 100644 index a2075034..00000000 --- a/DI/Lab.MsDI/WinFormNet48/packages.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/Lab.MsDIForAutofac.sln b/DI/Lab.MsDIForAutofac/Lab.MsDIForAutofac.sln deleted file mode 100644 index 7a83e88b..00000000 --- a/DI/Lab.MsDIForAutofac/Lab.MsDIForAutofac.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30503.244 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiNetCore31", "WebApiNetCore31\WebApiNetCore31.csproj", "{B5E0DA79-326E-41CB-A95C-0C7FFDC70FF0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiNet48", "WebApiNet48\WebApiNet48.csproj", "{9EA9B67E-7812-41CB-899B-4331B5344882}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B5E0DA79-326E-41CB-A95C-0C7FFDC70FF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B5E0DA79-326E-41CB-A95C-0C7FFDC70FF0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B5E0DA79-326E-41CB-A95C-0C7FFDC70FF0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B5E0DA79-326E-41CB-A95C-0C7FFDC70FF0}.Release|Any CPU.Build.0 = Release|Any CPU - {9EA9B67E-7812-41CB-899B-4331B5344882}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9EA9B67E-7812-41CB-899B-4331B5344882}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9EA9B67E-7812-41CB-899B-4331B5344882}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9EA9B67E-7812-41CB-899B-4331B5344882}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {526A154E-E406-4F6B-A76D-4455CA7B02B1} - EndGlobalSection -EndGlobal diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DefaultDependencyResolver.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DefaultDependencyResolver.cs deleted file mode 100644 index ea9b067e..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DefaultDependencyResolver.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web.Http.Dependencies; -using Microsoft.Extensions.DependencyInjection; - -namespace WebApiNet48 -{ - public class DefaultDependencyResolver : IDependencyResolver - { - protected IServiceProvider ServiceProvider { get; set; } - - public DefaultDependencyResolver(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public object GetService(Type serviceType) - { - return this.ServiceProvider.GetService(serviceType); - } - - public IEnumerable GetServices(Type serviceType) - { - return this.ServiceProvider.GetServices(serviceType); - } - - public IDependencyScope BeginScope() - { - return new DefaultDependencyResolver(this.ServiceProvider.CreateScope().ServiceProvider); - } - - public void Dispose() - { - // you can implement this interface just when you use .net core 2.0 - // this.ServiceProvider.Dispose(); - ((ServiceProvider)this.ServiceProvider).Dispose(); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DependencyInjectionConfig.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DependencyInjectionConfig.cs deleted file mode 100644 index 343599eb..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DependencyInjectionConfig.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Web.Http; -using System.Web.Http.Controllers; -using Autofac; -using Autofac.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection; - -namespace WebApiNet48 -{ - public class DependencyInjectionConfig - { - public static void Register(HttpConfiguration config) - { - var services = ConfigureServices(); - var builder = ConfigureContainerBuilder(services); - var provider = new AutofacServiceProvider(builder.Build()); - - //var provider = services.BuildServiceProvider(); - - var resolver = new DefaultDependencyResolver(provider); - config.DependencyResolver = resolver; - } - - /// - /// 使用 Autofac 註冊 - /// - /// - /// - private static ContainerBuilder ConfigureContainerBuilder(IServiceCollection services) - { - var builder = new ContainerBuilder(); - builder.Populate(services); - - var assembly = Assembly.GetExecutingAssembly(); - builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces(); - - return builder; - } - - /// - /// 使用 MS DI 註冊 - /// - /// - private static ServiceCollection ConfigureServices() - { - var services = new ServiceCollection(); - - //使用 Microsoft.Extensions.DependencyInjection 註冊 - services.AddControllersAsServices(typeof(DependencyInjectionConfig) - .Assembly - .GetExportedTypes() - .Where(t => !t.IsAbstract && !t.IsGenericTypeDefinition) - .Where(t => typeof(IHttpController).IsAssignableFrom(t) - || t.Name.EndsWith("Controller", - StringComparison.OrdinalIgnoreCase))); - - //services.AddScoped(); - - //services.AddTransient() - // .AddSingleton() - // .AddScoped(); - return services; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/ServiceProviderExtensions.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/ServiceProviderExtensions.cs deleted file mode 100644 index 614d3991..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/ServiceProviderExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; - -namespace WebApiNet48 -{ - public static class ServiceProviderExtensions - { - public static IServiceCollection AddControllersAsServices(this IServiceCollection services, - IEnumerable controllerTypes) - { - foreach (var type in controllerTypes) - { - services.AddTransient(type); - } - - return services; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/WebApiConfig.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/WebApiConfig.cs deleted file mode 100644 index cd67649d..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/WebApiConfig.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Http; - -namespace WebApiNet48 -{ - public static class WebApiConfig - { - public static void Register(HttpConfiguration config) - { - DependencyInjectionConfig.Register(config); - // Web API configuration and services - - // Web API routes - config.MapHttpAttributeRoutes(); - - config.Routes.MapHttpRoute( - name: "DefaultApi", - routeTemplate: "api/{controller}/{id}", - defaults: new { id = RouteParameter.Optional } - ); - } - } -} diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Controllers/DefaultController.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/Controllers/DefaultController.cs deleted file mode 100644 index a2030733..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/Controllers/DefaultController.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Net.Http; -using System.Web.Http; - -namespace WebApiNet48.Controllers -{ - public class DefaultController : ApiController - { - private IMessager Messager { get; set; } - - public DefaultController(IMessager messager) - { - this.Messager = messager; - } - - [HttpGet] - public IHttpActionResult Get() - { - var content = $"Messager:{this.Messager.OperationId}"; - return this.Ok(content); - } - - [HttpGet] - public IHttpActionResult Get1() - { - var messager = InstanceManager.Messager; - - var content = $"Messager:{messager.OperationId}"; - return this.Ok(content); - } - } - - public class InstanceManager - { - public static IMessager Messager { get; set; } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax b/DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax deleted file mode 100644 index 593fb3aa..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax +++ /dev/null @@ -1 +0,0 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="WebApiNet48.WebApiApplication" Language="C#" %> diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax.cs deleted file mode 100644 index d3bb6c7b..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Http; -using System.Web.Routing; - -namespace WebApiNet48 -{ - public class WebApiApplication : System.Web.HttpApplication - { - protected void Application_Start() - { - GlobalConfiguration.Configure(WebApiConfig.Register); - } - } -} diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Message/IMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/Message/IMessager.cs deleted file mode 100644 index 137ae92c..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/Message/IMessager.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace WebApiNet48 -{ - public interface IMessager - { - string OperationId { get; } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Message/LogMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/Message/LogMessager.cs deleted file mode 100644 index 91e273eb..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/Message/LogMessager.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace WebApiNet48 -{ - internal class LogMessager : IMessager - { - public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Properties/AssemblyInfo.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/Properties/AssemblyInfo.cs deleted file mode 100644 index e6621e86..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("WebApiNet48")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("WebApiNet48")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9ea9b67e-7812-41cb-899b-4331b5344882")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/ServiceCollectionExtensions.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/ServiceCollectionExtensions.cs deleted file mode 100644 index 7f5ea374..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using Autofac; -using Autofac.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection; - -namespace WebApiNet48 -{ - public static class ServiceCollectionExtensions - { - /// - /// Adds the to the service collection. ONLY FOR PRE-ASP.NET 3.0 HOSTING. THIS WON'T WORK - /// FOR ASP.NET CORE 3.0+ OR GENERIC HOSTING. - /// - /// The service collection to add the factory to. - /// Action on a that adds component registrations to the container. - /// The service collection. - public static IServiceCollection AddAutofac(this IServiceCollection services, Action configurationAction = null) - { - return services.AddSingleton>(new AutofacServiceProviderFactory(configurationAction)); - } - } - -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Web.Debug.config b/DI/Lab.MsDIForAutofac/WebApiNet48/Web.Debug.config deleted file mode 100644 index fae9cfef..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Web.Release.config b/DI/Lab.MsDIForAutofac/WebApiNet48/Web.Release.config deleted file mode 100644 index da6e960b..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Web.config b/DI/Lab.MsDIForAutofac/WebApiNet48/Web.config deleted file mode 100644 index 89be7bd4..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/Web.config +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj b/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj deleted file mode 100644 index f8a73be7..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj +++ /dev/null @@ -1,184 +0,0 @@ - - - - - Debug - AnyCPU - - - 2.0 - {9EA9B67E-7812-41CB-899B-4331B5344882} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - WebApiNet48 - WebApiNet48 - v4.8 - true - - 44327 - - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - true - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - ..\packages\Autofac.6.0.0\lib\netstandard2.0\Autofac.dll - - - ..\packages\Autofac.Extensions.DependencyInjection.7.1.0\lib\netstandard2.0\Autofac.Extensions.DependencyInjection.dll - - - ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll - - - - ..\packages\Microsoft.Extensions.DependencyInjection.3.1.9\lib\net461\Microsoft.Extensions.DependencyInjection.dll - - - ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - - ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll - - - ..\packages\System.Diagnostics.DiagnosticSource.4.7.1\lib\net46\System.Diagnostics.DiagnosticSource.dll - - - ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll - - - - - ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll - - - ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - - - - - - - - - - - - - - - - - - - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll - - - ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - - - - - - - - - - - - - - - - Global.asax - - - - - - - Web.config - - - Web.config - - - - - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - True - True - 54526 - / - https://localhost:44327/ - False - False - - - False - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj.DotSettings b/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj.DotSettings deleted file mode 100644 index 8a5228f0..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/packages.config b/DI/Lab.MsDIForAutofac/WebApiNet48/packages.config deleted file mode 100644 index f90d917a..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNet48/packages.config +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/DefaultController.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/DefaultController.cs deleted file mode 100644 index 3aa6d5a3..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/DefaultController.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace WebApiNetCore31.Controllers -{ - [ApiController] - [Route("[controller]")] - public class DefaultController : ControllerBase - { - private IMessager Messager { get; } - - private readonly ILogger _logger; - - public DefaultController(ILogger logger, - IMessager messager - ) - { - this._logger = logger; - this.Messager = messager; - } - - [HttpGet] - public IActionResult Get() - { - var content = $"Messager:{this.Messager.OperationId}"; - this._logger.LogInformation("Messager:{message}", content); - return this.Ok(content); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IMessager.cs deleted file mode 100644 index 07685fa7..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IMessager.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace WebApiNetCore31 -{ - public interface IMessager - { - string OperationId { get; } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IScopeMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IScopeMessager.cs deleted file mode 100644 index 101c08fa..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IScopeMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace WebApiNetCore31 -{ - public interface IScopeMessager : IMessager - { - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ISingleMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ISingleMessager.cs deleted file mode 100644 index e803b8c9..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ISingleMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace WebApiNetCore31 -{ - public interface ISingleMessager : IMessager - { - } -} diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ITransientMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ITransientMessager.cs deleted file mode 100644 index b152a276..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ITransientMessager.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace WebApiNetCore31 -{ - public interface ITransientMessager : IMessager - { - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/LogMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/LogMessager.cs deleted file mode 100644 index 633adcba..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/LogMessager.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace WebApiNetCore31 -{ - internal class LogMessager : IMessager - { - public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MachineMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MachineMessager.cs deleted file mode 100644 index d3e4038f..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MachineMessager.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace WebApiNetCore31 -{ - internal class MachineMessager : IMessager - { - public string OperationId { get; } = $"機器-{Guid.NewGuid()}"; - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MultiMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MultiMessager.cs deleted file mode 100644 index e4eb78a9..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MultiMessager.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace WebApiNetCore31 -{ - public class MultiMessager : IScopeMessager, ISingleMessager, ITransientMessager - { - public string OperationId { get; } = $"多個接口-{Guid.NewGuid()}"; - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Program.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Program.cs deleted file mode 100644 index 9ff36d18..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Program.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Autofac.Extensions.DependencyInjection; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; - -namespace WebApiNetCore31 -{ - public class Program - { - public static IHostBuilder CreateHostBuilder(string[] args) - { - return Host.CreateDefaultBuilder(args) - .UseServiceProviderFactory(new AutofacServiceProviderFactory()) - //.ConfigureServices(services => services.AddAutofac()) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); - } - - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Properties/launchSettings.json b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Properties/launchSettings.json deleted file mode 100644 index 21810b86..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Properties/launchSettings.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:54205", - "sslPort": 44308 - } - }, - "$schema": "http://json.schemastore.org/launchsettings.json", - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "default", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "WebApiNetCore31": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "weatherforecast", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Startup.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Startup.cs deleted file mode 100644 index 255e068b..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Startup.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Reflection; -using Autofac; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace WebApiNetCore31 -{ - public class Startup - { - public IConfiguration Configuration { get; } - - public Startup(IConfiguration configuration) - { - this.Configuration = configuration; - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseHttpsRedirection(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); - } - - public void ConfigureContainer(ContainerBuilder builder) - { - var assembly = Assembly.GetExecutingAssembly(); - builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces(); - } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj b/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj deleted file mode 100644 index 3172ea45..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - netcoreapp3.1 - - - - - - - - - - - - - - - - diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings b/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings deleted file mode 100644 index 8a5228f0..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.Development.json b/DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.Development.json deleted file mode 100644 index 8983e0fc..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.json b/DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.json deleted file mode 100644 index d9d9a9bf..00000000 --- a/DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*" -} diff --git a/DI/Lab.MultipleImpl/Client/Client.csproj b/DI/Lab.MultipleImpl/Client/Client.csproj deleted file mode 100644 index e7e23018..00000000 --- a/DI/Lab.MultipleImpl/Client/Client.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - net5.0 - - false - - - - - - - - - - - - - - - - - diff --git a/DI/Lab.MultipleImpl/Client/UnitTest1.cs b/DI/Lab.MultipleImpl/Client/UnitTest1.cs deleted file mode 100644 index 0dcc77b8..00000000 --- a/DI/Lab.MultipleImpl/Client/UnitTest1.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using Autofac; -using Autofac.Extensions.DependencyInjection; -using Autofac.Features.AttributeFilters; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Server; -using Server.Controllers; -using Unity; -using Unity.Microsoft.DependencyInjection; - -namespace Client -{ - [TestClass] - public class UnitTest1 - { - [TestMethod] - public void Autofac注入ServiceName() - { - var hostBuilder = WebHost.CreateDefaultBuilder() - // .UseServiceProviderFactory(new AutofacServiceProviderFactory()) - .ConfigureServices(services => { services.AddAutofac(); }) - .UseStartup() - ; - using var server = new TestServer(hostBuilder) - { - BaseAddress = new Uri("http://localhost:9527") - }; - - var client = server.CreateClient(); - var url = "autofac"; - var response = client.GetAsync(url).Result; - response.EnsureSuccessStatusCode(); - - var result = response.Content.ReadAsStringAsync().Result; - Assert.AreEqual("ZipFileProvider", result); - } - - [TestMethod] - public void Unity注入ServiceName() - { - var unityContainer = new UnityContainer(); - ConfigureContainer(unityContainer); - - using var server = - new TestServer(WebHost.CreateDefaultBuilder() - .UseStartup() - .UseUnityServiceProvider(unityContainer) - .ConfigureServices(UseUnityController) - ) - { - BaseAddress = new Uri("http://localhost:9527") - }; - - var client = server.CreateClient(); - var url = "unity"; - var response = client.GetAsync(url).Result; - response.EnsureSuccessStatusCode(); - - var result = response.Content.ReadAsStringAsync().Result; - Assert.AreEqual("ZipFileProvider", result); - } - - [TestMethod] - public void 注入FuncName() - { - using var server = - new TestServer(WebHost.CreateDefaultBuilder() - .UseStartup() - .ConfigureServices(UseFuncName) - ) - { - BaseAddress = new Uri("http://localhost:9527") - }; - - var client = server.CreateClient(); - var url = "default/zip"; - var response = client.GetAsync(url).Result; - response.EnsureSuccessStatusCode(); - - var result = response.Content.ReadAsStringAsync().Result; - Assert.AreEqual("ZipFileProvider", result); - } - - private static void ConfigureContainer(ContainerBuilder builder) - { - // builder.RegisterType().Keyed("file"); - // builder.RegisterType().Keyed("zip"); - // builder.RegisterType().WithAttributeFiltering(); - } - - private static void ConfigureContainer(IUnityContainer container) - { - container.RegisterType("zip"); - container.RegisterType("file"); - } - - private static void UseFuncName(IServiceCollection services) - { - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton>(provider => - key => - { - switch (key) - { - case "zip": - return provider - .GetService(); - case "file": - return provider - .GetService(); - default: - throw new NotSupportedException(); - } - }); - } - - private static void UseUnityController(IServiceCollection services) - { - services.AddControllers() - .AddControllersAsServices(); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Lab.MultipleImpl.sln b/DI/Lab.MultipleImpl/Lab.MultipleImpl.sln deleted file mode 100644 index 8a158dd3..00000000 --- a/DI/Lab.MultipleImpl/Lab.MultipleImpl.sln +++ /dev/null @@ -1,28 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{DAE7F74D-E847-4B2F-8930-59AF2698FD1D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Client\Client.csproj", "{6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NET5.TestProject", "NET5.TestProject\NET5.TestProject.csproj", "{A433C8F8-3B75-412E-955F-287639C55C5F}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DAE7F74D-E847-4B2F-8930-59AF2698FD1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DAE7F74D-E847-4B2F-8930-59AF2698FD1D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DAE7F74D-E847-4B2F-8930-59AF2698FD1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DAE7F74D-E847-4B2F-8930-59AF2698FD1D}.Release|Any CPU.Build.0 = Release|Any CPU - {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Release|Any CPU.Build.0 = Release|Any CPU - {A433C8F8-3B75-412E-955F-287639C55C5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A433C8F8-3B75-412E-955F-287639C55C5F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A433C8F8-3B75-412E-955F-287639C55C5F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A433C8F8-3B75-412E-955F-287639C55C5F}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs b/DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs deleted file mode 100644 index a1721c5a..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Autofac; -using Autofac.Features.AttributeFilters; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using NET5.TestProject.Controllers; -using NET5.TestProject.File; - -namespace NET5.TestProject -{ - public class AutofacStartup - { - public AutofacStartup(IConfiguration configuration) - { - this.Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public void ConfigureContainer(ContainerBuilder builder) - { - builder.RegisterType().Keyed("file"); - builder.RegisterType().Keyed("zip"); - builder.RegisterType().WithAttributeFiltering();//<-- add line - } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers() - .AddControllersAsServices(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseHttpsRedirection(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs deleted file mode 100644 index 50bdf475..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using Autofac; -using Autofac.Extensions.DependencyInjection; -using Autofac.Features.AttributeFilters; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using NET5.TestProject.File; - -namespace NET5.TestProject.Controllers -{ - [ApiController] - [Route("[controller]")] - public class AutofacController : ControllerBase - { - private readonly IFileProvider _fileProvider; - - private readonly ILogger _logger; - - public AutofacController(ILogger logger, - [KeyFilter("zip")] IFileProvider fileProvider) - { - this._logger = logger; - this._fileProvider = fileProvider; - var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; - Console.WriteLine(msg); - } - - [HttpGet] - [Route("{key}")] - public IActionResult Get(string key) - { - var serviceProvider = this.HttpContext.RequestServices; - var autofacServiceProvider = (AutofacServiceProvider) serviceProvider; - var fileProvider = autofacServiceProvider.LifetimeScope.ResolveKeyed(key); - return this.Ok(fileProvider.Print()); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs deleted file mode 100644 index fad0093e..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using NET5.TestProject.File; - -namespace NET5.TestProject.Controllers -{ - [ApiController] - [Route("[controller]")] - public class DefaultController : ControllerBase - { - private readonly ILogger _logger; - private readonly IFileProvider _fileProvider; - - public DefaultController(ILogger logger, - IFileProvider fileProvider) - { - this._logger = logger; - this._fileProvider = fileProvider; - } - [HttpGet] - public IActionResult Get() - { - // var fileProvider = this.HttpContext.RequestServices.GetService(); - var fileProvider = this._fileProvider; - var result = fileProvider.Print(); - return this.Ok(result); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/FuncController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/FuncController.cs deleted file mode 100644 index 43728f63..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/FuncController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using NET5.TestProject.File; - -namespace NET5.TestProject.Controllers -{ - [ApiController] - [Route("[controller]")] - public class FuncController : ControllerBase - { - private readonly IFileProvider _fileProvider; - private readonly ILogger _logger; - - public FuncController(ILogger logger, - Func pool) - { - this._fileProvider = pool("zip"); - this._logger = logger; - var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; - Console.WriteLine(msg); - } - - [HttpGet] - [Route("{type}")] - public IActionResult Get(string type) - { - var fileProvider = this.HttpContext.RequestServices.GetService(type); - var result = fileProvider.Print(); - return this.Ok(result); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/MultiController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/MultiController.cs deleted file mode 100644 index 48942f3f..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/MultiController.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using NET5.TestProject.File; - -namespace NET5.TestProject.Controllers -{ - [ApiController] - [Route("[controller]")] - public class MultiController : ControllerBase - { - private readonly IFileProvider _fileProvider; - - private readonly ILogger _logger; - - // public MultiController(ILogger logger, - // IEnumerable pool) - // { - // this._logger = logger; - // this._fileProvider = pool.FirstOrDefault(p => p.GetType().Name == "ZipFileProvider"); - // var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; - // Console.WriteLine(msg); - // } - // - // [HttpGet] - // public IActionResult Get() - // { - // var serviceProvider = this.HttpContext.RequestServices; - // var pool = serviceProvider.GetServices(); - // var fileProvider = pool.FirstOrDefault(p => p.GetType().Name == "ZipFileProvider"); - // return this.Ok(fileProvider.Print()); - // } - - public MultiController(ILogger logger, - Dictionary pool) - { - this._logger = logger; - this._fileProvider = pool["zip"]; - var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; - Console.WriteLine(msg); - } - - [HttpGet] - [Route("{key}")] - public IActionResult Get(string key) - { - var serviceProvider = this.HttpContext.RequestServices; - var pool = serviceProvider.GetService>(); - var fileProvider = pool[key]; - return this.Ok(fileProvider.Print()); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs deleted file mode 100644 index c94576a0..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using NET5.TestProject.File; -using Unity; -using Unity.Microsoft.DependencyInjection; - -namespace NET5.TestProject.Controllers -{ - [ApiController] - [Route("[controller]")] - public class UnityController : ControllerBase - { - private readonly IFileProvider _fileProvider; - - private readonly ILogger _logger; - - public UnityController(ILogger logger, - [Dependency("zip")] IFileProvider fileProvider) - { - this._logger = logger; - this._fileProvider = fileProvider; - var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; - Console.WriteLine(msg); - } - - [HttpGet] - [Route("{key}")] - public IActionResult Get(string key) - { - var serviceProvider = this.HttpContext.RequestServices; - var unityServiceProvider = (ServiceProvider) serviceProvider; - var unityContainer = (UnityContainer) unityServiceProvider; - var fileProvider = unityContainer.Resolve(key); - var result = fileProvider.Print(); - return this.Ok(result); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/File/FileProvider.cs b/DI/Lab.MultipleImpl/NET5.TestProject/File/FileProvider.cs deleted file mode 100644 index f9ea7bea..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/File/FileProvider.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace NET5.TestProject.File -{ - public class FileProvider : IFileProvider - { - public string Print() - { - var msg = "FileProvider"; - Console.WriteLine(msg); - return msg; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/File/IFileProvider.cs b/DI/Lab.MultipleImpl/NET5.TestProject/File/IFileProvider.cs deleted file mode 100644 index e72e465a..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/File/IFileProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NET5.TestProject.File -{ - public interface IFileProvider - { - string Print(); - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/File/ZipFileProvider.cs b/DI/Lab.MultipleImpl/NET5.TestProject/File/ZipFileProvider.cs deleted file mode 100644 index 058d383d..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/File/ZipFileProvider.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace NET5.TestProject.File -{ - public class ZipFileProvider : IFileProvider - { - public string Print() - { - var msg = "ZipFileProvider"; - Console.WriteLine(msg); - return msg; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/FileAdapter.cs b/DI/Lab.MultipleImpl/NET5.TestProject/FileAdapter.cs deleted file mode 100644 index 41034a00..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/FileAdapter.cs +++ /dev/null @@ -1,19 +0,0 @@ -using NET5.TestProject.File; - -namespace NET5.TestProject -{ - public class FileAdapter - { - private readonly IFileProvider _fileProvider; - - public FileAdapter(IFileProvider fileProvider) - { - this._fileProvider = fileProvider; - } - - public string Get() - { - return this._fileProvider.Print(); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/FuncStartup.cs b/DI/Lab.MultipleImpl/NET5.TestProject/FuncStartup.cs deleted file mode 100644 index 5be65458..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/FuncStartup.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using NET5.TestProject.File; - -namespace NET5.TestProject -{ - public class FuncStartup - { - public FuncStartup(IConfiguration configuration) - { - this.Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - UseFuncName(services); - } - private static void UseFuncName(IServiceCollection services) - { - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton>(provider => - key => - { - switch (key) - { - case "zip": - return provider - .GetService(); - case "file": - return provider - .GetService(); - default: - throw new NotSupportedException(); - } - }); - } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseHttpsRedirection(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj b/DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj deleted file mode 100644 index 1c284244..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - net5.0 - - false - - - - - - - - - - - - - - - - true - PreserveNewest - PreserveNewest - - - - diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs b/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs deleted file mode 100644 index 2c381a2f..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using NET5.TestProject.File; - -namespace NET5.TestProject -{ - public static class ServiceProviderExtension - { - public static T GetService(this IServiceProvider provider, string name) - { - var pool = (Func) provider.GetService(typeof(Func)); - return (T) pool(name); - } - - public static List GetTypesAssignableFrom(this Assembly assembly) - { - return assembly.GetTypesAssignableFrom(typeof(T)); - } - - public static List GetTypesAssignableFrom(this Assembly assembly, Type compareType) - { - var results = new List(); - foreach (var type in assembly.DefinedTypes) - { - if (compareType.IsAssignableFrom(type) - && compareType != type - ) - { - results.Add(type); - } - } - - return results; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Startup.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Startup.cs deleted file mode 100644 index 2bad24fc..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/Startup.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace NET5.TestProject -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - this.Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseHttpsRedirection(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs deleted file mode 100644 index 6641e17a..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs +++ /dev/null @@ -1,214 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using Autofac.Extensions.DependencyInjection; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NET5.TestProject.Controllers; -using NET5.TestProject.File; -using Unity; -using Unity.Microsoft.DependencyInjection; - -namespace NET5.TestProject -{ - [TestClass] - public class UnitTest1 - { - [TestMethod] - public void Autofac注入ServiceName() - { - var hostBuilder = WebHost.CreateDefaultBuilder() - - // .UseServiceProviderFactory(new AutofacServiceProviderFactory()) - .UseStartup() //<-- add line - .ConfigureServices(services => - { - services.AddAutofac(); - services.AddControllers() - .AddControllersAsServices(); //<-- add line - }) - ; - using var server = new TestServer(hostBuilder) - { - BaseAddress = new Uri("http://localhost:9527") - }; - - var client = server.CreateClient(); - var url = "autofac/zip"; - var response = client.GetAsync(url).Result; - response.EnsureSuccessStatusCode(); - - var result = response.Content.ReadAsStringAsync().Result; - Assert.AreEqual("ZipFileProvider", result); - } - - [TestMethod] - public void Unity注入ServiceName() - { - var unityContainer = new UnityContainer(); - unityContainer.RegisterType("zip"); - unityContainer.RegisterType("file"); //<-- add line - - var builder = WebHost.CreateDefaultBuilder() - .UseStartup() - .UseUnityServiceProvider(unityContainer) //<-- add line - .ConfigureServices(s => - { - s.AddControllers() - .AddControllersAsServices(); //<-- add line - }) - ; - using var server = new TestServer(builder) - { - BaseAddress = new Uri("http://localhost:9527") - }; - - var client = server.CreateClient(); - var url = "unity/zip"; - var response = client.GetAsync(url).Result; - response.EnsureSuccessStatusCode(); - - var result = response.Content.ReadAsStringAsync().Result; - Assert.AreEqual("ZipFileProvider", result); - } - - [TestMethod] - public void 手動註冊() - { - var hostBuilder = - WebHost.CreateDefaultBuilder() - .UseStartup() - .ConfigureServices(s => - { - s.AddSingleton(); - s.AddSingleton(); - s.AddSingleton(p => - { - var fileProvider = p.GetService(); - var logger = - p.GetService>(); - return new DefaultController(logger, fileProvider); - }); - s.AddControllers().AddControllersAsServices(); - }) - ; - using var server = new TestServer(hostBuilder) - { - BaseAddress = new Uri("http://localhost:9527") - }; - - var client = server.CreateClient(); - var url = "default"; - var response = client.GetAsync(url).Result; - response.EnsureSuccessStatusCode(); - - var result = response.Content.ReadAsStringAsync().Result; - Assert.AreEqual("ZipFileProvider", result); - } - - [TestMethod] - public void 注入FuncName() - { - var builder = WebHost.CreateDefaultBuilder() - .UseStartup() - .ConfigureServices(s => - { - s.AddSingleton(); - s.AddSingleton(); - s.AddSingleton>(p => - key => - { - switch (key) - { - case "zip": - return p - .GetService(); - case "file": - return p - .GetService(); - default: - throw new NotSupportedException(); - } - }); - }) - ; - using var server = new TestServer(builder) - { - BaseAddress = new Uri("http://localhost:9527") - }; - - var client = server.CreateClient(); - var url = "func/zip"; - var response = client.GetAsync(url).Result; - response.EnsureSuccessStatusCode(); - - var result = response.Content.ReadAsStringAsync().Result; - Assert.AreEqual("ZipFileProvider", result); - } - - [TestMethod] - public void 注入相同的介面() - { - var hostBuilder = WebHost.CreateDefaultBuilder() - .UseStartup() //<-- add line - .ConfigureServices(service => - { - ScanToDictionary(service); - - // AddToDictionary(service); - }) - ; - using var server = new TestServer(hostBuilder) - { - BaseAddress = new Uri("http://localhost:9527") - }; - - var client = server.CreateClient(); - var url = "multi/zip"; - var response = client.GetAsync(url).Result; - response.EnsureSuccessStatusCode(); - - var result = response.Content.ReadAsStringAsync().Result; - Assert.AreEqual("ZipFileProvider", result); - } - - private static void AddToDictionary(IServiceCollection s) - { - s.AddSingleton(); - s.AddSingleton(); - s.AddSingleton(p => - { - var pool = - new Dictionary - { - {"zip", p.GetService()}, - {"file", p.GetService()} - }; - - return pool; - }); - } - - private static void ScanToDictionary(IServiceCollection services) - { - var assembly = Assembly.GetExecutingAssembly(); - assembly.GetTypesAssignableFrom() - .ForEach(t => { services.AddSingleton(t); }); - services.AddSingleton(p => - { - var pool = - new Dictionary - { - {"zip", p.GetService()}, - {"file", p.GetService()} - }; - - return pool; - }); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/appsettings.json b/DI/Lab.MultipleImpl/NET5.TestProject/appsettings.json deleted file mode 100644 index d9d9a9bf..00000000 --- a/DI/Lab.MultipleImpl/NET5.TestProject/appsettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*" -} diff --git a/DI/Lab.MultipleImpl/Server/AutofacStartup.cs b/DI/Lab.MultipleImpl/Server/AutofacStartup.cs deleted file mode 100644 index 1e93de1e..00000000 --- a/DI/Lab.MultipleImpl/Server/AutofacStartup.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Autofac; -using Autofac.Features.AttributeFilters; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.OpenApi.Models; -using Server.Controllers; - -namespace Server -{ - public class AutofacStartup - { - public AutofacStartup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public void ConfigureContainer(ContainerBuilder builder) - { - builder.RegisterType().Keyed("file"); - builder.RegisterType().Keyed("zip"); - builder.RegisterType().WithAttributeFiltering(); - } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers() - .AddControllersAsServices(); - services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Server", Version = "v1"}); }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseSwagger(); - app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server v1")); - } - - app.UseHttpsRedirection(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Controllers/AutofacController.cs b/DI/Lab.MultipleImpl/Server/Controllers/AutofacController.cs deleted file mode 100644 index 45debe20..00000000 --- a/DI/Lab.MultipleImpl/Server/Controllers/AutofacController.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Autofac.Features.AttributeFilters; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace Server.Controllers -{ - [ApiController] - [Route("[controller]")] - public class AutofacController : ControllerBase - { - private readonly IFileProvider _fileProvider; - - private readonly ILogger _logger; - - // public AutofacDefaultController(ILogger logger) - // { - // this._logger = logger; - // } - - public AutofacController(ILogger logger, - [KeyFilter("zip")] IFileProvider fileProvider) - { - this._logger = logger; - this._fileProvider = fileProvider; - this._fileProvider.Print(); - } - - [HttpGet] - public IActionResult Get() - { - return this.Ok(this._fileProvider.Print()); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Controllers/DefaultController.cs b/DI/Lab.MultipleImpl/Server/Controllers/DefaultController.cs deleted file mode 100644 index 79efc330..00000000 --- a/DI/Lab.MultipleImpl/Server/Controllers/DefaultController.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace Server.Controllers -{ - [ApiController] - [Route("[controller]")] - public class DefaultController : ControllerBase - { - - private readonly ILogger _logger; - - public DefaultController(ILogger logger) - { - _logger = logger; - } - - [HttpGet] - [Route("{type}")] - public IActionResult Get(string type) - { - var fileProvider = this.HttpContext.RequestServices.GetService(type); - var result = fileProvider.Print(); - return this.Ok(result); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Controllers/UnityController.cs b/DI/Lab.MultipleImpl/Server/Controllers/UnityController.cs deleted file mode 100644 index 44910a17..00000000 --- a/DI/Lab.MultipleImpl/Server/Controllers/UnityController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Unity; - -namespace Server.Controllers -{ - [ApiController] - [Route("[controller]")] - public class UnityController : ControllerBase - { - private readonly IFileProvider _fileProvider; - - private readonly ILogger _logger; - - public UnityController(ILogger logger, - [Dependency("zip")] IFileProvider fileProvider) - { - this._logger = logger; - this._fileProvider = fileProvider; - } - - [HttpGet] - public IActionResult Get() - { - var serviceProvider = this.HttpContext.RequestServices; - var unityServiceProvider = (Unity.Microsoft.DependencyInjection.ServiceProvider) serviceProvider; - var unityContainer = (UnityContainer) unityServiceProvider; - var fileProvider = unityContainer.Resolve("zip"); - var result = fileProvider.Print(); - return this.Ok(result); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Controllers/WeatherForecastController.cs b/DI/Lab.MultipleImpl/Server/Controllers/WeatherForecastController.cs deleted file mode 100644 index 69c4d675..00000000 --- a/DI/Lab.MultipleImpl/Server/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace Server.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet] - public IEnumerable Get() - { - var rng = new Random(); - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateTime.Now.AddDays(index), - TemperatureC = rng.Next(-20, 55), - Summary = Summaries[rng.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/DependencyConfig.cs b/DI/Lab.MultipleImpl/Server/DependencyConfig.cs deleted file mode 100644 index b6df11a4..00000000 --- a/DI/Lab.MultipleImpl/Server/DependencyConfig.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.OpenApi.Models; - -namespace Server -{ - public class DependencyConfig - { - public static void ConfigureServices(IServiceCollection services) - { - services.AddControllers() - .AddControllersAsServices() - ; - } - public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseSwagger(); - app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server v1")); - } - - app.UseHttpsRedirection(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/File/FileProvider.cs b/DI/Lab.MultipleImpl/Server/File/FileProvider.cs deleted file mode 100644 index 06045138..00000000 --- a/DI/Lab.MultipleImpl/Server/File/FileProvider.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Server -{ - public class FileProvider : IFileProvider - { - public string Print() - { - var msg = "FileProvider"; - Console.WriteLine(msg); - return msg; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/File/IFileProvider.cs b/DI/Lab.MultipleImpl/Server/File/IFileProvider.cs deleted file mode 100644 index 7c69a1ae..00000000 --- a/DI/Lab.MultipleImpl/Server/File/IFileProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Server -{ - public interface IFileProvider - { - string Print(); - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/File/ZipFileProvider.cs b/DI/Lab.MultipleImpl/Server/File/ZipFileProvider.cs deleted file mode 100644 index 804a286a..00000000 --- a/DI/Lab.MultipleImpl/Server/File/ZipFileProvider.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Server -{ - public class ZipFileProvider : IFileProvider - { - public string Print() - { - var msg = "ZipFileProvider"; - Console.WriteLine(msg); - return msg; - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Program.cs b/DI/Lab.MultipleImpl/Server/Program.cs deleted file mode 100644 index c083a4aa..00000000 --- a/DI/Lab.MultipleImpl/Server/Program.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Autofac.Extensions.DependencyInjection; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace Server -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .UseServiceProviderFactory(new AutofacServiceProviderFactory()) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Properties/launchSettings.json b/DI/Lab.MultipleImpl/Server/Properties/launchSettings.json deleted file mode 100644 index f822a28b..00000000 --- a/DI/Lab.MultipleImpl/Server/Properties/launchSettings.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:59369", - "sslPort": 44389 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Server": { - "commandName": "Project", - "dotnetRunMessages": "true", - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/DI/Lab.MultipleImpl/Server/Server.csproj b/DI/Lab.MultipleImpl/Server/Server.csproj deleted file mode 100644 index 153ed02c..00000000 --- a/DI/Lab.MultipleImpl/Server/Server.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - net5.0 - - - - - - - - - - - - - - - - - diff --git a/DI/Lab.MultipleImpl/Server/ServiceProviderExtension.cs b/DI/Lab.MultipleImpl/Server/ServiceProviderExtension.cs deleted file mode 100644 index 601138e2..00000000 --- a/DI/Lab.MultipleImpl/Server/ServiceProviderExtension.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Server -{ - public static class ServiceProviderExtension - { - public static T GetService(this IServiceProvider provider, string name) - { - var pool = (Func) provider.GetService(typeof(Func)); - return (T) pool(name); - } - } - -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Startup.cs b/DI/Lab.MultipleImpl/Server/Startup.cs deleted file mode 100644 index e7ecb450..00000000 --- a/DI/Lab.MultipleImpl/Server/Startup.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.OpenApi.Models; - -namespace Server -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Server", Version = "v1"}); }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseSwagger(); - app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server v1")); - } - - app.UseHttpsRedirection(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); - } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/WeatherForecast.cs b/DI/Lab.MultipleImpl/Server/WeatherForecast.cs deleted file mode 100644 index 36e011e2..00000000 --- a/DI/Lab.MultipleImpl/Server/WeatherForecast.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Server -{ - public class WeatherForecast - { - public DateTime Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int) (TemperatureC / 0.5556); - - public string Summary { get; set; } - } -} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/appsettings.Development.json b/DI/Lab.MultipleImpl/Server/appsettings.Development.json deleted file mode 100644 index 8983e0fc..00000000 --- a/DI/Lab.MultipleImpl/Server/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/DI/Lab.MultipleImpl/Server/appsettings.json b/DI/Lab.MultipleImpl/Server/appsettings.json deleted file mode 100644 index d9d9a9bf..00000000 --- a/DI/Lab.MultipleImpl/Server/appsettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*" -} diff --git a/DI/Lib.MsDiForScrutor b/DI/Lib.MsDiForScrutor deleted file mode 160000 index f36a5e15..00000000 --- a/DI/Lib.MsDiForScrutor +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f36a5e15db26213b8acfd0dcb823cf530996e1be From b364db7f3d1d48a3f7c4c7f5b6295e3327feebf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E5=B0=8F=E7=AB=A0?= Date: Thu, 19 Aug 2021 23:57:19 +0800 Subject: [PATCH 117/424] add project --- DI/Lab.MsDI/Lab.MsDI.sln | 49 + .../App_Start/DefaultDependencyResolver.cs | 31 + .../App_Start/DefaultDependencyResolver2.cs | 35 + .../App_Start/DependencyInjectionConfig.cs | 44 + .../Mvc5Net48/App_Start/FilterConfig.cs | 15 + .../Mvc5Net48/App_Start/RouteConfig.cs | 20 + .../App_Start/ServiceProviderExtensions.cs | 27 + .../Controllers/Default1Controller.cs | 28 + .../Controllers/DefaultController.cs | 43 + DI/Lab.MsDI/Mvc5Net48/Global.asax | 1 + DI/Lab.MsDI/Mvc5Net48/Global.asax.cs | 24 + DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute.cs | 40 + DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute2.cs | 30 + DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs | 9 + .../Mvc5Net48/Message/IScopeMessager.cs | 6 + .../Mvc5Net48/Message/ISingleMessager.cs | 6 + .../Mvc5Net48/Message/ITransientMessager.cs | 6 + DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs | 14 + .../Mvc5Net48/Message/MachineMessager.cs | 13 + .../Mvc5Net48/Message/MultiMessager.cs | 13 + DI/Lab.MsDI/Mvc5Net48/Mvc5Net48.csproj | 202 + DI/Lab.MsDI/Mvc5Net48/NLog.config | 45 + DI/Lab.MsDI/Mvc5Net48/NLog.xsd | 3644 +++++++++++++++++ .../Mvc5Net48/Properties/AssemblyInfo.cs | 35 + .../Mvc5Net48/ServiceScopeHttpModule.cs | 42 + .../Mvc5Net48/Views/Default/Index.cshtml | 18 + .../Mvc5Net48/Views/Default1/Index.cshtml | 18 + DI/Lab.MsDI/Mvc5Net48/Views/web.config | 42 + DI/Lab.MsDI/Mvc5Net48/Web.Debug.config | 30 + DI/Lab.MsDI/Mvc5Net48/Web.Release.config | 31 + DI/Lab.MsDI/Mvc5Net48/Web.config | 51 + DI/Lab.MsDI/Mvc5Net48/packages.config | 16 + .../App_Start/DefaultDependencyResolver.cs | 40 + .../App_Start/DependencyInjectionConfig.cs | 40 + .../App_Start/ServiceProviderExtensions.cs | 27 + .../WebApiNet48/App_Start/WebApiConfig.cs | 23 + .../Controllers/Default1Controller.cs | 27 + .../Controllers/DefaultController.cs | 45 + DI/Lab.MsDI/WebApiNet48/Global.asax | 1 + DI/Lab.MsDI/WebApiNet48/Global.asax.cs | 14 + DI/Lab.MsDI/WebApiNet48/LogFilterAttribute.cs | 28 + DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs | 9 + .../WebApiNet48/Message/IScopeMessager.cs | 6 + .../WebApiNet48/Message/ISingleMessager.cs | 6 + .../WebApiNet48/Message/ITransientMessager.cs | 6 + .../WebApiNet48/Message/LogMessager.cs | 14 + .../WebApiNet48/Message/MachineMessager.cs | 14 + .../WebApiNet48/Message/MultiMessager.cs | 14 + DI/Lab.MsDI/WebApiNet48/NLog.config | 45 + DI/Lab.MsDI/WebApiNet48/NLog.xsd | 3644 +++++++++++++++++ .../WebApiNet48/Properties/AssemblyInfo.cs | 35 + .../ServiceProviderDependencyResolver.cs | 50 + DI/Lab.MsDI/WebApiNet48/ServiceScopeModule.cs | 42 + DI/Lab.MsDI/WebApiNet48/Web.Debug.config | 30 + DI/Lab.MsDI/WebApiNet48/Web.Release.config | 31 + DI/Lab.MsDI/WebApiNet48/Web.config | 54 + DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj | 186 + .../WebApiNet48.csproj.DotSettings | 2 + DI/Lab.MsDI/WebApiNet48/packages.config | 17 + .../Controllers/Default1Controller.cs | 35 + .../Controllers/DefaultController.cs | 44 + .../WebApiNetCore31/LogFilterAttribute.cs | 27 + .../WebApiNetCore31/Message/IMessager.cs | 9 + .../WebApiNetCore31/Message/IScopeMessager.cs | 6 + .../Message/ISingleMessager.cs | 6 + .../Message/ITransientMessager.cs | 6 + .../WebApiNetCore31/Message/LogMessager.cs | 14 + .../Message/MachineMessager.cs | 13 + .../WebApiNetCore31/Message/MultiMessager.cs | 14 + DI/Lab.MsDI/WebApiNetCore31/Program.cs | 26 + .../Properties/launchSettings.json | 30 + DI/Lab.MsDI/WebApiNetCore31/Startup.cs | 49 + .../WebApiNetCore31/WeatherForecast.cs | 15 + .../WebApiNetCore31/WebApiNetCore31.csproj | 8 + .../WebApiNetCore31.csproj.DotSettings | 2 + DI/Lab.MsDI/WebApiNetCore31/Worker.cs | 22 + .../appsettings.Development.json | 9 + DI/Lab.MsDI/WebApiNetCore31/appsettings.json | 10 + DI/Lab.MsDI/WebApiOwinNet48/App.config | 14 + DI/Lab.MsDI/WebApiOwinNet48/AppSetting.cs | 8 + .../App_Start/DefaultDependencyResolver.cs | 39 + .../App_Start/DependencyInjectionConfig.cs | 34 + .../App_Start/ServiceProviderExtensions.cs | 27 + .../WebApiOwinNet48/App_Start/WebApiConfig.cs | 23 + DI/Lab.MsDI/WebApiOwinNet48/Commander.cs | 15 + .../Controllers/DefaultController.cs | 32 + DI/Lab.MsDI/WebApiOwinNet48/LabMiddleware.cs | 69 + DI/Lab.MsDI/WebApiOwinNet48/Program.cs | 18 + .../Properties/AssemblyInfo.cs | 36 + DI/Lab.MsDI/WebApiOwinNet48/Startup.cs | 19 + .../WebApiOwinNet48/WebApiOwinNet48.csproj | 111 + DI/Lab.MsDI/WebApiOwinNet48/packages.config | 18 + DI/Lab.MsDI/WinFormNet48/App.config | 18 + .../WinFormNet48/DependencyInjectionConfig.cs | 64 + DI/Lab.MsDI/WinFormNet48/Form1.Designer.cs | 61 + DI/Lab.MsDI/WinFormNet48/Form1.cs | 21 + DI/Lab.MsDI/WinFormNet48/Form1.resx | 120 + DI/Lab.MsDI/WinFormNet48/Message/IMessager.cs | 7 + .../WinFormNet48/Message/IScopeMessager.cs | 6 + .../WinFormNet48/Message/ISingleMessager.cs | 6 + .../Message/ITransientMessager.cs | 6 + .../WinFormNet48/Message/LogMessager.cs | 9 + .../WinFormNet48/Message/MachineMessager.cs | 9 + .../WinFormNet48/Message/MultiMessager.cs | 9 + DI/Lab.MsDI/WinFormNet48/Program.cs | 26 + .../WinFormNet48/Properties/AssemblyInfo.cs | 36 + .../Properties/Resources.Designer.cs | 71 + .../WinFormNet48/Properties/Resources.resx | 117 + .../Properties/Settings.Designer.cs | 30 + .../WinFormNet48/Properties/Settings.settings | 7 + DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj | 113 + .../WinFormNet48.csproj.DotSettings | 2 + DI/Lab.MsDI/WinFormNet48/Worker.cs | 25 + DI/Lab.MsDI/WinFormNet48/Workflow.cs | 16 + DI/Lab.MsDI/WinFormNet48/packages.config | 8 + DI/Lab.MultipleImpl/Client/Client.csproj | 23 + DI/Lab.MultipleImpl/Client/UnitTest1.cs | 128 + DI/Lab.MultipleImpl/Lab.MultipleImpl.sln | 28 + .../NET5.TestProject/AutofacStartup.cs | 53 + .../Controllers/AutofacController.cs | 38 + .../Controllers/DefaultController.cs | 30 + .../Controllers/FuncController.cs | 33 + .../Controllers/MultiController.cs | 56 + .../Controllers/UnityController.cs | 39 + .../NET5.TestProject/File/FileProvider.cs | 14 + .../NET5.TestProject/File/IFileProvider.cs | 7 + .../NET5.TestProject/File/ZipFileProvider.cs | 14 + .../NET5.TestProject/FileAdapter.cs | 19 + .../NET5.TestProject/FuncStartup.cs | 63 + .../NET5.TestProject/NET5.TestProject.csproj | 28 + .../ServiceProviderExtension.cs | 37 + .../NET5.TestProject/Startup.cs | 41 + .../NET5.TestProject/UnitTest1.cs | 214 + .../NET5.TestProject/appsettings.json | 10 + DI/Lab.MultipleImpl/Server/AutofacStartup.cs | 63 + .../Server/Controllers/AutofacController.cs | 35 + .../Server/Controllers/DefaultController.cs | 31 + .../Server/Controllers/UnityController.cs | 33 + .../Controllers/WeatherForecastController.cs | 39 + .../Server/DependencyConfig.cs | 35 + .../Server/File/FileProvider.cs | 14 + .../Server/File/IFileProvider.cs | 7 + .../Server/File/ZipFileProvider.cs | 14 + DI/Lab.MultipleImpl/Server/Program.cs | 25 + .../Server/Properties/launchSettings.json | 31 + DI/Lab.MultipleImpl/Server/Server.csproj | 21 + .../Server/Server.csproj.DotSettings | 2 + .../Server/ServiceProviderExtension.cs | 14 + DI/Lab.MultipleImpl/Server/Startup.cs | 52 + DI/Lab.MultipleImpl/Server/WeatherForecast.cs | 15 + .../Server/appsettings.Development.json | 9 + DI/Lab.MultipleImpl/Server/appsettings.json | 10 + DI/Lib.MsDiForScrutor/Lib.MsDiForScrutor.sln | 25 + .../Controllers/Default1Controller.cs | 36 + .../Controllers/DefaultController.cs | 43 + .../WebApiNetCore31/Message/IMessager.cs | 7 + .../WebApiNetCore31/Message/IScopeMessager.cs | 6 + .../Message/ISingleMessager.cs | 6 + .../Message/ITransientMessager.cs | 6 + .../WebApiNetCore31/Message/LogMessager.cs | 9 + .../Message/MachineMessager.cs | 9 + .../WebApiNetCore31/Message/Messager.cs | 9 + .../WebApiNetCore31/Message/MultiMessager.cs | 9 + .../WebApiNetCore31/Program.cs | 26 + .../Properties/launchSettings.json | 30 + .../WebApiNetCore31/Startup.cs | 72 + .../WebApiNetCore31/WeatherForecast.cs | 15 + .../WebApiNetCore31/WebApiNetCore31.csproj | 21 + .../WebApiNetCore31/Worker.cs | 22 + .../appsettings.Development.json | 9 + .../WebApiNetCore31/appsettings.json | 10 + 171 files changed, 12368 insertions(+) create mode 100644 DI/Lab.MsDI/Lab.MsDI.sln create mode 100644 DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver2.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/App_Start/DependencyInjectionConfig.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/App_Start/FilterConfig.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/App_Start/RouteConfig.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/App_Start/ServiceProviderExtensions.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/Controllers/Default1Controller.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/Controllers/DefaultController.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/Global.asax create mode 100644 DI/Lab.MsDI/Mvc5Net48/Global.asax.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute2.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/Message/IScopeMessager.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/Message/ISingleMessager.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/Message/ITransientMessager.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/Message/MachineMessager.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/Message/MultiMessager.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/Mvc5Net48.csproj create mode 100644 DI/Lab.MsDI/Mvc5Net48/NLog.config create mode 100644 DI/Lab.MsDI/Mvc5Net48/NLog.xsd create mode 100644 DI/Lab.MsDI/Mvc5Net48/Properties/AssemblyInfo.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/ServiceScopeHttpModule.cs create mode 100644 DI/Lab.MsDI/Mvc5Net48/Views/Default/Index.cshtml create mode 100644 DI/Lab.MsDI/Mvc5Net48/Views/Default1/Index.cshtml create mode 100644 DI/Lab.MsDI/Mvc5Net48/Views/web.config create mode 100644 DI/Lab.MsDI/Mvc5Net48/Web.Debug.config create mode 100644 DI/Lab.MsDI/Mvc5Net48/Web.Release.config create mode 100644 DI/Lab.MsDI/Mvc5Net48/Web.config create mode 100644 DI/Lab.MsDI/Mvc5Net48/packages.config create mode 100644 DI/Lab.MsDI/WebApiNet48/App_Start/DefaultDependencyResolver.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/App_Start/DependencyInjectionConfig.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/App_Start/ServiceProviderExtensions.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/App_Start/WebApiConfig.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/Controllers/Default1Controller.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/Controllers/DefaultController.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/Global.asax create mode 100644 DI/Lab.MsDI/WebApiNet48/Global.asax.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/LogFilterAttribute.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/Message/IScopeMessager.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/Message/ISingleMessager.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/Message/ITransientMessager.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/Message/LogMessager.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/Message/MachineMessager.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/Message/MultiMessager.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/NLog.config create mode 100644 DI/Lab.MsDI/WebApiNet48/NLog.xsd create mode 100644 DI/Lab.MsDI/WebApiNet48/Properties/AssemblyInfo.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/ServiceProviderDependencyResolver.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/ServiceScopeModule.cs create mode 100644 DI/Lab.MsDI/WebApiNet48/Web.Debug.config create mode 100644 DI/Lab.MsDI/WebApiNet48/Web.Release.config create mode 100644 DI/Lab.MsDI/WebApiNet48/Web.config create mode 100644 DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj create mode 100644 DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj.DotSettings create mode 100644 DI/Lab.MsDI/WebApiNet48/packages.config create mode 100644 DI/Lab.MsDI/WebApiNetCore31/Controllers/Default1Controller.cs create mode 100644 DI/Lab.MsDI/WebApiNetCore31/Controllers/DefaultController.cs create mode 100644 DI/Lab.MsDI/WebApiNetCore31/LogFilterAttribute.cs create mode 100644 DI/Lab.MsDI/WebApiNetCore31/Message/IMessager.cs create mode 100644 DI/Lab.MsDI/WebApiNetCore31/Message/IScopeMessager.cs create mode 100644 DI/Lab.MsDI/WebApiNetCore31/Message/ISingleMessager.cs create mode 100644 DI/Lab.MsDI/WebApiNetCore31/Message/ITransientMessager.cs create mode 100644 DI/Lab.MsDI/WebApiNetCore31/Message/LogMessager.cs create mode 100644 DI/Lab.MsDI/WebApiNetCore31/Message/MachineMessager.cs create mode 100644 DI/Lab.MsDI/WebApiNetCore31/Message/MultiMessager.cs create mode 100644 DI/Lab.MsDI/WebApiNetCore31/Program.cs create mode 100644 DI/Lab.MsDI/WebApiNetCore31/Properties/launchSettings.json create mode 100644 DI/Lab.MsDI/WebApiNetCore31/Startup.cs create mode 100644 DI/Lab.MsDI/WebApiNetCore31/WeatherForecast.cs create mode 100644 DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj create mode 100644 DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings create mode 100644 DI/Lab.MsDI/WebApiNetCore31/Worker.cs create mode 100644 DI/Lab.MsDI/WebApiNetCore31/appsettings.Development.json create mode 100644 DI/Lab.MsDI/WebApiNetCore31/appsettings.json create mode 100644 DI/Lab.MsDI/WebApiOwinNet48/App.config create mode 100644 DI/Lab.MsDI/WebApiOwinNet48/AppSetting.cs create mode 100644 DI/Lab.MsDI/WebApiOwinNet48/App_Start/DefaultDependencyResolver.cs create mode 100644 DI/Lab.MsDI/WebApiOwinNet48/App_Start/DependencyInjectionConfig.cs create mode 100644 DI/Lab.MsDI/WebApiOwinNet48/App_Start/ServiceProviderExtensions.cs create mode 100644 DI/Lab.MsDI/WebApiOwinNet48/App_Start/WebApiConfig.cs create mode 100644 DI/Lab.MsDI/WebApiOwinNet48/Commander.cs create mode 100644 DI/Lab.MsDI/WebApiOwinNet48/Controllers/DefaultController.cs create mode 100644 DI/Lab.MsDI/WebApiOwinNet48/LabMiddleware.cs create mode 100644 DI/Lab.MsDI/WebApiOwinNet48/Program.cs create mode 100644 DI/Lab.MsDI/WebApiOwinNet48/Properties/AssemblyInfo.cs create mode 100644 DI/Lab.MsDI/WebApiOwinNet48/Startup.cs create mode 100644 DI/Lab.MsDI/WebApiOwinNet48/WebApiOwinNet48.csproj create mode 100644 DI/Lab.MsDI/WebApiOwinNet48/packages.config create mode 100644 DI/Lab.MsDI/WinFormNet48/App.config create mode 100644 DI/Lab.MsDI/WinFormNet48/DependencyInjectionConfig.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Form1.Designer.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Form1.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Form1.resx create mode 100644 DI/Lab.MsDI/WinFormNet48/Message/IMessager.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Message/IScopeMessager.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Message/ISingleMessager.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Message/ITransientMessager.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Message/LogMessager.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Message/MachineMessager.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Message/MultiMessager.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Program.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Properties/AssemblyInfo.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Properties/Resources.Designer.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Properties/Resources.resx create mode 100644 DI/Lab.MsDI/WinFormNet48/Properties/Settings.Designer.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Properties/Settings.settings create mode 100644 DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj create mode 100644 DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj.DotSettings create mode 100644 DI/Lab.MsDI/WinFormNet48/Worker.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/Workflow.cs create mode 100644 DI/Lab.MsDI/WinFormNet48/packages.config create mode 100644 DI/Lab.MultipleImpl/Client/Client.csproj create mode 100644 DI/Lab.MultipleImpl/Client/UnitTest1.cs create mode 100644 DI/Lab.MultipleImpl/Lab.MultipleImpl.sln create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/FuncController.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/MultiController.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/File/FileProvider.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/File/IFileProvider.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/File/ZipFileProvider.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/FileAdapter.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/FuncStartup.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/Startup.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs create mode 100644 DI/Lab.MultipleImpl/NET5.TestProject/appsettings.json create mode 100644 DI/Lab.MultipleImpl/Server/AutofacStartup.cs create mode 100644 DI/Lab.MultipleImpl/Server/Controllers/AutofacController.cs create mode 100644 DI/Lab.MultipleImpl/Server/Controllers/DefaultController.cs create mode 100644 DI/Lab.MultipleImpl/Server/Controllers/UnityController.cs create mode 100644 DI/Lab.MultipleImpl/Server/Controllers/WeatherForecastController.cs create mode 100644 DI/Lab.MultipleImpl/Server/DependencyConfig.cs create mode 100644 DI/Lab.MultipleImpl/Server/File/FileProvider.cs create mode 100644 DI/Lab.MultipleImpl/Server/File/IFileProvider.cs create mode 100644 DI/Lab.MultipleImpl/Server/File/ZipFileProvider.cs create mode 100644 DI/Lab.MultipleImpl/Server/Program.cs create mode 100644 DI/Lab.MultipleImpl/Server/Properties/launchSettings.json create mode 100644 DI/Lab.MultipleImpl/Server/Server.csproj create mode 100644 DI/Lab.MultipleImpl/Server/Server.csproj.DotSettings create mode 100644 DI/Lab.MultipleImpl/Server/ServiceProviderExtension.cs create mode 100644 DI/Lab.MultipleImpl/Server/Startup.cs create mode 100644 DI/Lab.MultipleImpl/Server/WeatherForecast.cs create mode 100644 DI/Lab.MultipleImpl/Server/appsettings.Development.json create mode 100644 DI/Lab.MultipleImpl/Server/appsettings.json create mode 100644 DI/Lib.MsDiForScrutor/Lib.MsDiForScrutor.sln create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/Controllers/Default1Controller.cs create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/Controllers/DefaultController.cs create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/IMessager.cs create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/IScopeMessager.cs create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/ISingleMessager.cs create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/ITransientMessager.cs create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/LogMessager.cs create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/MachineMessager.cs create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/Messager.cs create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/MultiMessager.cs create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/Program.cs create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/Properties/launchSettings.json create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/Startup.cs create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/WeatherForecast.cs create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/WebApiNetCore31.csproj create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/Worker.cs create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/appsettings.Development.json create mode 100644 DI/Lib.MsDiForScrutor/WebApiNetCore31/appsettings.json diff --git a/DI/Lab.MsDI/Lab.MsDI.sln b/DI/Lab.MsDI/Lab.MsDI.sln new file mode 100644 index 00000000..fa493253 --- /dev/null +++ b/DI/Lab.MsDI/Lab.MsDI.sln @@ -0,0 +1,49 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30503.244 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinFormNet48", "WinFormNet48\WinFormNet48.csproj", "{FC5AA11A-0879-43D8-8E79-DFC37DE75FB8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApiNetCore31", "WebApiNetCore31\WebApiNetCore31.csproj", "{08C0CD22-F32B-4E54-BC60-AB6912C8D84F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiNet48", "WebApiNet48\WebApiNet48.csproj", "{429D865C-EDE7-4EA1-BC51-9A5BCAB26BB8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mvc5Net48", "Mvc5Net48\Mvc5Net48.csproj", "{91B2AC8D-1516-4D84-AF08-D72A50854607}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiOwinNet48", "WebApiOwinNet48\WebApiOwinNet48.csproj", "{680EC3CF-CFE2-48F9-8006-F6A3D9B5DC42}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FC5AA11A-0879-43D8-8E79-DFC37DE75FB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC5AA11A-0879-43D8-8E79-DFC37DE75FB8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC5AA11A-0879-43D8-8E79-DFC37DE75FB8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC5AA11A-0879-43D8-8E79-DFC37DE75FB8}.Release|Any CPU.Build.0 = Release|Any CPU + {08C0CD22-F32B-4E54-BC60-AB6912C8D84F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08C0CD22-F32B-4E54-BC60-AB6912C8D84F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08C0CD22-F32B-4E54-BC60-AB6912C8D84F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08C0CD22-F32B-4E54-BC60-AB6912C8D84F}.Release|Any CPU.Build.0 = Release|Any CPU + {429D865C-EDE7-4EA1-BC51-9A5BCAB26BB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {429D865C-EDE7-4EA1-BC51-9A5BCAB26BB8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {429D865C-EDE7-4EA1-BC51-9A5BCAB26BB8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {429D865C-EDE7-4EA1-BC51-9A5BCAB26BB8}.Release|Any CPU.Build.0 = Release|Any CPU + {91B2AC8D-1516-4D84-AF08-D72A50854607}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91B2AC8D-1516-4D84-AF08-D72A50854607}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91B2AC8D-1516-4D84-AF08-D72A50854607}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91B2AC8D-1516-4D84-AF08-D72A50854607}.Release|Any CPU.Build.0 = Release|Any CPU + {680EC3CF-CFE2-48F9-8006-F6A3D9B5DC42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {680EC3CF-CFE2-48F9-8006-F6A3D9B5DC42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {680EC3CF-CFE2-48F9-8006-F6A3D9B5DC42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {680EC3CF-CFE2-48F9-8006-F6A3D9B5DC42}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {49F395E2-6467-4209-955A-D361369FCEA6} + EndGlobalSection +EndGlobal diff --git a/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver.cs b/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver.cs new file mode 100644 index 00000000..ab391905 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.Mvc; +using Microsoft.Extensions.DependencyInjection; + +namespace Mvc5Net48 +{ + internal class DefaultDependencyResolver : IDependencyResolver + { + public object GetService(Type serviceType) + { + if (HttpContext.Current?.Items[typeof(IServiceScope)] is IServiceScope scope) + { + return scope.ServiceProvider.GetService(serviceType); + } + + throw new InvalidOperationException("IServiceScope not provided"); + } + + public IEnumerable GetServices(Type serviceType) + { + if (HttpContext.Current?.Items[typeof(IServiceScope)] is IServiceScope scope) + { + return scope.ServiceProvider.GetServices(serviceType); + } + + throw new InvalidOperationException("IServiceScope not provided"); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver2.cs b/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver2.cs new file mode 100644 index 00000000..0336d89b --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/App_Start/DefaultDependencyResolver2.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Web.Mvc; +using Microsoft.Extensions.DependencyInjection; + +namespace Mvc5Net48 +{ + /// + /// Scope 的生命週期錯誤 + /// + public class DefaultDependencyResolver2 : IDependencyResolver + { + private readonly ServiceProvider _serviceProvider; + + public DefaultDependencyResolver2(IServiceProvider serviceProvider) + { + this._serviceProvider = serviceProvider as ServiceProvider; + } + + public object GetService(Type serviceType) + { + return this._serviceProvider.GetService(serviceType); + } + + public IEnumerable GetServices(Type serviceType) + { + return this._serviceProvider.GetServices(serviceType); + } + + public void Dispose() + { + this._serviceProvider?.Dispose(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/App_Start/DependencyInjectionConfig.cs b/DI/Lab.MsDI/Mvc5Net48/App_Start/DependencyInjectionConfig.cs new file mode 100644 index 00000000..2c1a5ccf --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/App_Start/DependencyInjectionConfig.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq; +using System.Web.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Mvc5Net48.Message; + +namespace Mvc5Net48 +{ + public class DependencyInjectionConfig + { + public static void Register() + { + var services = ConfigureServices(); + + var provider = services.BuildServiceProvider(); + + var resolver = new DefaultDependencyResolver(); + ServiceScopeHttpModule.SetServiceProvider(provider); + DependencyResolver.SetResolver(resolver); + + //config.DependencyResolver = resolver; + } + + /// + /// 使用 MS DI 註冊 + /// + /// + private static ServiceCollection ConfigureServices() + { + var services = new ServiceCollection(); + + //使用 Microsoft.Extensions.DependencyInjection 註冊 + services.AddControllersAsServices(typeof(DependencyInjectionConfig).Assembly + .GetExportedTypes()); + + services.AddScoped(); + + services.AddTransient() + .AddSingleton() + .AddScoped(); + return services; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/App_Start/FilterConfig.cs b/DI/Lab.MsDI/Mvc5Net48/App_Start/FilterConfig.cs new file mode 100644 index 00000000..921340fc --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/App_Start/FilterConfig.cs @@ -0,0 +1,15 @@ +using System.Web.Mvc; + +namespace Mvc5Net48 +{ + public class FilterConfig + { + public static void RegisterGlobalFilters(GlobalFilterCollection filters) + { + filters.Add(new HandleErrorAttribute()); + + // filters.Add(new LogFilterAttribute()); + filters.Add(new LogFilterAttribute2()); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/App_Start/RouteConfig.cs b/DI/Lab.MsDI/Mvc5Net48/App_Start/RouteConfig.cs new file mode 100644 index 00000000..c951f437 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/App_Start/RouteConfig.cs @@ -0,0 +1,20 @@ +using System.Web.Mvc; +using System.Web.Routing; + +namespace Mvc5Net48 +{ + public class RouteConfig + { + public static void RegisterRoutes(RouteCollection routes) + { + routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); + + routes.MapRoute( + name: "Default", + url: "{controller}/{action}/{id}", + defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } + ); + + } + } +} diff --git a/DI/Lab.MsDI/Mvc5Net48/App_Start/ServiceProviderExtensions.cs b/DI/Lab.MsDI/Mvc5Net48/App_Start/ServiceProviderExtensions.cs new file mode 100644 index 00000000..a346c1b6 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/App_Start/ServiceProviderExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using Microsoft.Extensions.DependencyInjection; + +namespace Mvc5Net48 +{ + public static class ServiceProviderExtensions + { + public static IServiceCollection AddControllersAsServices(this IServiceCollection services, + IEnumerable controllerTypes) + { + var filter = controllerTypes.Where(t => !t.IsAbstract + && !t.IsGenericTypeDefinition) + .Where(t => typeof(ControllerBase).IsAssignableFrom(t) + || t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)); + + foreach (var type in filter) + { + services.AddTransient(type); + } + + return services; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Controllers/Default1Controller.cs b/DI/Lab.MsDI/Mvc5Net48/Controllers/Default1Controller.cs new file mode 100644 index 00000000..567997d6 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Controllers/Default1Controller.cs @@ -0,0 +1,28 @@ +using System; +using System.Web.Mvc; +using Mvc5Net48.Message; +using NLog; + +namespace Mvc5Net48.Controllers +{ + public class Default1Controller : Controller + { + // GET: Default + public ActionResult Index() + { + var single = this.Resolver.GetService(typeof(ISingleMessager)) as ISingleMessager; + var scope = this.Resolver.GetService(typeof(IScopeMessager)) as IScopeMessager; + var transient = this.Resolver.GetService(typeof(ITransientMessager)) as ITransientMessager; + var content = "我在 Controller.Get Action\r\n" + + $"transient:{transient.OperationId}\r\n" + + $"scope:{scope.OperationId}\r\n" + + $"single:{single.OperationId}\r\n" + ; + Console.WriteLine(content); + this.ViewBag.Message = content; + var logger = LogManager.GetCurrentClassLogger(); + logger.Trace(content); + return this.View(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Controllers/DefaultController.cs b/DI/Lab.MsDI/Mvc5Net48/Controllers/DefaultController.cs new file mode 100644 index 00000000..3f17c0d1 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Controllers/DefaultController.cs @@ -0,0 +1,43 @@ +using System; +using System.Web.Mvc; +using Mvc5Net48.Message; +using NLog; + +namespace Mvc5Net48.Controllers +{ + public class DefaultController : Controller + { + private ITransientMessager Transient { get; } + + private IScopeMessager Scope { get; } + + private ISingleMessager Single { get; } + + public DefaultController(ITransientMessager transient, + IScopeMessager scope, + ISingleMessager single) + { + this.Transient = transient; + this.Scope = scope; + this.Single = single; + } + + // GET: Default + public ActionResult Index() + { + var single = this.Single; + var scope = this.Scope; + var transient = this.Transient; + var content = "我在 Controller.Get Action\r\n" + + $"transient:{transient.OperationId}\r\n" + + $"scope:{scope.OperationId}\r\n" + + $"single:{single.OperationId}\r\n" + ; + Console.WriteLine(content); + this.ViewBag.Message = content; + var logger = LogManager.GetCurrentClassLogger(); + logger.Trace(content); + return this.View(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Global.asax b/DI/Lab.MsDI/Mvc5Net48/Global.asax new file mode 100644 index 00000000..9fa1c945 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="Mvc5Net48.MvcApplication" Language="C#" %> diff --git a/DI/Lab.MsDI/Mvc5Net48/Global.asax.cs b/DI/Lab.MsDI/Mvc5Net48/Global.asax.cs new file mode 100644 index 00000000..e8693b77 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Global.asax.cs @@ -0,0 +1,24 @@ +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using Mvc5Net48; + +[assembly: PreApplicationStartMethod(typeof(MvcApplication), "InitModule")] +namespace Mvc5Net48 +{ + public class MvcApplication : HttpApplication + { + public static void InitModule() + { + RegisterModule(typeof(ServiceScopeHttpModule)); + } + + protected void Application_Start() + { + AreaRegistration.RegisterAllAreas(); + RouteConfig.RegisterRoutes(RouteTable.Routes); + DependencyInjectionConfig.Register(); + FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute.cs b/DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute.cs new file mode 100644 index 00000000..8738a24c --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute.cs @@ -0,0 +1,40 @@ +using System; +using System.Diagnostics; +using System.Web.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Mvc5Net48.Message; +using NLog; + +namespace Mvc5Net48 +{ + public class LogFilterAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(ActionExecutingContext filterContext) + { + var serviceScope = filterContext.HttpContext?.Items[typeof(IServiceScope)] as IServiceScope; + if (serviceScope == null) + { + return; + } + + var serviceProvider = serviceScope.ServiceProvider; + + var transient = serviceProvider.GetService(); + var single = serviceProvider.GetService(); + + var scope = serviceProvider.GetService(); + var scope2 = DependencyResolver.Current.GetService(); + var noeq = scope.OperationId == scope2.OperationId; + Debug.Assert(noeq); + + var logger = LogManager.GetCurrentClassLogger(); + var content = "我在 LogFilterAttribute.OnActionExecuting\r\n" + + $"transient:{transient.OperationId}\r\n" + + $"scope:{scope.OperationId}\r\n" + + $"single:{single.OperationId}"; + Console.WriteLine(content); + + logger.Trace(content); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute2.cs b/DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute2.cs new file mode 100644 index 00000000..76e285d4 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/LogFilterAttribute2.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics; +using System.Web.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Mvc5Net48.Message; +using NLog; + +namespace Mvc5Net48 +{ + public class LogFilterAttribute2 : ActionFilterAttribute + { + public override void OnActionExecuting(ActionExecutingContext filterContext) + { + // var transientMessager = filterContext.HttpContext.GetService();//失敗 + + var transient = DependencyResolver.Current.GetService(); + var single = DependencyResolver.Current.GetService(); + var scope = DependencyResolver.Current.GetService(); + + var logger = LogManager.GetCurrentClassLogger(); + var content = "我在 LogFilterAttribute.OnActionExecuting\r\n" + + $"transient:{transient.OperationId}\r\n" + + $"scope:{scope.OperationId}\r\n" + + $"single:{single.OperationId}"; + Console.WriteLine(content); + + logger.Trace(content); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs new file mode 100644 index 00000000..e3bda957 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Message/IMessager.cs @@ -0,0 +1,9 @@ +using System; + +namespace Mvc5Net48.Message +{ + public interface IMessager:IDisposable + { + string OperationId { get; } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/IScopeMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/IScopeMessager.cs new file mode 100644 index 00000000..3946da29 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Message/IScopeMessager.cs @@ -0,0 +1,6 @@ +namespace Mvc5Net48.Message +{ + public interface IScopeMessager : IMessager + { + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/ISingleMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/ISingleMessager.cs new file mode 100644 index 00000000..78d712f3 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Message/ISingleMessager.cs @@ -0,0 +1,6 @@ +namespace Mvc5Net48.Message +{ + public interface ISingleMessager : IMessager + { + } +} diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/ITransientMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/ITransientMessager.cs new file mode 100644 index 00000000..b83ff166 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Message/ITransientMessager.cs @@ -0,0 +1,6 @@ +namespace Mvc5Net48.Message +{ + public interface ITransientMessager : IMessager + { + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs new file mode 100644 index 00000000..b40b1319 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Message/LogMessager.cs @@ -0,0 +1,14 @@ +using System; + +namespace Mvc5Net48.Message +{ + internal class LogMessager : IMessager + { + public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; + + public void Dispose() + { + Console.WriteLine($"{nameof(LogMessager)} GC"); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/MachineMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/MachineMessager.cs new file mode 100644 index 00000000..8d91ec1d --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Message/MachineMessager.cs @@ -0,0 +1,13 @@ +using System; + +namespace Mvc5Net48.Message +{ + internal class MachineMessager : IMessager + { + public string OperationId { get; } = $"機器-{Guid.NewGuid()}"; + public void Dispose() + { + Console.WriteLine($"{nameof(MachineMessager)} GC"); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Message/MultiMessager.cs b/DI/Lab.MsDI/Mvc5Net48/Message/MultiMessager.cs new file mode 100644 index 00000000..d2206ff8 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Message/MultiMessager.cs @@ -0,0 +1,13 @@ +using System; + +namespace Mvc5Net48.Message +{ + public class MultiMessager : IScopeMessager, ISingleMessager, ITransientMessager + { + public string OperationId { get; } = $"多個接口-{Guid.NewGuid()}"; + public void Dispose() + { + Console.WriteLine($"{nameof(MultiMessager)} GC"); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Mvc5Net48.csproj b/DI/Lab.MsDI/Mvc5Net48/Mvc5Net48.csproj new file mode 100644 index 00000000..543d462b --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Mvc5Net48.csproj @@ -0,0 +1,202 @@ + + + + + Debug + AnyCPU + + + 2.0 + {91B2AC8D-1516-4D84-AF08-D72A50854607} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Mvc5Net48 + Mvc5Net48 + v4.8 + true + + 44362 + + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + true + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.5.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + + + ..\packages\Microsoft.Extensions.DependencyInjection.3.1.9\lib\net461\Microsoft.Extensions.DependencyInjection.dll + True + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + True + + + ..\packages\NLog.4.7.6\lib\net45\NLog.dll + + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + + + + + + + + + + + + + + + + + + + + ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll + + + ..\packages\Microsoft.AspNet.Webpages.3.2.7\lib\net45\System.Web.Webpages.dll + + + ..\packages\Microsoft.AspNet.Webpages.3.2.7\lib\net45\System.Web.Webpages.Deployment.dll + + + ..\packages\Microsoft.AspNet.Webpages.3.2.7\lib\net45\System.Web.Webpages.Razor.dll + + + ..\packages\Microsoft.AspNet.Webpages.3.2.7\lib\net45\System.Web.Helpers.dll + + + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + + ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll + + + ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll + + + + + + + + + + + + + + + + + Global.asax + + + + + + + + + + + + + + + + + PreserveNewest + + + Designer + + + + + + Web.config + + + Web.config + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + True + True + 60289 + / + https://localhost:44362/ + False + False + + + False + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/NLog.config b/DI/Lab.MsDI/Mvc5Net48/NLog.config new file mode 100644 index 00000000..cd5f89eb --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/NLog.config @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DI/Lab.MsDI/Mvc5Net48/NLog.xsd b/DI/Lab.MsDI/Mvc5Net48/NLog.xsd new file mode 100644 index 00000000..32246a71 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/NLog.xsd @@ -0,0 +1,3644 @@ + + + + + + + + + + + + + + + Watch config file for changes and reload automatically. + + + + + Print internal NLog messages to the console. Default value is: false + + + + + Print internal NLog messages to the console error output. Default value is: false + + + + + Write internal NLog messages to the specified file. + + + + + Log level threshold for internal log messages. Default value is: Info. + + + + + Global log level threshold for application log messages. Messages below this level won't be logged. + + + + + Throw an exception when there is an internal error. Default value is: false. Not recommend to set to true in production! + + + + + Throw an exception when there is a configuration error. If not set, determined by throwExceptions. + + + + + Gets or sets a value indicating whether Variables should be kept on configuration reload. Default value is: false. + + + + + Write internal NLog messages to the System.Diagnostics.Trace. Default value is: false. + + + + + Write timestamps for internal NLog messages. Default value is: true. + + + + + Use InvariantCulture as default culture instead of CurrentCulture. Default value is: false. + + + + + Perform message template parsing and formatting of LogEvent messages (true = Always, false = Never, empty = Auto Detect). Default value is: empty. + + + + + + + + + + + + + + Make all targets within this section asynchronous (creates additional threads but the calling thread isn't blocked by any target writes). + + + + + + + + + + + + + + + + + Prefix for targets/layout renderers/filters/conditions loaded from this assembly. + + + + + Load NLog extensions from the specified file (*.dll) + + + + + Load NLog extensions from the specified assembly. Assembly name should be fully qualified. + + + + + + + + + + Filter on the name of the logger. May include wildcard characters ('*' or '?'). + + + + + Comma separated list of levels that this rule matches. + + + + + Minimum level that this rule matches. + + + + + Maximum level that this rule matches. + + + + + Level that this rule matches. + + + + + Comma separated list of target names. + + + + + Ignore further rules if this one matches. + + + + + Enable this rule. Note: disabled rules aren't available from the API. + + + + + Rule identifier to allow rule lookup with Configuration.FindRuleByName and Configuration.RemoveRuleByName. + + + + + + + + + + + + + + + Default action if none of the filters match. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the file to be included. You could use * wildcard. The name is relative to the name of the current config file. + + + + + Ignore any errors in the include file. + + + + + + + + Variable value. Note, the 'value' attribute has precedence over this one. + + + + + + Variable name. + + + + + Variable value. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Number of log events that should be processed in a batch by the lazy writer thread. + + + + + Whether to use the locking queue, instead of a lock-free concurrent queue The locking queue is less concurrent when many logger threads, but reduces memory allocation + + + + + Limit of full s to write before yielding into Performance is better when writing many small batches, than writing a single large batch + + + + + Action to be taken when the lazy writer thread request queue count exceeds the set limit. + + + + + Limit on the number of requests in the lazy writer thread request queue. + + + + + Time in milliseconds to sleep between batches. (1 or less means trigger on new activity) + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + Delay the flush until the LogEvent has been confirmed as written + + + + + Condition expression. Log events who meet this condition will cause a flush on the wrapped target. + + + + + Only flush when LogEvent matches condition. Ignore explicit-flush, config-reload-flush and shutdown-flush + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Number of log events to be buffered. + + + + + Timeout (in milliseconds) after which the contents of buffer will be flushed if there's no write in the specified period of time. Use -1 to disable timed flushes. + + + + + Action to take if the buffer overflows. + + + + + Indicates whether to use sliding timeout. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Encoding to be used. + + + + + Instance of that is used to format log messages. + + + + + End of line value if a newline is appended at the end of log message . + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. + + + + + Network address. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + The number of seconds a connection will remain idle before the first keep-alive probe is sent + + + + + Maximum queue size. + + + + + Maximum current connections. 0 = no maximum. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Indicates whether to keep connection open whenever possible. + + + + + NDLC item separator. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + Renderer for log4j:event logger-xml-attribute (Default ${logger}) + + + + + Indicates whether to include NLog-specific extensions to log4j schema. + + + + + Indicates whether to include contents of the stack. + + + + + Indicates whether to include stack contents. + + + + + Indicates whether to include dictionary contents. + + + + + Indicates whether to include dictionary contents. + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + Option to include all properties from the log events + + + + + AppInfo field. By default it's the friendly name of the current AppDomain. + + + + + NDC item separator. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Layout that should be use to calculate the value for the parameter. + + + + + Viewer parameter name. + + + + + Whether an attribute with empty value should be included in the output + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether to auto-check if the console is available. - Disables console writing if Environment.UserInteractive = False (Windows Service) - Disables console writing if Console Standard Input is not available (Non-Console-App) + + + + + Enables output using ANSI Color Codes + + + + + The encoding for writing messages to the . + + + + + Indicates whether the error stream (stderr) should be used instead of the output stream (stdout). + + + + + Indicates whether to auto-flush after + + + + + Indicates whether to auto-check if the console has been redirected to file - Disables coloring logic when System.Console.IsOutputRedirected = true + + + + + Indicates whether to use default row highlighting rules. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Condition that must be met in order to set the specified foreground and background color. + + + + + Background color. + + + + + Foreground color. + + + + + + + + + + + + + + + + + Compile the ? This can improve the performance, but at the costs of more memory usage. If false, the Regex Cache is used. + + + + + Condition that must be met before scanning the row for highlight of words + + + + + Indicates whether to ignore case when comparing texts. + + + + + Regular expression to be matched. You must specify either text or regex. + + + + + Text to be matched. You must specify either text or regex. + + + + + Indicates whether to match whole words only. + + + + + Background color. + + + + + Foreground color. + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether to auto-flush after + + + + + Indicates whether to auto-check if the console is available - Disables console writing if Environment.UserInteractive = False (Windows Service) - Disables console writing if Console Standard Input is not available (Non-Console-App) + + + + + The encoding for writing messages to the . + + + + + Indicates whether to send the log messages to the standard error instead of the standard output. + + + + + Whether to enable batch writing using char[]-buffers, instead of using + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Obsolete - value will be ignored! The logging code always runs outside of transaction. Gets or sets a value indicating whether to use database transactions. Some data providers require this. + + + + + Indicates whether to keep the database connection open between the log events. + + + + + Name of the database provider. + + + + + Database password. If the ConnectionString is not provided this value will be used to construct the "Password=" part of the connection string. + + + + + Database host name. If the ConnectionString is not provided this value will be used to construct the "Server=" part of the connection string. + + + + + Database user name. If the ConnectionString is not provided this value will be used to construct the "User ID=" part of the connection string. + + + + + Name of the connection string (as specified in <connectionStrings> configuration section. + + + + + Connection string. When provided, it overrides the values specified in DBHost, DBUserName, DBPassword, DBDatabase. + + + + + Database name. If the ConnectionString is not provided this value will be used to construct the "Database=" part of the connection string. + + + + + Connection string using for installation and uninstallation. If not provided, regular ConnectionString is being used. + + + + + Configures isolated transaction batch writing. If supported by the database, then it will improve insert performance. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Text of the SQL command to be run on each log level. + + + + + Type of the SQL command to be run on each log level. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Convert format of the property value + + + + + Culture used for parsing property string-value for type-conversion + + + + + Value to assign on the object-property + + + + + Name for the object-property + + + + + Type of the object-property + + + + + + + + + + + + + + Type of the command. + + + + + Connection string to run the command against. If not provided, connection string from the target is used. + + + + + Indicates whether to ignore failures. + + + + + Command text. + + + + + + + + + + + + + + + + + + + Database parameter name. + + + + + Layout that should be use to calculate the value for the parameter. + + + + + Database parameter DbType. + + + + + Database parameter size. + + + + + Database parameter precision. + + + + + Database parameter scale. + + + + + Type of the parameter. + + + + + Whether empty value should translate into DbNull. Requires database column to allow NULL values. + + + + + Convert format of the database parameter value. + + + + + Culture used for parsing parameter string-value for type-conversion + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Layout that renders event Category. + + + + + Optional entry type. When not set, or when not convertible to then determined by + + + + + Layout that renders event ID. + + + + + Name of the Event Log to write to. This can be System, Application or any user-defined name. + + + + + Name of the machine on which Event Log service is running. + + + + + Maximum Event log size in kilobytes. + + + + + Message length limit to write to the Event Log. + + + + + Value to be used as the event Source. + + + + + Action to take if the message is larger than the option. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Indicates whether to return to the first target after any successful write. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + File encoding. + + + + + Line ending mode. + + + + + Maximum days of archive files that should be kept. + + + + + Indicates whether to compress archive files into the zip archive format. + + + + + Way file archives are numbered. + + + + + Name of the file to be used for an archive. + + + + + Is the an absolute or relative path? + + + + + Indicates whether to automatically archive log files every time the specified time passes. + + + + + Size in bytes above which log files will be automatically archived. Warning: combining this with isn't supported. We cannot create multiple archive files, if they should have the same name. Choose: + + + + + Maximum number of archive files that should be kept. + + + + + Indicates whether the footer should be written only when the file is archived. + + + + + Maximum number of log file names that should be stored as existing. + + + + + Indicates whether to delete old log file on startup. + + + + + File attributes (Windows only). + + + + + Indicates whether to create directories if they do not exist. + + + + + Cleanup invalid values in a filename, e.g. slashes in a filename. If set to true, this can impact the performance of massive writes. If set to false, nothing gets written when the filename is wrong. + + + + + Value of the file size threshold to archive old log file on startup. + + + + + Indicates whether to archive old log file on startup. + + + + + Value specifying the date format to use when archiving files. + + + + + Indicates whether to enable log file(s) to be deleted. + + + + + Indicates whether to write BOM (byte order mark) in created files + + + + + Indicates whether to replace file contents on each write instead of appending log message at the end. + + + + + Indicates whether file creation calls should be synchronized by a system global mutex. + + + + + Gets or set a value indicating whether a managed file stream is forced, instead of using the native implementation. + + + + + Is the an absolute or relative path? + + + + + Name of the file to write to. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Indicates whether concurrent writes to the log file by multiple processes on different network hosts. + + + + + Maximum number of seconds that files are kept open. If this number is negative the files are not automatically closed after a period of inactivity. + + + + + Number of files to be kept open. Setting this to a higher value may improve performance in a situation where a single File target is writing to many files (such as splitting by level or by logger). + + + + + Indicates whether to keep log file open instead of opening and closing it on each logging event. + + + + + Whether or not this target should just discard all data that its asked to write. Mostly used for when testing NLog Stack except final write + + + + + Indicates whether concurrent writes to the log file by multiple processes on the same host. + + + + + Number of times the write is appended on the file before NLog discards the log message. + + + + + Delay in milliseconds to wait before attempting to write to the file again. + + + + + Log file buffer size in bytes. + + + + + Maximum number of seconds before open files are flushed. If this number is negative or zero the files are not flushed by timer. + + + + + Indicates whether to automatically flush the file buffers after each log message. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Condition expression. Log events who meet this condition will be forwarded to the wrapped target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Windows domain name to change context to. + + + + + Required impersonation level. + + + + + Type of the logon provider. + + + + + Logon Type. + + + + + User account password. + + + + + Indicates whether to revert to the credentials of the process instead of impersonating another user. + + + + + Username to change context to. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Interval in which messages will be written up to the number of messages. + + + + + Maximum allowed number of messages written per . + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Endpoint address. + + + + + Name of the endpoint configuration in WCF configuration file. + + + + + Indicates whether to use a WCF service contract that is one way (fire and forget) or two way (request-reply) + + + + + Client ID. + + + + + Indicates whether to include per-event properties in the payload sent to the server. + + + + + Indicates whether to use binary message encoding. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + Layout that should be use to calculate the value for the parameter. + + + + + Name of the parameter. + + + + + Type of the parameter. + + + + + Type of the parameter. Obsolete alias for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether NewLine characters in the body should be replaced with tags. + + + + + Priority used for sending mails. + + + + + Encoding to be used for sending e-mail. + + + + + BCC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + CC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + Indicates whether to add new lines between log entries. + + + + + Indicates whether to send message as HTML instead of plain text. + + + + + Sender's email address (e.g. joe@domain.com). + + + + + Mail message body (repeated for each log message send in one mail). + + + + + Mail subject. + + + + + Recipients' email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Indicates the SMTP client timeout. + + + + + SMTP Server to be used for sending. + + + + + SMTP Authentication mode. + + + + + Username used to connect to SMTP server (used when SmtpAuthentication is set to "basic"). + + + + + Password used to authenticate against SMTP server (used when SmtpAuthentication is set to "basic"). + + + + + Indicates whether SSL (secure sockets layer) should be used when communicating with SMTP server. + + + + + Port number that SMTP Server is listening on. + + + + + Indicates whether the default Settings from System.Net.MailSettings should be used. + + + + + Folder where applications save mail messages to be processed by the local SMTP server. + + + + + Specifies how outgoing email messages will be handled. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Max number of items to have in memory + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Class name. + + + + + Method name. The method must be public and static. Use the AssemblyQualifiedName , https://msdn.microsoft.com/en-us/library/system.type.assemblyqualifiedname(v=vs.110).aspx e.g. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Encoding to be used. + + + + + End of line value if a newline is appended at the end of log message . + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Network address. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + The number of seconds a connection will remain idle before the first keep-alive probe is sent + + + + + Indicates whether to keep connection open whenever possible. + + + + + Maximum current connections. 0 = no maximum. + + + + + Maximum queue size. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Encoding to be used. + + + + + Instance of that is used to format log messages. + + + + + End of line value if a newline is appended at the end of log message . + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. + + + + + Network address. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + The number of seconds a connection will remain idle before the first keep-alive probe is sent + + + + + Maximum queue size. + + + + + Maximum current connections. 0 = no maximum. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Indicates whether to keep connection open whenever possible. + + + + + NDLC item separator. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + Renderer for log4j:event logger-xml-attribute (Default ${logger}) + + + + + Indicates whether to include NLog-specific extensions to log4j schema. + + + + + Indicates whether to include contents of the stack. + + + + + Indicates whether to include stack contents. + + + + + Indicates whether to include dictionary contents. + + + + + Indicates whether to include dictionary contents. + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + Option to include all properties from the log events + + + + + AppInfo field. By default it's the friendly name of the current AppDomain. + + + + + NDC item separator. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Indicates whether to perform layout calculation. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Indicates whether performance counter should be automatically created. + + + + + Name of the performance counter category. + + + + + Counter help text. + + + + + Name of the performance counter. + + + + + Performance counter type. + + + + + The value by which to increment the counter. + + + + + Performance counter instance name. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Default filter to be applied when no specific rule matches. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + Condition to be tested. + + + + + Resulting filter to be applied when the condition matches. + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Number of times to repeat each log message. + + + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Number of retries that should be attempted on the wrapped target in case of a failure. + + + + + Time to wait between retries in milliseconds. + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Forward to (Instead of ) + + + + + Always use independent of + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Should we include the BOM (Byte-order-mark) for UTF? Influences the property. This will only work for UTF-8. + + + + + Web service method name. Only used with Soap. + + + + + Web service namespace. Only used with Soap. + + + + + Protocol to be used when calling web service. + + + + + Custom proxy address, include port separated by a colon + + + + + Encoding. + + + + + Web service URL. + + + + + Value whether escaping be done according to the old NLog style (Very non-standard) + + + + + Value whether escaping be done according to Rfc3986 (Supports Internationalized Resource Identifiers - IRIs) + + + + + Indicates whether to pre-authenticate the HttpWebRequest (Requires 'Authorization' in parameters) + + + + + Name of the root XML element, if POST of XML document chosen. If so, this property must not be null. (see and ). + + + + + (optional) root namespace of the XML document, if POST of XML document chosen. (see and ). + + + + + Proxy configuration when calling web service + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Footer layout. + + + + + Header layout. + + + + + Body layout (can be repeated multiple times). + + + + + Custom column delimiter value (valid when ColumnDelimiter is set to 'Custom'). + + + + + Column delimiter. + + + + + Quote Character. + + + + + Quoting mode. + + + + + Indicates whether CVS should include header. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Layout of the column. + + + + + Name of the column. + + + + + Override of Quoting mode + + + + + + + + + + + + + + + + + + + + + Should forward slashes be escaped? If true, / will be converted to \/ + + + + + Option to render the empty object value {} + + + + + Option to suppress the extra spaces in the output json + + + + + List of property names to exclude when is true + + + + + Option to include all properties from the log event (as JSON) + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + How far should the JSON serializer follow object references before backing off + + + + + + + + + + + + + + + + + Layout that will be rendered as the attribute's value. + + + + + Name of the attribute. + + + + + Determines whether or not this attribute will be Json encoded. + + + + + Should forward slashes be escaped? If true, / will be converted to \/ + + + + + Indicates whether to escape non-ascii characters + + + + + Whether an attribute with empty value should be included in the output + + + + + + + + + + + + + + Footer layout. + + + + + Header layout. + + + + + Body layout (can be repeated multiple times). + + + + + + + + + + + + + + + + + + + + + Option to include all properties from the log events + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the stack. + + + + + Indicates whether to include contents of the stack. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + + + + + + + + + + Layout text. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + List of property names to exclude when is true + + + + + Option to include all properties from the log event (as XML) + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + How far should the XML serializer follow object references before backing off + + + + + XML element name to use for rendering IList-collections items + + + + + XML attribute name to use when rendering property-key When null (or empty) then key-attribute is not included + + + + + XML element name to use when rendering properties + + + + + XML attribute name to use when rendering property-value When null (or empty) then value-attribute is not included and value is formatted as XML-element-value + + + + + Name of the root XML element + + + + + Value inside the root XML element + + + + + Whether a ElementValue with empty value should be included in the output + + + + + Auto indent and create new lines + + + + + Determines whether or not this attribute will be Xml encoded. + + + + + + + + + + + + + + + Layout that will be rendered as the attribute's value. + + + + + Name of the attribute. + + + + + Determines whether or not this attribute will be Xml encoded. + + + + + Whether an attribute with empty value should be included in the output + + + + + + + + + + + + + + + + + + + + + + + + + Determines whether or not this attribute will be Xml encoded. + + + + + Name of the element + + + + + Value inside the element + + + + + Whether a ElementValue with empty value should be included in the output + + + + + Auto indent and create new lines + + + + + List of property names to exclude when is true + + + + + Option to include all properties from the log event (as XML) + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + How far should the XML serializer follow object references before backing off + + + + + XML element name to use for rendering IList-collections items + + + + + XML attribute name to use when rendering property-key When null (or empty) then key-attribute is not included + + + + + XML element name to use when rendering properties + + + + + XML attribute name to use when rendering property-value When null (or empty) then value-attribute is not included and value is formatted as XML-element-value + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Condition expression. + + + + + + + + + + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + Substring to be matched. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + String to compare the layout to. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + Substring to be matched. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + String to compare the layout to. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + + + + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Default number of unique filter values to expect, will automatically increase if needed + + + + + Applies the configured action to the initial logevent that starts the timeout period. Used to configure that it should ignore all events until timeout. + + + + + Layout to be used to filter log messages. + + + + + Max number of unique filter values to expect simultaneously + + + + + Max length of filter values, will truncate if above limit + + + + + How long before a filter expires, and logging is accepted again + + + + + Default buffer size for the internal buffers + + + + + Reuse internal buffers, and doesn't have to constantly allocate new buffers + + + + + Append FilterCount to the when an event is no longer filtered + + + + + Insert FilterCount value into when an event is no longer filtered + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Properties/AssemblyInfo.cs b/DI/Lab.MsDI/Mvc5Net48/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..fecc934b --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Mvc5Net48")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Mvc5Net48")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("91b2ac8d-1516-4d84-af08-d72a50854607")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DI/Lab.MsDI/Mvc5Net48/ServiceScopeHttpModule.cs b/DI/Lab.MsDI/Mvc5Net48/ServiceScopeHttpModule.cs new file mode 100644 index 00000000..7cdb30a7 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/ServiceScopeHttpModule.cs @@ -0,0 +1,42 @@ +using System; +using System.Web; +using Microsoft.Extensions.DependencyInjection; + +namespace Mvc5Net48 +{ + internal class ServiceScopeHttpModule : IHttpModule + { + private static ServiceProvider s_serviceProvider; + + public static void SetServiceProvider(IServiceProvider serviceProvider) + { + s_serviceProvider = serviceProvider as ServiceProvider; + } + + public void Dispose() + { + s_serviceProvider?.Dispose(); + } + + public void Init(HttpApplication context) + { + context.BeginRequest += this.Context_BeginRequest; + context.EndRequest += this.Context_EndRequest; + } + + private void Context_BeginRequest(object sender, EventArgs e) + { + var context = ((HttpApplication) sender).Context; + context.Items[typeof(IServiceScope)] = s_serviceProvider.CreateScope(); + } + + private void Context_EndRequest(object sender, EventArgs e) + { + var context = ((HttpApplication) sender).Context; + if (context.Items[typeof(IServiceScope)] is IServiceScope scope) + { + scope.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Views/Default/Index.cshtml b/DI/Lab.MsDI/Mvc5Net48/Views/Default/Index.cshtml new file mode 100644 index 00000000..8ce20645 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Views/Default/Index.cshtml @@ -0,0 +1,18 @@ +@model dynamic + +@{ + Layout = null; +} + + + + + + title + + +
+ @Html.Raw(HttpUtility.HtmlDecode(@ViewBag.Message)) +
+ + \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Views/Default1/Index.cshtml b/DI/Lab.MsDI/Mvc5Net48/Views/Default1/Index.cshtml new file mode 100644 index 00000000..8ce20645 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Views/Default1/Index.cshtml @@ -0,0 +1,18 @@ +@model dynamic + +@{ + Layout = null; +} + + + + + + title + + +
+ @Html.Raw(HttpUtility.HtmlDecode(@ViewBag.Message)) +
+ + \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Views/web.config b/DI/Lab.MsDI/Mvc5Net48/Views/web.config new file mode 100644 index 00000000..3a3275a4 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Views/web.config @@ -0,0 +1,42 @@ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DI/Lab.MsDI/Mvc5Net48/Web.Debug.config b/DI/Lab.MsDI/Mvc5Net48/Web.Debug.config new file mode 100644 index 00000000..fae9cfef --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Web.Release.config b/DI/Lab.MsDI/Mvc5Net48/Web.Release.config new file mode 100644 index 00000000..da6e960b --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/Mvc5Net48/Web.config b/DI/Lab.MsDI/Mvc5Net48/Web.config new file mode 100644 index 00000000..58fb7220 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/Web.config @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DI/Lab.MsDI/Mvc5Net48/packages.config b/DI/Lab.MsDI/Mvc5Net48/packages.config new file mode 100644 index 00000000..138971e0 --- /dev/null +++ b/DI/Lab.MsDI/Mvc5Net48/packages.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/App_Start/DefaultDependencyResolver.cs b/DI/Lab.MsDI/WebApiNet48/App_Start/DefaultDependencyResolver.cs new file mode 100644 index 00000000..e9e64035 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/App_Start/DefaultDependencyResolver.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Web.Http.Dependencies; +using Microsoft.Extensions.DependencyInjection; + +namespace WebApiNet48 +{ + public class DefaultDependencyResolver : IDependencyResolver + { + private readonly IServiceProvider _serviceProvider; + private IServiceScope _serviceScope; + + public DefaultDependencyResolver(IServiceProvider serviceProvider, IServiceScope serviceScope = null) + { + this._serviceProvider = serviceProvider; + this._serviceScope = serviceScope; + } + + public object GetService(Type serviceType) + { + return this._serviceProvider.GetService(serviceType); + } + + public IEnumerable GetServices(Type serviceType) + { + return this._serviceProvider.GetServices(serviceType); + } + + public IDependencyScope BeginScope() + { + this._serviceScope = this._serviceProvider.CreateScope(); + return new DefaultDependencyResolver(this._serviceScope.ServiceProvider,this._serviceScope); + } + + public void Dispose() + { + this._serviceScope?.Dispose(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/App_Start/DependencyInjectionConfig.cs b/DI/Lab.MsDI/WebApiNet48/App_Start/DependencyInjectionConfig.cs new file mode 100644 index 00000000..fee3990d --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/App_Start/DependencyInjectionConfig.cs @@ -0,0 +1,40 @@ +using System; +using System.Linq; +using System.Web.Http; +using System.Web.Http.Controllers; +using Microsoft.Extensions.DependencyInjection; + +namespace WebApiNet48 +{ + public class DependencyInjectionConfig + { + public static void Register(HttpConfiguration config) + { + var services = ConfigureServices(); + + var provider = services.BuildServiceProvider(); + + var resolver = new DefaultDependencyResolver(provider); + config.DependencyResolver = resolver; + } + + /// + /// 使用 MS DI 註冊 + /// + /// + private static ServiceCollection ConfigureServices() + { + var services = new ServiceCollection(); + + //使用 Microsoft.Extensions.DependencyInjection 註冊 + services.AddControllersAsServices(typeof(DependencyInjectionConfig).Assembly.GetExportedTypes()); + + services.AddScoped(); + + services.AddTransient() + .AddSingleton() + .AddScoped(); + return services; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/App_Start/ServiceProviderExtensions.cs b/DI/Lab.MsDI/WebApiNet48/App_Start/ServiceProviderExtensions.cs new file mode 100644 index 00000000..ae5524bc --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/App_Start/ServiceProviderExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Http.Controllers; +using Microsoft.Extensions.DependencyInjection; + +namespace WebApiNet48 +{ + public static class ServiceProviderExtensions + { + public static IServiceCollection AddControllersAsServices(this IServiceCollection services, + IEnumerable controllerTypes) + { + var filter = controllerTypes.Where(t => !t.IsAbstract + && !t.IsGenericTypeDefinition) + .Where(t => typeof(IHttpController).IsAssignableFrom(t) + || t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)); + + foreach (var type in filter) + { + services.AddTransient(type); + } + + return services; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/App_Start/WebApiConfig.cs b/DI/Lab.MsDI/WebApiNet48/App_Start/WebApiConfig.cs new file mode 100644 index 00000000..19e9c7a4 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/App_Start/WebApiConfig.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Web.Http; + +namespace WebApiNet48 +{ + public static class WebApiConfig + { + public static void Register(HttpConfiguration config) + { + // Web API configuration and services + config.Filters.Add(new LogFilterAttribute()); + + // Web API routes + config.MapHttpAttributeRoutes(); + + config.Routes.MapHttpRoute( + name: "DefaultApi", + routeTemplate: "api/{controller}/{id}", + defaults: new { id = RouteParameter.Optional } + ); + } + } +} diff --git a/DI/Lab.MsDI/WebApiNet48/Controllers/Default1Controller.cs b/DI/Lab.MsDI/WebApiNet48/Controllers/Default1Controller.cs new file mode 100644 index 00000000..ea631cce --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Controllers/Default1Controller.cs @@ -0,0 +1,27 @@ +using System; +using System.Net.Http; +using System.Web.Http; +using NLog; + +namespace WebApiNet48.Controllers +{ + public class Default1Controller : ApiController + { + [HttpGet] + public IHttpActionResult Get() + { + var logger = LogManager.GetCurrentClassLogger(); + var requestScope = this.Request.GetDependencyScope(); + var transient = requestScope.GetService(typeof(ITransientMessager)) as ITransientMessager; + var scope = requestScope.GetService(typeof(IScopeMessager)) as IScopeMessager; + var single = requestScope.GetService(typeof(ISingleMessager)) as ISingleMessager; + var content = "我在 Controller.Get Action\r\n" + + $"transient:{transient.OperationId}\r\n" + + $"scope:{scope.OperationId}\r\n" + + $"single:{single.OperationId}"; + Console.WriteLine(content); + logger.Info(content); + return this.Ok(content); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Controllers/DefaultController.cs b/DI/Lab.MsDI/WebApiNet48/Controllers/DefaultController.cs new file mode 100644 index 00000000..51985f63 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Controllers/DefaultController.cs @@ -0,0 +1,45 @@ +using System; +using System.Web.Http; +using NLog; + +namespace WebApiNet48.Controllers +{ + public class DefaultController : ApiController + { + private IMessager Transient { get; } + + private IMessager Scope { get; } + + private IMessager Single { get; } + + public DefaultController(ITransientMessager transient, + IScopeMessager scope, + ISingleMessager single) + { + this.Transient = transient; + this.Scope = scope; + this.Single = single; + } + + [HttpGet] + public IHttpActionResult Get() + { + var logger = LogManager.GetCurrentClassLogger(); + + var content = "我在 Controller.Get Action\r\n" + + $"transient:{this.Transient.OperationId}\r\n" + + $"scope:{this.Scope.OperationId}\r\n" + + $"single:{this.Single.OperationId}"; + Console.WriteLine(content); + logger.Info(content); + + //this._logger.LogInformation("transient = {transient},scope = {scope},single = {single}", + // this.Transient.OperationId, + // this.Scope.OperationId, + // this.Single.OperationId); + return this.Ok(content); + } + + + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Global.asax b/DI/Lab.MsDI/WebApiNet48/Global.asax new file mode 100644 index 00000000..593fb3aa --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="WebApiNet48.WebApiApplication" Language="C#" %> diff --git a/DI/Lab.MsDI/WebApiNet48/Global.asax.cs b/DI/Lab.MsDI/WebApiNet48/Global.asax.cs new file mode 100644 index 00000000..3cb9d613 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Global.asax.cs @@ -0,0 +1,14 @@ +using System.Web; +using System.Web.Http; + +namespace WebApiNet48 +{ + public class WebApiApplication : HttpApplication + { + protected void Application_Start() + { + GlobalConfiguration.Configure(WebApiConfig.Register); + GlobalConfiguration.Configure(DependencyInjectionConfig.Register); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/LogFilterAttribute.cs b/DI/Lab.MsDI/WebApiNet48/LogFilterAttribute.cs new file mode 100644 index 00000000..f2055f34 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/LogFilterAttribute.cs @@ -0,0 +1,28 @@ +using System; +using System.Net.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using NLog; + +namespace WebApiNet48 +{ + public class LogFilterAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(HttpActionContext actionContext) + { + var requestScope = actionContext.Request.GetDependencyScope(); + + var transient = requestScope.GetService(typeof(ITransientMessager)) as MultiMessager; + var scope = requestScope.GetService(typeof(IScopeMessager)) as MultiMessager; + var single = requestScope.GetService(typeof(ISingleMessager)) as MultiMessager; + + var logger = LogManager.GetCurrentClassLogger(); + var content = "我在 LogFilterAttribute.OnActionExecuting\r\n" + + $"transient:{transient.OperationId}\r\n" + + $"scope:{scope.OperationId}\r\n" + + $"single:{single.OperationId}"; + Console.WriteLine(content); + logger.Info(content); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs new file mode 100644 index 00000000..9883346c --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Message/IMessager.cs @@ -0,0 +1,9 @@ +using System; + +namespace WebApiNet48 +{ + public interface IMessager:IDisposable + { + string OperationId { get; } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/IScopeMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/IScopeMessager.cs new file mode 100644 index 00000000..a29d7677 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Message/IScopeMessager.cs @@ -0,0 +1,6 @@ +namespace WebApiNet48 +{ + public interface IScopeMessager : IMessager + { + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/ISingleMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/ISingleMessager.cs new file mode 100644 index 00000000..27284507 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Message/ISingleMessager.cs @@ -0,0 +1,6 @@ +namespace WebApiNet48 +{ + public interface ISingleMessager : IMessager + { + } +} diff --git a/DI/Lab.MsDI/WebApiNet48/Message/ITransientMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/ITransientMessager.cs new file mode 100644 index 00000000..d9314497 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Message/ITransientMessager.cs @@ -0,0 +1,6 @@ +namespace WebApiNet48 +{ + public interface ITransientMessager : IMessager + { + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/LogMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/LogMessager.cs new file mode 100644 index 00000000..a73f42f4 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Message/LogMessager.cs @@ -0,0 +1,14 @@ +using System; + +namespace WebApiNet48 +{ + internal class LogMessager : IMessager + { + public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; + + public void Dispose() + { + Console.WriteLine($"{nameof(LogMessager)} GC"); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/MachineMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/MachineMessager.cs new file mode 100644 index 00000000..8aed0c19 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Message/MachineMessager.cs @@ -0,0 +1,14 @@ +using System; + +namespace WebApiNet48 +{ + internal class MachineMessager : IMessager + { + public string OperationId { get; } = $"機器-{Guid.NewGuid()}"; + + public void Dispose() + { + Console.WriteLine($"{nameof(MachineMessager)} GC"); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Message/MultiMessager.cs b/DI/Lab.MsDI/WebApiNet48/Message/MultiMessager.cs new file mode 100644 index 00000000..7e3edf7b --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Message/MultiMessager.cs @@ -0,0 +1,14 @@ +using System; + +namespace WebApiNet48 +{ + public class MultiMessager : IScopeMessager, ISingleMessager, ITransientMessager + { + public string OperationId { get; } = $"多個接口-{Guid.NewGuid()}"; + + public void Dispose() + { + Console.WriteLine($"{nameof(MultiMessager)} GC"); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/NLog.config b/DI/Lab.MsDI/WebApiNet48/NLog.config new file mode 100644 index 00000000..df272249 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/NLog.config @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DI/Lab.MsDI/WebApiNet48/NLog.xsd b/DI/Lab.MsDI/WebApiNet48/NLog.xsd new file mode 100644 index 00000000..32246a71 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/NLog.xsd @@ -0,0 +1,3644 @@ + + + + + + + + + + + + + + + Watch config file for changes and reload automatically. + + + + + Print internal NLog messages to the console. Default value is: false + + + + + Print internal NLog messages to the console error output. Default value is: false + + + + + Write internal NLog messages to the specified file. + + + + + Log level threshold for internal log messages. Default value is: Info. + + + + + Global log level threshold for application log messages. Messages below this level won't be logged. + + + + + Throw an exception when there is an internal error. Default value is: false. Not recommend to set to true in production! + + + + + Throw an exception when there is a configuration error. If not set, determined by throwExceptions. + + + + + Gets or sets a value indicating whether Variables should be kept on configuration reload. Default value is: false. + + + + + Write internal NLog messages to the System.Diagnostics.Trace. Default value is: false. + + + + + Write timestamps for internal NLog messages. Default value is: true. + + + + + Use InvariantCulture as default culture instead of CurrentCulture. Default value is: false. + + + + + Perform message template parsing and formatting of LogEvent messages (true = Always, false = Never, empty = Auto Detect). Default value is: empty. + + + + + + + + + + + + + + Make all targets within this section asynchronous (creates additional threads but the calling thread isn't blocked by any target writes). + + + + + + + + + + + + + + + + + Prefix for targets/layout renderers/filters/conditions loaded from this assembly. + + + + + Load NLog extensions from the specified file (*.dll) + + + + + Load NLog extensions from the specified assembly. Assembly name should be fully qualified. + + + + + + + + + + Filter on the name of the logger. May include wildcard characters ('*' or '?'). + + + + + Comma separated list of levels that this rule matches. + + + + + Minimum level that this rule matches. + + + + + Maximum level that this rule matches. + + + + + Level that this rule matches. + + + + + Comma separated list of target names. + + + + + Ignore further rules if this one matches. + + + + + Enable this rule. Note: disabled rules aren't available from the API. + + + + + Rule identifier to allow rule lookup with Configuration.FindRuleByName and Configuration.RemoveRuleByName. + + + + + + + + + + + + + + + Default action if none of the filters match. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the file to be included. You could use * wildcard. The name is relative to the name of the current config file. + + + + + Ignore any errors in the include file. + + + + + + + + Variable value. Note, the 'value' attribute has precedence over this one. + + + + + + Variable name. + + + + + Variable value. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Number of log events that should be processed in a batch by the lazy writer thread. + + + + + Whether to use the locking queue, instead of a lock-free concurrent queue The locking queue is less concurrent when many logger threads, but reduces memory allocation + + + + + Limit of full s to write before yielding into Performance is better when writing many small batches, than writing a single large batch + + + + + Action to be taken when the lazy writer thread request queue count exceeds the set limit. + + + + + Limit on the number of requests in the lazy writer thread request queue. + + + + + Time in milliseconds to sleep between batches. (1 or less means trigger on new activity) + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + Delay the flush until the LogEvent has been confirmed as written + + + + + Condition expression. Log events who meet this condition will cause a flush on the wrapped target. + + + + + Only flush when LogEvent matches condition. Ignore explicit-flush, config-reload-flush and shutdown-flush + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Number of log events to be buffered. + + + + + Timeout (in milliseconds) after which the contents of buffer will be flushed if there's no write in the specified period of time. Use -1 to disable timed flushes. + + + + + Action to take if the buffer overflows. + + + + + Indicates whether to use sliding timeout. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Encoding to be used. + + + + + Instance of that is used to format log messages. + + + + + End of line value if a newline is appended at the end of log message . + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. + + + + + Network address. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + The number of seconds a connection will remain idle before the first keep-alive probe is sent + + + + + Maximum queue size. + + + + + Maximum current connections. 0 = no maximum. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Indicates whether to keep connection open whenever possible. + + + + + NDLC item separator. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + Renderer for log4j:event logger-xml-attribute (Default ${logger}) + + + + + Indicates whether to include NLog-specific extensions to log4j schema. + + + + + Indicates whether to include contents of the stack. + + + + + Indicates whether to include stack contents. + + + + + Indicates whether to include dictionary contents. + + + + + Indicates whether to include dictionary contents. + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + Option to include all properties from the log events + + + + + AppInfo field. By default it's the friendly name of the current AppDomain. + + + + + NDC item separator. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Layout that should be use to calculate the value for the parameter. + + + + + Viewer parameter name. + + + + + Whether an attribute with empty value should be included in the output + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether to auto-check if the console is available. - Disables console writing if Environment.UserInteractive = False (Windows Service) - Disables console writing if Console Standard Input is not available (Non-Console-App) + + + + + Enables output using ANSI Color Codes + + + + + The encoding for writing messages to the . + + + + + Indicates whether the error stream (stderr) should be used instead of the output stream (stdout). + + + + + Indicates whether to auto-flush after + + + + + Indicates whether to auto-check if the console has been redirected to file - Disables coloring logic when System.Console.IsOutputRedirected = true + + + + + Indicates whether to use default row highlighting rules. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Condition that must be met in order to set the specified foreground and background color. + + + + + Background color. + + + + + Foreground color. + + + + + + + + + + + + + + + + + Compile the ? This can improve the performance, but at the costs of more memory usage. If false, the Regex Cache is used. + + + + + Condition that must be met before scanning the row for highlight of words + + + + + Indicates whether to ignore case when comparing texts. + + + + + Regular expression to be matched. You must specify either text or regex. + + + + + Text to be matched. You must specify either text or regex. + + + + + Indicates whether to match whole words only. + + + + + Background color. + + + + + Foreground color. + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether to auto-flush after + + + + + Indicates whether to auto-check if the console is available - Disables console writing if Environment.UserInteractive = False (Windows Service) - Disables console writing if Console Standard Input is not available (Non-Console-App) + + + + + The encoding for writing messages to the . + + + + + Indicates whether to send the log messages to the standard error instead of the standard output. + + + + + Whether to enable batch writing using char[]-buffers, instead of using + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Obsolete - value will be ignored! The logging code always runs outside of transaction. Gets or sets a value indicating whether to use database transactions. Some data providers require this. + + + + + Indicates whether to keep the database connection open between the log events. + + + + + Name of the database provider. + + + + + Database password. If the ConnectionString is not provided this value will be used to construct the "Password=" part of the connection string. + + + + + Database host name. If the ConnectionString is not provided this value will be used to construct the "Server=" part of the connection string. + + + + + Database user name. If the ConnectionString is not provided this value will be used to construct the "User ID=" part of the connection string. + + + + + Name of the connection string (as specified in <connectionStrings> configuration section. + + + + + Connection string. When provided, it overrides the values specified in DBHost, DBUserName, DBPassword, DBDatabase. + + + + + Database name. If the ConnectionString is not provided this value will be used to construct the "Database=" part of the connection string. + + + + + Connection string using for installation and uninstallation. If not provided, regular ConnectionString is being used. + + + + + Configures isolated transaction batch writing. If supported by the database, then it will improve insert performance. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Text of the SQL command to be run on each log level. + + + + + Type of the SQL command to be run on each log level. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Convert format of the property value + + + + + Culture used for parsing property string-value for type-conversion + + + + + Value to assign on the object-property + + + + + Name for the object-property + + + + + Type of the object-property + + + + + + + + + + + + + + Type of the command. + + + + + Connection string to run the command against. If not provided, connection string from the target is used. + + + + + Indicates whether to ignore failures. + + + + + Command text. + + + + + + + + + + + + + + + + + + + Database parameter name. + + + + + Layout that should be use to calculate the value for the parameter. + + + + + Database parameter DbType. + + + + + Database parameter size. + + + + + Database parameter precision. + + + + + Database parameter scale. + + + + + Type of the parameter. + + + + + Whether empty value should translate into DbNull. Requires database column to allow NULL values. + + + + + Convert format of the database parameter value. + + + + + Culture used for parsing parameter string-value for type-conversion + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Layout that renders event Category. + + + + + Optional entry type. When not set, or when not convertible to then determined by + + + + + Layout that renders event ID. + + + + + Name of the Event Log to write to. This can be System, Application or any user-defined name. + + + + + Name of the machine on which Event Log service is running. + + + + + Maximum Event log size in kilobytes. + + + + + Message length limit to write to the Event Log. + + + + + Value to be used as the event Source. + + + + + Action to take if the message is larger than the option. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Indicates whether to return to the first target after any successful write. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + File encoding. + + + + + Line ending mode. + + + + + Maximum days of archive files that should be kept. + + + + + Indicates whether to compress archive files into the zip archive format. + + + + + Way file archives are numbered. + + + + + Name of the file to be used for an archive. + + + + + Is the an absolute or relative path? + + + + + Indicates whether to automatically archive log files every time the specified time passes. + + + + + Size in bytes above which log files will be automatically archived. Warning: combining this with isn't supported. We cannot create multiple archive files, if they should have the same name. Choose: + + + + + Maximum number of archive files that should be kept. + + + + + Indicates whether the footer should be written only when the file is archived. + + + + + Maximum number of log file names that should be stored as existing. + + + + + Indicates whether to delete old log file on startup. + + + + + File attributes (Windows only). + + + + + Indicates whether to create directories if they do not exist. + + + + + Cleanup invalid values in a filename, e.g. slashes in a filename. If set to true, this can impact the performance of massive writes. If set to false, nothing gets written when the filename is wrong. + + + + + Value of the file size threshold to archive old log file on startup. + + + + + Indicates whether to archive old log file on startup. + + + + + Value specifying the date format to use when archiving files. + + + + + Indicates whether to enable log file(s) to be deleted. + + + + + Indicates whether to write BOM (byte order mark) in created files + + + + + Indicates whether to replace file contents on each write instead of appending log message at the end. + + + + + Indicates whether file creation calls should be synchronized by a system global mutex. + + + + + Gets or set a value indicating whether a managed file stream is forced, instead of using the native implementation. + + + + + Is the an absolute or relative path? + + + + + Name of the file to write to. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Indicates whether concurrent writes to the log file by multiple processes on different network hosts. + + + + + Maximum number of seconds that files are kept open. If this number is negative the files are not automatically closed after a period of inactivity. + + + + + Number of files to be kept open. Setting this to a higher value may improve performance in a situation where a single File target is writing to many files (such as splitting by level or by logger). + + + + + Indicates whether to keep log file open instead of opening and closing it on each logging event. + + + + + Whether or not this target should just discard all data that its asked to write. Mostly used for when testing NLog Stack except final write + + + + + Indicates whether concurrent writes to the log file by multiple processes on the same host. + + + + + Number of times the write is appended on the file before NLog discards the log message. + + + + + Delay in milliseconds to wait before attempting to write to the file again. + + + + + Log file buffer size in bytes. + + + + + Maximum number of seconds before open files are flushed. If this number is negative or zero the files are not flushed by timer. + + + + + Indicates whether to automatically flush the file buffers after each log message. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Condition expression. Log events who meet this condition will be forwarded to the wrapped target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Windows domain name to change context to. + + + + + Required impersonation level. + + + + + Type of the logon provider. + + + + + Logon Type. + + + + + User account password. + + + + + Indicates whether to revert to the credentials of the process instead of impersonating another user. + + + + + Username to change context to. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Interval in which messages will be written up to the number of messages. + + + + + Maximum allowed number of messages written per . + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Endpoint address. + + + + + Name of the endpoint configuration in WCF configuration file. + + + + + Indicates whether to use a WCF service contract that is one way (fire and forget) or two way (request-reply) + + + + + Client ID. + + + + + Indicates whether to include per-event properties in the payload sent to the server. + + + + + Indicates whether to use binary message encoding. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + Layout that should be use to calculate the value for the parameter. + + + + + Name of the parameter. + + + + + Type of the parameter. + + + + + Type of the parameter. Obsolete alias for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether NewLine characters in the body should be replaced with tags. + + + + + Priority used for sending mails. + + + + + Encoding to be used for sending e-mail. + + + + + BCC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + CC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + Indicates whether to add new lines between log entries. + + + + + Indicates whether to send message as HTML instead of plain text. + + + + + Sender's email address (e.g. joe@domain.com). + + + + + Mail message body (repeated for each log message send in one mail). + + + + + Mail subject. + + + + + Recipients' email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Indicates the SMTP client timeout. + + + + + SMTP Server to be used for sending. + + + + + SMTP Authentication mode. + + + + + Username used to connect to SMTP server (used when SmtpAuthentication is set to "basic"). + + + + + Password used to authenticate against SMTP server (used when SmtpAuthentication is set to "basic"). + + + + + Indicates whether SSL (secure sockets layer) should be used when communicating with SMTP server. + + + + + Port number that SMTP Server is listening on. + + + + + Indicates whether the default Settings from System.Net.MailSettings should be used. + + + + + Folder where applications save mail messages to be processed by the local SMTP server. + + + + + Specifies how outgoing email messages will be handled. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Max number of items to have in memory + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Class name. + + + + + Method name. The method must be public and static. Use the AssemblyQualifiedName , https://msdn.microsoft.com/en-us/library/system.type.assemblyqualifiedname(v=vs.110).aspx e.g. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Encoding to be used. + + + + + End of line value if a newline is appended at the end of log message . + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Network address. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + The number of seconds a connection will remain idle before the first keep-alive probe is sent + + + + + Indicates whether to keep connection open whenever possible. + + + + + Maximum current connections. 0 = no maximum. + + + + + Maximum queue size. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Encoding to be used. + + + + + Instance of that is used to format log messages. + + + + + End of line value if a newline is appended at the end of log message . + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. + + + + + Network address. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + The number of seconds a connection will remain idle before the first keep-alive probe is sent + + + + + Maximum queue size. + + + + + Maximum current connections. 0 = no maximum. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Indicates whether to keep connection open whenever possible. + + + + + NDLC item separator. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + Renderer for log4j:event logger-xml-attribute (Default ${logger}) + + + + + Indicates whether to include NLog-specific extensions to log4j schema. + + + + + Indicates whether to include contents of the stack. + + + + + Indicates whether to include stack contents. + + + + + Indicates whether to include dictionary contents. + + + + + Indicates whether to include dictionary contents. + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + Option to include all properties from the log events + + + + + AppInfo field. By default it's the friendly name of the current AppDomain. + + + + + NDC item separator. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Indicates whether to perform layout calculation. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Indicates whether performance counter should be automatically created. + + + + + Name of the performance counter category. + + + + + Counter help text. + + + + + Name of the performance counter. + + + + + Performance counter type. + + + + + The value by which to increment the counter. + + + + + Performance counter instance name. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Default filter to be applied when no specific rule matches. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + Condition to be tested. + + + + + Resulting filter to be applied when the condition matches. + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Number of times to repeat each log message. + + + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Number of retries that should be attempted on the wrapped target in case of a failure. + + + + + Time to wait between retries in milliseconds. + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Forward to (Instead of ) + + + + + Always use independent of + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Should we include the BOM (Byte-order-mark) for UTF? Influences the property. This will only work for UTF-8. + + + + + Web service method name. Only used with Soap. + + + + + Web service namespace. Only used with Soap. + + + + + Protocol to be used when calling web service. + + + + + Custom proxy address, include port separated by a colon + + + + + Encoding. + + + + + Web service URL. + + + + + Value whether escaping be done according to the old NLog style (Very non-standard) + + + + + Value whether escaping be done according to Rfc3986 (Supports Internationalized Resource Identifiers - IRIs) + + + + + Indicates whether to pre-authenticate the HttpWebRequest (Requires 'Authorization' in parameters) + + + + + Name of the root XML element, if POST of XML document chosen. If so, this property must not be null. (see and ). + + + + + (optional) root namespace of the XML document, if POST of XML document chosen. (see and ). + + + + + Proxy configuration when calling web service + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Footer layout. + + + + + Header layout. + + + + + Body layout (can be repeated multiple times). + + + + + Custom column delimiter value (valid when ColumnDelimiter is set to 'Custom'). + + + + + Column delimiter. + + + + + Quote Character. + + + + + Quoting mode. + + + + + Indicates whether CVS should include header. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Layout of the column. + + + + + Name of the column. + + + + + Override of Quoting mode + + + + + + + + + + + + + + + + + + + + + Should forward slashes be escaped? If true, / will be converted to \/ + + + + + Option to render the empty object value {} + + + + + Option to suppress the extra spaces in the output json + + + + + List of property names to exclude when is true + + + + + Option to include all properties from the log event (as JSON) + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + How far should the JSON serializer follow object references before backing off + + + + + + + + + + + + + + + + + Layout that will be rendered as the attribute's value. + + + + + Name of the attribute. + + + + + Determines whether or not this attribute will be Json encoded. + + + + + Should forward slashes be escaped? If true, / will be converted to \/ + + + + + Indicates whether to escape non-ascii characters + + + + + Whether an attribute with empty value should be included in the output + + + + + + + + + + + + + + Footer layout. + + + + + Header layout. + + + + + Body layout (can be repeated multiple times). + + + + + + + + + + + + + + + + + + + + + Option to include all properties from the log events + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the stack. + + + + + Indicates whether to include contents of the stack. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + + + + + + + + + + Layout text. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + List of property names to exclude when is true + + + + + Option to include all properties from the log event (as XML) + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + How far should the XML serializer follow object references before backing off + + + + + XML element name to use for rendering IList-collections items + + + + + XML attribute name to use when rendering property-key When null (or empty) then key-attribute is not included + + + + + XML element name to use when rendering properties + + + + + XML attribute name to use when rendering property-value When null (or empty) then value-attribute is not included and value is formatted as XML-element-value + + + + + Name of the root XML element + + + + + Value inside the root XML element + + + + + Whether a ElementValue with empty value should be included in the output + + + + + Auto indent and create new lines + + + + + Determines whether or not this attribute will be Xml encoded. + + + + + + + + + + + + + + + Layout that will be rendered as the attribute's value. + + + + + Name of the attribute. + + + + + Determines whether or not this attribute will be Xml encoded. + + + + + Whether an attribute with empty value should be included in the output + + + + + + + + + + + + + + + + + + + + + + + + + Determines whether or not this attribute will be Xml encoded. + + + + + Name of the element + + + + + Value inside the element + + + + + Whether a ElementValue with empty value should be included in the output + + + + + Auto indent and create new lines + + + + + List of property names to exclude when is true + + + + + Option to include all properties from the log event (as XML) + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + How far should the XML serializer follow object references before backing off + + + + + XML element name to use for rendering IList-collections items + + + + + XML attribute name to use when rendering property-key When null (or empty) then key-attribute is not included + + + + + XML element name to use when rendering properties + + + + + XML attribute name to use when rendering property-value When null (or empty) then value-attribute is not included and value is formatted as XML-element-value + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Condition expression. + + + + + + + + + + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + Substring to be matched. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + String to compare the layout to. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + Substring to be matched. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + String to compare the layout to. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + + + + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Default number of unique filter values to expect, will automatically increase if needed + + + + + Applies the configured action to the initial logevent that starts the timeout period. Used to configure that it should ignore all events until timeout. + + + + + Layout to be used to filter log messages. + + + + + Max number of unique filter values to expect simultaneously + + + + + Max length of filter values, will truncate if above limit + + + + + How long before a filter expires, and logging is accepted again + + + + + Default buffer size for the internal buffers + + + + + Reuse internal buffers, and doesn't have to constantly allocate new buffers + + + + + Append FilterCount to the when an event is no longer filtered + + + + + Insert FilterCount value into when an event is no longer filtered + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Properties/AssemblyInfo.cs b/DI/Lab.MsDI/WebApiNet48/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..f5b17259 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WebApiNet48")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WebApiNet48")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("429d865c-ede7-4ea1-bc51-9a5bcab26bb8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DI/Lab.MsDI/WebApiNet48/ServiceProviderDependencyResolver.cs b/DI/Lab.MsDI/WebApiNet48/ServiceProviderDependencyResolver.cs new file mode 100644 index 00000000..ccb1df5c --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/ServiceProviderDependencyResolver.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.Http.Dependencies; +using Microsoft.Extensions.DependencyInjection; + +namespace WebApiNet48 +{ + internal class ServiceProviderDependencyResolver : IDependencyResolver + { + protected IServiceProvider ServiceProvider { get; set; } + + public ServiceProviderDependencyResolver(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public object GetService(Type serviceType) + { + if (HttpContext.Current?.Items[typeof(IServiceScope)] is IServiceScope scope) + { + return scope.ServiceProvider.GetService(serviceType); + } + + return this.ServiceProvider.GetService(serviceType); + throw new InvalidOperationException("IServiceScope not provided"); + } + + public IEnumerable GetServices(Type serviceType) + { + if (HttpContext.Current?.Items[typeof(IServiceScope)] is IServiceScope scope) + { + return scope.ServiceProvider.GetServices(serviceType); + } + + return this.ServiceProvider.GetServices(serviceType); + throw new InvalidOperationException("IServiceScope not provided"); + } + + public IDependencyScope BeginScope() + { + return new DefaultDependencyResolver(this.ServiceProvider.CreateScope().ServiceProvider); + } + + public void Dispose() + { + //throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/ServiceScopeModule.cs b/DI/Lab.MsDI/WebApiNet48/ServiceScopeModule.cs new file mode 100644 index 00000000..d326414f --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/ServiceScopeModule.cs @@ -0,0 +1,42 @@ +using System; +using System.Web; +using Microsoft.Extensions.DependencyInjection; + +namespace WebApiNet48 +{ + internal class ServiceScopeModule : IHttpModule + { + private static ServiceProvider _serviceProvider; + + public void Dispose() + { + + } + + public void Init(HttpApplication context) + { + context.BeginRequest += this.Context_BeginRequest; + context.EndRequest += this.Context_EndRequest; + } + + private void Context_EndRequest(object sender, EventArgs e) + { + var context = ((HttpApplication)sender).Context; + if (context.Items[typeof(IServiceScope)] is IServiceScope scope) + { + scope.Dispose(); + } + } + + private void Context_BeginRequest(object sender, EventArgs e) + { + var context = ((HttpApplication)sender).Context; + context.Items[typeof(IServiceScope)] = _serviceProvider.CreateScope(); + } + + public static void SetServiceProvider(ServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Web.Debug.config b/DI/Lab.MsDI/WebApiNet48/Web.Debug.config new file mode 100644 index 00000000..fae9cfef --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Web.Release.config b/DI/Lab.MsDI/WebApiNet48/Web.Release.config new file mode 100644 index 00000000..da6e960b --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/Web.config b/DI/Lab.MsDI/WebApiNet48/Web.config new file mode 100644 index 00000000..491d448d --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/Web.config @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj b/DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj new file mode 100644 index 00000000..32461adc --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj @@ -0,0 +1,186 @@ + + + + + Debug + AnyCPU + + + 2.0 + {429D865C-EDE7-4EA1-BC51-9A5BCAB26BB8} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + WebApiNet48 + WebApiNet48 + v4.8 + true + + 44304 + + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + true + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + + + ..\packages\Microsoft.Extensions.DependencyInjection.3.1.9\lib\net461\Microsoft.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\NLog.4.7.5\lib\net45\NLog.dll + + + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + + + + + + + + + + + + + + + + + + + + ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll + + + ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll + + + + + + + + + + + + + + Global.asax + + + + + + + + + + + + + + + + PreserveNewest + + + Designer + + + + Web.config + + + Web.config + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + True + True + 50548 + / + https://localhost:44304/ + False + False + + + False + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj.DotSettings b/DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj.DotSettings new file mode 100644 index 00000000..8a5228f0 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/WebApiNet48.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNet48/packages.config b/DI/Lab.MsDI/WebApiNet48/packages.config new file mode 100644 index 00000000..e8fcf0e2 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNet48/packages.config @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Controllers/Default1Controller.cs b/DI/Lab.MsDI/WebApiNetCore31/Controllers/Default1Controller.cs new file mode 100644 index 00000000..38dc48ee --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/Controllers/Default1Controller.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace WebApiNetCore31.Controllers +{ + [ApiController] + [Route("[controller]")] + public class Default1Controller : ControllerBase + { + private readonly ILogger _logger; + + public Default1Controller(ILogger logger) + { + this._logger = logger; + } + + [HttpGet] + public IActionResult Get() + { + var serviceProvider = this.HttpContext.RequestServices; + var transient = serviceProvider.GetService(typeof(ITransientMessager)) as ITransientMessager; + var scope = serviceProvider.GetService(typeof(IScopeMessager)) as IScopeMessager; + var single = serviceProvider.GetService(typeof(ISingleMessager)) as ISingleMessager; + var content = $"transient:{transient.OperationId}\r\n" + + $"scope:{scope.OperationId}\r\n" + + $"single:{single.OperationId}"; + + this._logger.LogInformation("transient = {transient},scope = {scope},single = {single}", + transient.OperationId, + scope.OperationId, + single.OperationId); + return this.Ok(content); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Controllers/DefaultController.cs b/DI/Lab.MsDI/WebApiNetCore31/Controllers/DefaultController.cs new file mode 100644 index 00000000..2fe6ac31 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/Controllers/DefaultController.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace WebApiNetCore31.Controllers +{ + [ApiController] + [Route("[controller]")] + public class DefaultController : ControllerBase + { + private IMessager Transient { get; } + + private IMessager Scope { get; } + + private IMessager Single { get; } + + private readonly ILogger _logger; + + public DefaultController(ILogger logger, + ITransientMessager transient, + IScopeMessager scope, + ISingleMessager single) + { + this._logger = logger; + + this.Transient = transient; + this.Scope = scope; + this.Single = single; + } + + [HttpGet] + public IActionResult Get() + { + var content = "我在 DefaultController.Get \r\n" + + $"transient:{this.Transient.OperationId}\r\n" + + $"scope:{this.Scope.OperationId}\r\n" + + $"single:{this.Single.OperationId}"; + this._logger.LogInformation("我在 DefaultController.Get ,transient = {transient},scope = {scope},single = {single}", + this.Transient.OperationId, + this.Scope.OperationId, + this.Single.OperationId); + return this.Ok(content); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/LogFilterAttribute.cs b/DI/Lab.MsDI/WebApiNetCore31/LogFilterAttribute.cs new file mode 100644 index 00000000..72b0c840 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/LogFilterAttribute.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace WebApiNetCore31 +{ + public class LogFilterAttribute : ActionFilterAttribute + { + public LogFilterAttribute() + { + + } + public override void OnActionExecuting(ActionExecutingContext context) + { + var transient = context.HttpContext.RequestServices.GetService(); + var scope = context.HttpContext.RequestServices.GetService(); + var single = context.HttpContext.RequestServices.GetService(); + var logger = context.HttpContext.RequestServices.GetService>(); + logger.LogInformation("我在 LogFilterAttribute ,transient = {transient},scope = {scope},single = {single}", + transient.OperationId, + scope.OperationId, + single.OperationId); + + //Console.WriteLine(content); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/IMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/IMessager.cs new file mode 100644 index 00000000..479144a9 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/Message/IMessager.cs @@ -0,0 +1,9 @@ +using System; + +namespace WebApiNetCore31 +{ + public interface IMessager:IDisposable + { + string OperationId { get; } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/IScopeMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/IScopeMessager.cs new file mode 100644 index 00000000..101c08fa --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/Message/IScopeMessager.cs @@ -0,0 +1,6 @@ +namespace WebApiNetCore31 +{ + public interface IScopeMessager : IMessager + { + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/ISingleMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/ISingleMessager.cs new file mode 100644 index 00000000..e803b8c9 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/Message/ISingleMessager.cs @@ -0,0 +1,6 @@ +namespace WebApiNetCore31 +{ + public interface ISingleMessager : IMessager + { + } +} diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/ITransientMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/ITransientMessager.cs new file mode 100644 index 00000000..b152a276 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/Message/ITransientMessager.cs @@ -0,0 +1,6 @@ +namespace WebApiNetCore31 +{ + public interface ITransientMessager : IMessager + { + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/LogMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/LogMessager.cs new file mode 100644 index 00000000..9819ac66 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/Message/LogMessager.cs @@ -0,0 +1,14 @@ +using System; + +namespace WebApiNetCore31 +{ + internal class LogMessager : IMessager + { + public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; + + public void Dispose() + { + Console.WriteLine($"{nameof(LogMessager)} GC"); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/MachineMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/MachineMessager.cs new file mode 100644 index 00000000..ed49b5dd --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/Message/MachineMessager.cs @@ -0,0 +1,13 @@ +using System; + +namespace WebApiNetCore31 +{ + internal class MachineMessager : IMessager + { + public string OperationId { get; } = $"機器-{Guid.NewGuid()}"; + public void Dispose() + { + Console.WriteLine($"{nameof(MachineMessager)} GC"); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Message/MultiMessager.cs b/DI/Lab.MsDI/WebApiNetCore31/Message/MultiMessager.cs new file mode 100644 index 00000000..68182ed0 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/Message/MultiMessager.cs @@ -0,0 +1,14 @@ +using System; + +namespace WebApiNetCore31 +{ + public class MultiMessager : IScopeMessager, ISingleMessager, ITransientMessager + { + public string OperationId { get; } = $"多個接口-{Guid.NewGuid()}"; + + public void Dispose() + { + Console.WriteLine($"{nameof(MultiMessager)} GC"); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Program.cs b/DI/Lab.MsDI/WebApiNetCore31/Program.cs new file mode 100644 index 00000000..aba702a0 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace WebApiNetCore31 +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/DI/Lab.MsDI/WebApiNetCore31/Properties/launchSettings.json b/DI/Lab.MsDI/WebApiNetCore31/Properties/launchSettings.json new file mode 100644 index 00000000..e27ff49d --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:55458", + "sslPort": 44311 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "default", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WebApiNetCore31": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "weatherforecast", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Startup.cs b/DI/Lab.MsDI/WebApiNetCore31/Startup.cs new file mode 100644 index 00000000..60390753 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/Startup.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace WebApiNetCore31 +{ + public class Startup + { + public IConfiguration Configuration { get; } + + public Startup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + //services.AddControllers(); + services.AddControllers(options => options.Filters.Add(new LogFilterAttribute())); + + services.AddTransient(p => new Worker(new LogMessager())); + services.AddTransient(p => new Worker2(new MachineMessager())); + + services.AddTransient() + .AddSingleton() + .AddScoped() + ; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/WeatherForecast.cs b/DI/Lab.MsDI/WebApiNetCore31/WeatherForecast.cs new file mode 100644 index 00000000..1bb9e27c --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/WeatherForecast.cs @@ -0,0 +1,15 @@ +using System; + +namespace WebApiNetCore31 +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string Summary { get; set; } + } +} diff --git a/DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj b/DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj new file mode 100644 index 00000000..d12c450b --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj @@ -0,0 +1,8 @@ + + + + netcoreapp3.1 + + + + diff --git a/DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings b/DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings new file mode 100644 index 00000000..8a5228f0 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/Worker.cs b/DI/Lab.MsDI/WebApiNetCore31/Worker.cs new file mode 100644 index 00000000..f069f7e4 --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/Worker.cs @@ -0,0 +1,22 @@ +namespace WebApiNetCore31 +{ + public class Worker + { + public IMessager Messager { get; set; } + + public Worker(IMessager messager) + { + this.Messager = messager; + } + } + + public class Worker2 + { + public IMessager Messager { get; set; } + + public Worker2(IMessager messager) + { + this.Messager = messager; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiNetCore31/appsettings.Development.json b/DI/Lab.MsDI/WebApiNetCore31/appsettings.Development.json new file mode 100644 index 00000000..8983e0fc --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/DI/Lab.MsDI/WebApiNetCore31/appsettings.json b/DI/Lab.MsDI/WebApiNetCore31/appsettings.json new file mode 100644 index 00000000..d9d9a9bf --- /dev/null +++ b/DI/Lab.MsDI/WebApiNetCore31/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/DI/Lab.MsDI/WebApiOwinNet48/App.config b/DI/Lab.MsDI/WebApiOwinNet48/App.config new file mode 100644 index 00000000..4a79e878 --- /dev/null +++ b/DI/Lab.MsDI/WebApiOwinNet48/App.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/AppSetting.cs b/DI/Lab.MsDI/WebApiOwinNet48/AppSetting.cs new file mode 100644 index 00000000..cf2c1518 --- /dev/null +++ b/DI/Lab.MsDI/WebApiOwinNet48/AppSetting.cs @@ -0,0 +1,8 @@ +namespace WebApiOwinNet48 +{ + public class ServerSetting + { + public const string HostEndpoint = "http://localhost:8001"; + + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/App_Start/DefaultDependencyResolver.cs b/DI/Lab.MsDI/WebApiOwinNet48/App_Start/DefaultDependencyResolver.cs new file mode 100644 index 00000000..d4d14983 --- /dev/null +++ b/DI/Lab.MsDI/WebApiOwinNet48/App_Start/DefaultDependencyResolver.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Web.Http.Dependencies; +using Microsoft.Extensions.DependencyInjection; + +namespace WebApiOwinNet48 +{ + public class DefaultDependencyResolver : IDependencyResolver + { + protected IServiceProvider ServiceProvider { get; set; } + + public DefaultDependencyResolver(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public object GetService(Type serviceType) + { + return this.ServiceProvider.GetService(serviceType); + } + + public IEnumerable GetServices(Type serviceType) + { + return this.ServiceProvider.GetServices(serviceType); + } + + public IDependencyScope BeginScope() + { + return new DefaultDependencyResolver(this.ServiceProvider.CreateScope().ServiceProvider); + } + + public void Dispose() + { + // you can implement this interface just when you use .net core 2.0 + // this.ServiceProvider.Dispose(); + ((ServiceProvider) this.ServiceProvider).Dispose(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/App_Start/DependencyInjectionConfig.cs b/DI/Lab.MsDI/WebApiOwinNet48/App_Start/DependencyInjectionConfig.cs new file mode 100644 index 00000000..0bb16012 --- /dev/null +++ b/DI/Lab.MsDI/WebApiOwinNet48/App_Start/DependencyInjectionConfig.cs @@ -0,0 +1,34 @@ +using System.Web.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace WebApiOwinNet48 +{ + public class DependencyInjectionConfig + { + public static void Register(HttpConfiguration config) + { + var services = ConfigureServices(); + + var provider = services.BuildServiceProvider(); + + var resolver = new DefaultDependencyResolver(provider); + config.DependencyResolver = resolver; + } + + /// + /// 使用 MS DI 註冊 + /// + /// + private static ServiceCollection ConfigureServices() + { + var services = new ServiceCollection(); + + //使用 Microsoft.Extensions.DependencyInjection 註冊 + services.AddControllersAsServices(typeof(DependencyInjectionConfig).Assembly.GetExportedTypes()); + + services.AddScoped(); + + return services; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/App_Start/ServiceProviderExtensions.cs b/DI/Lab.MsDI/WebApiOwinNet48/App_Start/ServiceProviderExtensions.cs new file mode 100644 index 00000000..ffb8667b --- /dev/null +++ b/DI/Lab.MsDI/WebApiOwinNet48/App_Start/ServiceProviderExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Http.Controllers; +using Microsoft.Extensions.DependencyInjection; + +namespace WebApiOwinNet48 +{ + public static class ServiceProviderExtensions + { + public static IServiceCollection AddControllersAsServices(this IServiceCollection services, + IEnumerable controllerTypes) + { + var filter = controllerTypes.Where(t => !t.IsAbstract + && !t.IsGenericTypeDefinition) + .Where(t => typeof(IHttpController).IsAssignableFrom(t) + || t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)); + + foreach (var type in filter) + { + services.AddTransient(type); + } + + return services; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/App_Start/WebApiConfig.cs b/DI/Lab.MsDI/WebApiOwinNet48/App_Start/WebApiConfig.cs new file mode 100644 index 00000000..3a04e4e2 --- /dev/null +++ b/DI/Lab.MsDI/WebApiOwinNet48/App_Start/WebApiConfig.cs @@ -0,0 +1,23 @@ +using System.Web.Http; + +namespace WebApiOwinNet48 +{ + public static class WebApiConfig + { + public static void Register(HttpConfiguration config) + { + // Web API configuration and services + // config.Filters.Add(new LogFilterAttribute()); + + // Web API routes + config.MapHttpAttributeRoutes(); + + config.Routes.MapHttpRoute( + name: "DefaultApi", + routeTemplate: "api/{controller}/{id}", + defaults: new { id = RouteParameter.Optional } + ); + + } + } +} diff --git a/DI/Lab.MsDI/WebApiOwinNet48/Commander.cs b/DI/Lab.MsDI/WebApiOwinNet48/Commander.cs new file mode 100644 index 00000000..7a1b5afa --- /dev/null +++ b/DI/Lab.MsDI/WebApiOwinNet48/Commander.cs @@ -0,0 +1,15 @@ +using System; + +namespace WebApiOwinNet48 +{ + public class Commander + { + public Guid Id { get; set; } = Guid.NewGuid(); + public string Get() + { + var msg = "GG"; + Console.WriteLine(msg); + return msg; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/Controllers/DefaultController.cs b/DI/Lab.MsDI/WebApiOwinNet48/Controllers/DefaultController.cs new file mode 100644 index 00000000..99e3277a --- /dev/null +++ b/DI/Lab.MsDI/WebApiOwinNet48/Controllers/DefaultController.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Web.Http; + +namespace WebApiOwinNet48.Controllers +{ + public class DefaultController : ApiController + { + private Commander _cmder; + + public DefaultController(Commander cmder) + { + this._cmder = cmder; + } + + // GET + public async Task Get() + { + // this._cmder.Get(); + Console.WriteLine($"我在 DefaultController.Get ,Command.Id = {this._cmder.Id}"); + return this.Ok(this._cmder); + } + + private class Member + { + public Guid Id { get; set; } + + public int Age { get; set; } + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/LabMiddleware.cs b/DI/Lab.MsDI/WebApiOwinNet48/LabMiddleware.cs new file mode 100644 index 00000000..3811e5f3 --- /dev/null +++ b/DI/Lab.MsDI/WebApiOwinNet48/LabMiddleware.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using Microsoft.Owin; +using AppFunc = System.Func, System.Threading.Tasks.Task>; + +namespace WebApiOwinNet48 +{ + public class LabMiddleware1 : OwinMiddleware + { + private readonly Commander _cmder; + + public LabMiddleware1(OwinMiddleware next, Commander cmder) : base(next) + { + this._cmder = this._cmder; + } + + // public LabMiddleware(OwinMiddleware next) : base(next) + // { + // } + + public override Task Invoke(IOwinContext context) + { + Console.WriteLine("我在 LabMiddleware.Invoke"); + + // Console.WriteLine($"我在 LabMiddleware.Invoke ,Command.Id = {this._cmder.Id}"); + return this.Next.Invoke(context); + } + } + + + public class LabMiddleware + { + private readonly AppFunc _next; + + public LabMiddleware(AppFunc next,Commander cmder) + { + if (next == null) + { + throw new ArgumentNullException("next"); + } + + this._next = next; + } + + public async Task Invoke(IDictionary environment) + { + try + { + await this._next(environment); + } + catch (Exception ex) + { + var owinContext = new OwinContext(environment); + + this.ErrorHandle(ex, owinContext); + } + } + + private void ErrorHandle(Exception ex, IOwinContext context) + { + context.Response.StatusCode = (int) HttpStatusCode.InternalServerError; + context.Response.ReasonPhrase = "Internal Server Error"; + context.Response.ContentType = "application/json"; + context.Response.Write(ex.Message); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/Program.cs b/DI/Lab.MsDI/WebApiOwinNet48/Program.cs new file mode 100644 index 00000000..c2b4659a --- /dev/null +++ b/DI/Lab.MsDI/WebApiOwinNet48/Program.cs @@ -0,0 +1,18 @@ +using System; +using Microsoft.Owin.Hosting; + +namespace WebApiOwinNet48 +{ + internal class Program + { + private static void Main(string[] args) + { + using (WebApp.Start(ServerSetting.HostEndpoint)) + { + Console.WriteLine($"伺服器已啟動, 位置:{ServerSetting.HostEndpoint}"); + Console.WriteLine("按下任意建離開應用程式"); + Console.ReadLine(); + } + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/Properties/AssemblyInfo.cs b/DI/Lab.MsDI/WebApiOwinNet48/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..8a1889ce --- /dev/null +++ b/DI/Lab.MsDI/WebApiOwinNet48/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WebApiOwinNet48")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WebApiOwinNet48")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("680ec3cf-cfe2-48f9-8006-f6a3d9b5dc42")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DI/Lab.MsDI/WebApiOwinNet48/Startup.cs b/DI/Lab.MsDI/WebApiOwinNet48/Startup.cs new file mode 100644 index 00000000..b76ef16c --- /dev/null +++ b/DI/Lab.MsDI/WebApiOwinNet48/Startup.cs @@ -0,0 +1,19 @@ +using System.Web.Http; +using Owin; + +namespace WebApiOwinNet48 +{ + public class Startup + { + public void Configuration(IAppBuilder app) + { + var httpConfig = new HttpConfiguration(); + DependencyInjectionConfig.Register(httpConfig); + WebApiConfig.Register(httpConfig); + //app.UseErrorPage(); + //app.UseWelcomePage("/Welcome"); + // app.Use(); + app.UseWebApi(httpConfig); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/WebApiOwinNet48.csproj b/DI/Lab.MsDI/WebApiOwinNet48/WebApiOwinNet48.csproj new file mode 100644 index 00000000..dbd189c3 --- /dev/null +++ b/DI/Lab.MsDI/WebApiOwinNet48/WebApiOwinNet48.csproj @@ -0,0 +1,111 @@ + + + + + Debug + AnyCPU + {680EC3CF-CFE2-48F9-8006-F6A3D9B5DC42} + Exe + WebApiOwinNet48 + WebApiOwinNet48 + v4.8 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + True + + + ..\packages\Microsoft.Extensions.DependencyInjection.3.1.9\lib\net461\Microsoft.Extensions.DependencyInjection.dll + True + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + True + + + ..\packages\Microsoft.Owin.4.1.1\lib\net45\Microsoft.Owin.dll + + + ..\packages\Microsoft.Owin.Diagnostics.4.1.1\lib\net45\Microsoft.Owin.Diagnostics.dll + + + ..\packages\Microsoft.Owin.Host.HttpListener.2.0.2\lib\net45\Microsoft.Owin.Host.HttpListener.dll + + + ..\packages\Microsoft.Owin.Hosting.2.0.2\lib\net45\Microsoft.Owin.Hosting.dll + + + + ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + True + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + True + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.Owin.5.2.7\lib\net45\System.Web.Http.Owin.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/WebApiOwinNet48/packages.config b/DI/Lab.MsDI/WebApiOwinNet48/packages.config new file mode 100644 index 00000000..4d964e03 --- /dev/null +++ b/DI/Lab.MsDI/WebApiOwinNet48/packages.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/App.config b/DI/Lab.MsDI/WinFormNet48/App.config new file mode 100644 index 00000000..1a57e048 --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/App.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/DependencyInjectionConfig.cs b/DI/Lab.MsDI/WinFormNet48/DependencyInjectionConfig.cs new file mode 100644 index 00000000..00209ab0 --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/DependencyInjectionConfig.cs @@ -0,0 +1,64 @@ +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace WinFormNet48 +{ + internal class DependencyInjectionConfig + { + public static IServiceProvider ServiceProvider { get; set; } + + public static IServiceProvider Register() + { + var services = new ServiceCollection(); + ConfigureServices(services); + ServiceProvider = services.BuildServiceProvider(); + return ServiceProvider; + } + + /// + /// 使用 MS DI 註冊 + /// + private static IServiceCollection ConfigureServices(IServiceCollection services) + { + return services.AddSingleton() + .AddTransient() + .AddTransient() + .AddSingleton() + .AddScoped() + + //.AddTransient(provider => + // { + // var operation = provider.GetRequiredService(); + // return new Worker(operation); + // }) + //.AddTransient(provider => + // { + // var operation = provider.GetRequiredService(); + // return new Workflow(operation); + // }) + + //.AddTransient() + //.AddTransient() + //.AddLogging(loggingBuilder => + // { + // // configure Logging with NLog + // loggingBuilder.ClearProviders(); + // loggingBuilder.SetMinimumLevel(LogLevel.Trace); + // loggingBuilder.AddNLog(config); + // }) + ; + + ; + } + + //private static IConfiguration CreateConfig() + //{ + // var config = new ConfigurationBuilder() + // .SetBasePath(System.IO.Directory + // .GetCurrentDirectory()) //From NuGet Package Microsoft.Extensions.Configuration.Json + // .AddJsonFile("appsettings.json", true, true) + // .Build(); + // return config; + //} + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Form1.Designer.cs b/DI/Lab.MsDI/WinFormNet48/Form1.Designer.cs new file mode 100644 index 00000000..abde6f86 --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Form1.Designer.cs @@ -0,0 +1,61 @@ +namespace WinFormNet48 +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.button1 = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // button1 + // + this.button1.Location = new System.Drawing.Point(47, 64); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(75, 23); + this.button1.TabIndex = 0; + this.button1.Text = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Controls.Add(this.button1); + this.Name = "Form1"; + this.Text = "Form1"; + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button button1; + } +} + diff --git a/DI/Lab.MsDI/WinFormNet48/Form1.cs b/DI/Lab.MsDI/WinFormNet48/Form1.cs new file mode 100644 index 00000000..3c3e1406 --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Form1.cs @@ -0,0 +1,21 @@ +using System; +using System.Windows.Forms; +using Microsoft.Extensions.DependencyInjection; + +namespace WinFormNet48 +{ + public partial class Form1 : Form + { + public Form1() + { + this.InitializeComponent(); + } + + private void button1_Click(object sender, EventArgs e) + { + var serviceProvider = DependencyInjectionConfig.ServiceProvider; + var work = serviceProvider.GetRequiredService(); + Console.WriteLine(work.Get()); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Form1.resx b/DI/Lab.MsDI/WinFormNet48/Form1.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Message/IMessager.cs b/DI/Lab.MsDI/WinFormNet48/Message/IMessager.cs new file mode 100644 index 00000000..741af8b8 --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Message/IMessager.cs @@ -0,0 +1,7 @@ +namespace WinFormNet48 +{ + public interface IMessager + { + string OperationId { get; } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Message/IScopeMessager.cs b/DI/Lab.MsDI/WinFormNet48/Message/IScopeMessager.cs new file mode 100644 index 00000000..0241069d --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Message/IScopeMessager.cs @@ -0,0 +1,6 @@ +namespace WinFormNet48 +{ + public interface IScopeMessager : IMessager + { + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Message/ISingleMessager.cs b/DI/Lab.MsDI/WinFormNet48/Message/ISingleMessager.cs new file mode 100644 index 00000000..76e8fce5 --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Message/ISingleMessager.cs @@ -0,0 +1,6 @@ +namespace WinFormNet48 +{ + public interface ISingleMessager : IMessager + { + } +} diff --git a/DI/Lab.MsDI/WinFormNet48/Message/ITransientMessager.cs b/DI/Lab.MsDI/WinFormNet48/Message/ITransientMessager.cs new file mode 100644 index 00000000..0c0d795b --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Message/ITransientMessager.cs @@ -0,0 +1,6 @@ +namespace WinFormNet48 +{ + public interface ITransientMessager : IMessager + { + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Message/LogMessager.cs b/DI/Lab.MsDI/WinFormNet48/Message/LogMessager.cs new file mode 100644 index 00000000..7e18917a --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Message/LogMessager.cs @@ -0,0 +1,9 @@ +using System; + +namespace WinFormNet48 +{ + internal class LogMessager : IMessager + { + public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Message/MachineMessager.cs b/DI/Lab.MsDI/WinFormNet48/Message/MachineMessager.cs new file mode 100644 index 00000000..2d0c9de3 --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Message/MachineMessager.cs @@ -0,0 +1,9 @@ +using System; + +namespace WinFormNet48 +{ + internal class MachineMessager : IMessager + { + public string OperationId { get; } = $"機器-{Guid.NewGuid()}"; + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Message/MultiMessager.cs b/DI/Lab.MsDI/WinFormNet48/Message/MultiMessager.cs new file mode 100644 index 00000000..878b27bf --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Message/MultiMessager.cs @@ -0,0 +1,9 @@ +using System; + +namespace WinFormNet48 +{ + public class MultiMessager : IScopeMessager, ISingleMessager, ITransientMessager + { + public string OperationId { get; } = $"多個接口-{Guid.NewGuid()}"; + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Program.cs b/DI/Lab.MsDI/WinFormNet48/Program.cs new file mode 100644 index 00000000..9f3dda6e --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Windows.Forms; +using Microsoft.Extensions.DependencyInjection; + +namespace WinFormNet48 +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + private static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + var serviceProvider = DependencyInjectionConfig.Register() as ServiceProvider; + + using (serviceProvider) + { + var form = serviceProvider.GetService(typeof(Form1)) as Form; + Application.Run(form); + } + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Properties/AssemblyInfo.cs b/DI/Lab.MsDI/WinFormNet48/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..b7aebc8e --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WinFormNet48")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WinFormNet48")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("fc5aa11a-0879-43d8-8e79-dfc37de75fb8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DI/Lab.MsDI/WinFormNet48/Properties/Resources.Designer.cs b/DI/Lab.MsDI/WinFormNet48/Properties/Resources.Designer.cs new file mode 100644 index 00000000..ca98cf3f --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WinFormNet48.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WinFormNet48.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/DI/Lab.MsDI/WinFormNet48/Properties/Resources.resx b/DI/Lab.MsDI/WinFormNet48/Properties/Resources.resx new file mode 100644 index 00000000..af7dbebb --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Properties/Settings.Designer.cs b/DI/Lab.MsDI/WinFormNet48/Properties/Settings.Designer.cs new file mode 100644 index 00000000..8411c43f --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WinFormNet48.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/DI/Lab.MsDI/WinFormNet48/Properties/Settings.settings b/DI/Lab.MsDI/WinFormNet48/Properties/Settings.settings new file mode 100644 index 00000000..39645652 --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj b/DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj new file mode 100644 index 00000000..34b52830 --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj @@ -0,0 +1,113 @@ + + + + + Debug + AnyCPU + {FC5AA11A-0879-43D8-8E79-DFC37DE75FB8} + Exe + WinFormNet48 + WinFormNet48 + v4.8 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.3.1.9\lib\net461\Microsoft.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + + + + + + + + + + + + + + + + + Form + + + Form1.cs + + + + + + + + + + Form1.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj.DotSettings b/DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj.DotSettings new file mode 100644 index 00000000..8a5228f0 --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/WinFormNet48.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Worker.cs b/DI/Lab.MsDI/WinFormNet48/Worker.cs new file mode 100644 index 00000000..d6bcf3f2 --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Worker.cs @@ -0,0 +1,25 @@ +namespace WinFormNet48 +{ + public class Worker + { + private IMessager Transient { get; } + + private IMessager Scope { get; } + + private IMessager Single { get; } + + public Worker(ITransientMessager transient, IScopeMessager scope, ISingleMessager single) + { + this.Transient = transient; + this.Scope = scope; + this.Single = single; + } + + public string Get() + { + return $"transient:{this.Transient.OperationId}\r\n" + + $"scope:{this.Scope.OperationId}\r\n" + + $"single:{this.Single.OperationId}"; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/WinFormNet48/Workflow.cs b/DI/Lab.MsDI/WinFormNet48/Workflow.cs new file mode 100644 index 00000000..8298237c --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/Workflow.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WinFormNet48 +{ + public class Workflow + { + public Workflow(IMessager operation) + { + Console.WriteLine(operation.OperationId); + } + } +} diff --git a/DI/Lab.MsDI/WinFormNet48/packages.config b/DI/Lab.MsDI/WinFormNet48/packages.config new file mode 100644 index 00000000..a2075034 --- /dev/null +++ b/DI/Lab.MsDI/WinFormNet48/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Client/Client.csproj b/DI/Lab.MultipleImpl/Client/Client.csproj new file mode 100644 index 00000000..e7e23018 --- /dev/null +++ b/DI/Lab.MultipleImpl/Client/Client.csproj @@ -0,0 +1,23 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + + + + diff --git a/DI/Lab.MultipleImpl/Client/UnitTest1.cs b/DI/Lab.MultipleImpl/Client/UnitTest1.cs new file mode 100644 index 00000000..0dcc77b8 --- /dev/null +++ b/DI/Lab.MultipleImpl/Client/UnitTest1.cs @@ -0,0 +1,128 @@ +using System; +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Autofac.Features.AttributeFilters; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Server; +using Server.Controllers; +using Unity; +using Unity.Microsoft.DependencyInjection; + +namespace Client +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void Autofac注入ServiceName() + { + var hostBuilder = WebHost.CreateDefaultBuilder() + // .UseServiceProviderFactory(new AutofacServiceProviderFactory()) + .ConfigureServices(services => { services.AddAutofac(); }) + .UseStartup() + ; + using var server = new TestServer(hostBuilder) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "autofac"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } + + [TestMethod] + public void Unity注入ServiceName() + { + var unityContainer = new UnityContainer(); + ConfigureContainer(unityContainer); + + using var server = + new TestServer(WebHost.CreateDefaultBuilder() + .UseStartup() + .UseUnityServiceProvider(unityContainer) + .ConfigureServices(UseUnityController) + ) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "unity"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } + + [TestMethod] + public void 注入FuncName() + { + using var server = + new TestServer(WebHost.CreateDefaultBuilder() + .UseStartup() + .ConfigureServices(UseFuncName) + ) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "default/zip"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } + + private static void ConfigureContainer(ContainerBuilder builder) + { + // builder.RegisterType().Keyed("file"); + // builder.RegisterType().Keyed("zip"); + // builder.RegisterType().WithAttributeFiltering(); + } + + private static void ConfigureContainer(IUnityContainer container) + { + container.RegisterType("zip"); + container.RegisterType("file"); + } + + private static void UseFuncName(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton>(provider => + key => + { + switch (key) + { + case "zip": + return provider + .GetService(); + case "file": + return provider + .GetService(); + default: + throw new NotSupportedException(); + } + }); + } + + private static void UseUnityController(IServiceCollection services) + { + services.AddControllers() + .AddControllersAsServices(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Lab.MultipleImpl.sln b/DI/Lab.MultipleImpl/Lab.MultipleImpl.sln new file mode 100644 index 00000000..8a158dd3 --- /dev/null +++ b/DI/Lab.MultipleImpl/Lab.MultipleImpl.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{DAE7F74D-E847-4B2F-8930-59AF2698FD1D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Client\Client.csproj", "{6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NET5.TestProject", "NET5.TestProject\NET5.TestProject.csproj", "{A433C8F8-3B75-412E-955F-287639C55C5F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DAE7F74D-E847-4B2F-8930-59AF2698FD1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DAE7F74D-E847-4B2F-8930-59AF2698FD1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DAE7F74D-E847-4B2F-8930-59AF2698FD1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DAE7F74D-E847-4B2F-8930-59AF2698FD1D}.Release|Any CPU.Build.0 = Release|Any CPU + {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6ADCD6A4-6733-46CF-8935-C2D77FC7FC79}.Release|Any CPU.Build.0 = Release|Any CPU + {A433C8F8-3B75-412E-955F-287639C55C5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A433C8F8-3B75-412E-955F-287639C55C5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A433C8F8-3B75-412E-955F-287639C55C5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A433C8F8-3B75-412E-955F-287639C55C5F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs b/DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs new file mode 100644 index 00000000..a1721c5a --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/AutofacStartup.cs @@ -0,0 +1,53 @@ +using Autofac; +using Autofac.Features.AttributeFilters; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using NET5.TestProject.Controllers; +using NET5.TestProject.File; + +namespace NET5.TestProject +{ + public class AutofacStartup + { + public AutofacStartup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + public void ConfigureContainer(ContainerBuilder builder) + { + builder.RegisterType().Keyed("file"); + builder.RegisterType().Keyed("zip"); + builder.RegisterType().WithAttributeFiltering();//<-- add line + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers() + .AddControllersAsServices(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs new file mode 100644 index 00000000..50bdf475 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/AutofacController.cs @@ -0,0 +1,38 @@ +using System; +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Autofac.Features.AttributeFilters; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using NET5.TestProject.File; + +namespace NET5.TestProject.Controllers +{ + [ApiController] + [Route("[controller]")] + public class AutofacController : ControllerBase + { + private readonly IFileProvider _fileProvider; + + private readonly ILogger _logger; + + public AutofacController(ILogger logger, + [KeyFilter("zip")] IFileProvider fileProvider) + { + this._logger = logger; + this._fileProvider = fileProvider; + var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; + Console.WriteLine(msg); + } + + [HttpGet] + [Route("{key}")] + public IActionResult Get(string key) + { + var serviceProvider = this.HttpContext.RequestServices; + var autofacServiceProvider = (AutofacServiceProvider) serviceProvider; + var fileProvider = autofacServiceProvider.LifetimeScope.ResolveKeyed(key); + return this.Ok(fileProvider.Print()); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs new file mode 100644 index 00000000..fad0093e --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/DefaultController.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using NET5.TestProject.File; + +namespace NET5.TestProject.Controllers +{ + [ApiController] + [Route("[controller]")] + public class DefaultController : ControllerBase + { + private readonly ILogger _logger; + private readonly IFileProvider _fileProvider; + + public DefaultController(ILogger logger, + IFileProvider fileProvider) + { + this._logger = logger; + this._fileProvider = fileProvider; + } + [HttpGet] + public IActionResult Get() + { + // var fileProvider = this.HttpContext.RequestServices.GetService(); + var fileProvider = this._fileProvider; + var result = fileProvider.Print(); + return this.Ok(result); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/FuncController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/FuncController.cs new file mode 100644 index 00000000..43728f63 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/FuncController.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using NET5.TestProject.File; + +namespace NET5.TestProject.Controllers +{ + [ApiController] + [Route("[controller]")] + public class FuncController : ControllerBase + { + private readonly IFileProvider _fileProvider; + private readonly ILogger _logger; + + public FuncController(ILogger logger, + Func pool) + { + this._fileProvider = pool("zip"); + this._logger = logger; + var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; + Console.WriteLine(msg); + } + + [HttpGet] + [Route("{type}")] + public IActionResult Get(string type) + { + var fileProvider = this.HttpContext.RequestServices.GetService(type); + var result = fileProvider.Print(); + return this.Ok(result); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/MultiController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/MultiController.cs new file mode 100644 index 00000000..48942f3f --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/MultiController.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using NET5.TestProject.File; + +namespace NET5.TestProject.Controllers +{ + [ApiController] + [Route("[controller]")] + public class MultiController : ControllerBase + { + private readonly IFileProvider _fileProvider; + + private readonly ILogger _logger; + + // public MultiController(ILogger logger, + // IEnumerable pool) + // { + // this._logger = logger; + // this._fileProvider = pool.FirstOrDefault(p => p.GetType().Name == "ZipFileProvider"); + // var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; + // Console.WriteLine(msg); + // } + // + // [HttpGet] + // public IActionResult Get() + // { + // var serviceProvider = this.HttpContext.RequestServices; + // var pool = serviceProvider.GetServices(); + // var fileProvider = pool.FirstOrDefault(p => p.GetType().Name == "ZipFileProvider"); + // return this.Ok(fileProvider.Print()); + // } + + public MultiController(ILogger logger, + Dictionary pool) + { + this._logger = logger; + this._fileProvider = pool["zip"]; + var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; + Console.WriteLine(msg); + } + + [HttpGet] + [Route("{key}")] + public IActionResult Get(string key) + { + var serviceProvider = this.HttpContext.RequestServices; + var pool = serviceProvider.GetService>(); + var fileProvider = pool[key]; + return this.Ok(fileProvider.Print()); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs new file mode 100644 index 00000000..c94576a0 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Controllers/UnityController.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using NET5.TestProject.File; +using Unity; +using Unity.Microsoft.DependencyInjection; + +namespace NET5.TestProject.Controllers +{ + [ApiController] + [Route("[controller]")] + public class UnityController : ControllerBase + { + private readonly IFileProvider _fileProvider; + + private readonly ILogger _logger; + + public UnityController(ILogger logger, + [Dependency("zip")] IFileProvider fileProvider) + { + this._logger = logger; + this._fileProvider = fileProvider; + var msg = $"{this._fileProvider.Print()} in {this.GetType().Name} constructor"; + Console.WriteLine(msg); + } + + [HttpGet] + [Route("{key}")] + public IActionResult Get(string key) + { + var serviceProvider = this.HttpContext.RequestServices; + var unityServiceProvider = (ServiceProvider) serviceProvider; + var unityContainer = (UnityContainer) unityServiceProvider; + var fileProvider = unityContainer.Resolve(key); + var result = fileProvider.Print(); + return this.Ok(result); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/File/FileProvider.cs b/DI/Lab.MultipleImpl/NET5.TestProject/File/FileProvider.cs new file mode 100644 index 00000000..f9ea7bea --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/File/FileProvider.cs @@ -0,0 +1,14 @@ +using System; + +namespace NET5.TestProject.File +{ + public class FileProvider : IFileProvider + { + public string Print() + { + var msg = "FileProvider"; + Console.WriteLine(msg); + return msg; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/File/IFileProvider.cs b/DI/Lab.MultipleImpl/NET5.TestProject/File/IFileProvider.cs new file mode 100644 index 00000000..e72e465a --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/File/IFileProvider.cs @@ -0,0 +1,7 @@ +namespace NET5.TestProject.File +{ + public interface IFileProvider + { + string Print(); + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/File/ZipFileProvider.cs b/DI/Lab.MultipleImpl/NET5.TestProject/File/ZipFileProvider.cs new file mode 100644 index 00000000..058d383d --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/File/ZipFileProvider.cs @@ -0,0 +1,14 @@ +using System; + +namespace NET5.TestProject.File +{ + public class ZipFileProvider : IFileProvider + { + public string Print() + { + var msg = "ZipFileProvider"; + Console.WriteLine(msg); + return msg; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/FileAdapter.cs b/DI/Lab.MultipleImpl/NET5.TestProject/FileAdapter.cs new file mode 100644 index 00000000..41034a00 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/FileAdapter.cs @@ -0,0 +1,19 @@ +using NET5.TestProject.File; + +namespace NET5.TestProject +{ + public class FileAdapter + { + private readonly IFileProvider _fileProvider; + + public FileAdapter(IFileProvider fileProvider) + { + this._fileProvider = fileProvider; + } + + public string Get() + { + return this._fileProvider.Print(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/FuncStartup.cs b/DI/Lab.MultipleImpl/NET5.TestProject/FuncStartup.cs new file mode 100644 index 00000000..5be65458 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/FuncStartup.cs @@ -0,0 +1,63 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using NET5.TestProject.File; + +namespace NET5.TestProject +{ + public class FuncStartup + { + public FuncStartup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + UseFuncName(services); + } + private static void UseFuncName(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton>(provider => + key => + { + switch (key) + { + case "zip": + return provider + .GetService(); + case "file": + return provider + .GetService(); + default: + throw new NotSupportedException(); + } + }); + } + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj b/DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj new file mode 100644 index 00000000..1c284244 --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/NET5.TestProject.csproj @@ -0,0 +1,28 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + + + true + PreserveNewest + PreserveNewest + + + + diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs b/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs new file mode 100644 index 00000000..2c381a2f --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/ServiceProviderExtension.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using NET5.TestProject.File; + +namespace NET5.TestProject +{ + public static class ServiceProviderExtension + { + public static T GetService(this IServiceProvider provider, string name) + { + var pool = (Func) provider.GetService(typeof(Func)); + return (T) pool(name); + } + + public static List GetTypesAssignableFrom(this Assembly assembly) + { + return assembly.GetTypesAssignableFrom(typeof(T)); + } + + public static List GetTypesAssignableFrom(this Assembly assembly, Type compareType) + { + var results = new List(); + foreach (var type in assembly.DefinedTypes) + { + if (compareType.IsAssignableFrom(type) + && compareType != type + ) + { + results.Add(type); + } + } + + return results; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/Startup.cs b/DI/Lab.MultipleImpl/NET5.TestProject/Startup.cs new file mode 100644 index 00000000..2bad24fc --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/Startup.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace NET5.TestProject +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs new file mode 100644 index 00000000..6641e17a --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/UnitTest1.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Autofac.Extensions.DependencyInjection; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NET5.TestProject.Controllers; +using NET5.TestProject.File; +using Unity; +using Unity.Microsoft.DependencyInjection; + +namespace NET5.TestProject +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void Autofac注入ServiceName() + { + var hostBuilder = WebHost.CreateDefaultBuilder() + + // .UseServiceProviderFactory(new AutofacServiceProviderFactory()) + .UseStartup() //<-- add line + .ConfigureServices(services => + { + services.AddAutofac(); + services.AddControllers() + .AddControllersAsServices(); //<-- add line + }) + ; + using var server = new TestServer(hostBuilder) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "autofac/zip"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } + + [TestMethod] + public void Unity注入ServiceName() + { + var unityContainer = new UnityContainer(); + unityContainer.RegisterType("zip"); + unityContainer.RegisterType("file"); //<-- add line + + var builder = WebHost.CreateDefaultBuilder() + .UseStartup() + .UseUnityServiceProvider(unityContainer) //<-- add line + .ConfigureServices(s => + { + s.AddControllers() + .AddControllersAsServices(); //<-- add line + }) + ; + using var server = new TestServer(builder) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "unity/zip"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } + + [TestMethod] + public void 手動註冊() + { + var hostBuilder = + WebHost.CreateDefaultBuilder() + .UseStartup() + .ConfigureServices(s => + { + s.AddSingleton(); + s.AddSingleton(); + s.AddSingleton(p => + { + var fileProvider = p.GetService(); + var logger = + p.GetService>(); + return new DefaultController(logger, fileProvider); + }); + s.AddControllers().AddControllersAsServices(); + }) + ; + using var server = new TestServer(hostBuilder) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "default"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } + + [TestMethod] + public void 注入FuncName() + { + var builder = WebHost.CreateDefaultBuilder() + .UseStartup() + .ConfigureServices(s => + { + s.AddSingleton(); + s.AddSingleton(); + s.AddSingleton>(p => + key => + { + switch (key) + { + case "zip": + return p + .GetService(); + case "file": + return p + .GetService(); + default: + throw new NotSupportedException(); + } + }); + }) + ; + using var server = new TestServer(builder) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "func/zip"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } + + [TestMethod] + public void 注入相同的介面() + { + var hostBuilder = WebHost.CreateDefaultBuilder() + .UseStartup() //<-- add line + .ConfigureServices(service => + { + ScanToDictionary(service); + + // AddToDictionary(service); + }) + ; + using var server = new TestServer(hostBuilder) + { + BaseAddress = new Uri("http://localhost:9527") + }; + + var client = server.CreateClient(); + var url = "multi/zip"; + var response = client.GetAsync(url).Result; + response.EnsureSuccessStatusCode(); + + var result = response.Content.ReadAsStringAsync().Result; + Assert.AreEqual("ZipFileProvider", result); + } + + private static void AddToDictionary(IServiceCollection s) + { + s.AddSingleton(); + s.AddSingleton(); + s.AddSingleton(p => + { + var pool = + new Dictionary + { + {"zip", p.GetService()}, + {"file", p.GetService()} + }; + + return pool; + }); + } + + private static void ScanToDictionary(IServiceCollection services) + { + var assembly = Assembly.GetExecutingAssembly(); + assembly.GetTypesAssignableFrom() + .ForEach(t => { services.AddSingleton(t); }); + services.AddSingleton(p => + { + var pool = + new Dictionary + { + {"zip", p.GetService()}, + {"file", p.GetService()} + }; + + return pool; + }); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/NET5.TestProject/appsettings.json b/DI/Lab.MultipleImpl/NET5.TestProject/appsettings.json new file mode 100644 index 00000000..d9d9a9bf --- /dev/null +++ b/DI/Lab.MultipleImpl/NET5.TestProject/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/DI/Lab.MultipleImpl/Server/AutofacStartup.cs b/DI/Lab.MultipleImpl/Server/AutofacStartup.cs new file mode 100644 index 00000000..1e93de1e --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/AutofacStartup.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Autofac; +using Autofac.Features.AttributeFilters; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; +using Server.Controllers; + +namespace Server +{ + public class AutofacStartup + { + public AutofacStartup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + public void ConfigureContainer(ContainerBuilder builder) + { + builder.RegisterType().Keyed("file"); + builder.RegisterType().Keyed("zip"); + builder.RegisterType().WithAttributeFiltering(); + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers() + .AddControllersAsServices(); + services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Server", Version = "v1"}); }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server v1")); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Controllers/AutofacController.cs b/DI/Lab.MultipleImpl/Server/Controllers/AutofacController.cs new file mode 100644 index 00000000..45debe20 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Controllers/AutofacController.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations; +using Autofac.Features.AttributeFilters; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Server.Controllers +{ + [ApiController] + [Route("[controller]")] + public class AutofacController : ControllerBase + { + private readonly IFileProvider _fileProvider; + + private readonly ILogger _logger; + + // public AutofacDefaultController(ILogger logger) + // { + // this._logger = logger; + // } + + public AutofacController(ILogger logger, + [KeyFilter("zip")] IFileProvider fileProvider) + { + this._logger = logger; + this._fileProvider = fileProvider; + this._fileProvider.Print(); + } + + [HttpGet] + public IActionResult Get() + { + return this.Ok(this._fileProvider.Print()); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Controllers/DefaultController.cs b/DI/Lab.MultipleImpl/Server/Controllers/DefaultController.cs new file mode 100644 index 00000000..79efc330 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Controllers/DefaultController.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Server.Controllers +{ + [ApiController] + [Route("[controller]")] + public class DefaultController : ControllerBase + { + + private readonly ILogger _logger; + + public DefaultController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + [Route("{type}")] + public IActionResult Get(string type) + { + var fileProvider = this.HttpContext.RequestServices.GetService(type); + var result = fileProvider.Print(); + return this.Ok(result); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Controllers/UnityController.cs b/DI/Lab.MultipleImpl/Server/Controllers/UnityController.cs new file mode 100644 index 00000000..44910a17 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Controllers/UnityController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Unity; + +namespace Server.Controllers +{ + [ApiController] + [Route("[controller]")] + public class UnityController : ControllerBase + { + private readonly IFileProvider _fileProvider; + + private readonly ILogger _logger; + + public UnityController(ILogger logger, + [Dependency("zip")] IFileProvider fileProvider) + { + this._logger = logger; + this._fileProvider = fileProvider; + } + + [HttpGet] + public IActionResult Get() + { + var serviceProvider = this.HttpContext.RequestServices; + var unityServiceProvider = (Unity.Microsoft.DependencyInjection.ServiceProvider) serviceProvider; + var unityContainer = (UnityContainer) unityServiceProvider; + var fileProvider = unityContainer.Resolve("zip"); + var result = fileProvider.Print(); + return this.Ok(result); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Controllers/WeatherForecastController.cs b/DI/Lab.MultipleImpl/Server/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..69c4d675 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Controllers/WeatherForecastController.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Server.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public IEnumerable Get() + { + var rng = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = rng.Next(-20, 55), + Summary = Summaries[rng.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/DependencyConfig.cs b/DI/Lab.MultipleImpl/Server/DependencyConfig.cs new file mode 100644 index 00000000..b6df11a4 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/DependencyConfig.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.OpenApi.Models; + +namespace Server +{ + public class DependencyConfig + { + public static void ConfigureServices(IServiceCollection services) + { + services.AddControllers() + .AddControllersAsServices() + ; + } + public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server v1")); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/File/FileProvider.cs b/DI/Lab.MultipleImpl/Server/File/FileProvider.cs new file mode 100644 index 00000000..06045138 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/File/FileProvider.cs @@ -0,0 +1,14 @@ +using System; + +namespace Server +{ + public class FileProvider : IFileProvider + { + public string Print() + { + var msg = "FileProvider"; + Console.WriteLine(msg); + return msg; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/File/IFileProvider.cs b/DI/Lab.MultipleImpl/Server/File/IFileProvider.cs new file mode 100644 index 00000000..7c69a1ae --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/File/IFileProvider.cs @@ -0,0 +1,7 @@ +namespace Server +{ + public interface IFileProvider + { + string Print(); + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/File/ZipFileProvider.cs b/DI/Lab.MultipleImpl/Server/File/ZipFileProvider.cs new file mode 100644 index 00000000..804a286a --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/File/ZipFileProvider.cs @@ -0,0 +1,14 @@ +using System; + +namespace Server +{ + public class ZipFileProvider : IFileProvider + { + public string Print() + { + var msg = "ZipFileProvider"; + Console.WriteLine(msg); + return msg; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Program.cs b/DI/Lab.MultipleImpl/Server/Program.cs new file mode 100644 index 00000000..c083a4aa --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Autofac.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Server +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseServiceProviderFactory(new AutofacServiceProviderFactory()) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Properties/launchSettings.json b/DI/Lab.MultipleImpl/Server/Properties/launchSettings.json new file mode 100644 index 00000000..f822a28b --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:59369", + "sslPort": 44389 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Server": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/DI/Lab.MultipleImpl/Server/Server.csproj b/DI/Lab.MultipleImpl/Server/Server.csproj new file mode 100644 index 00000000..153ed02c --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Server.csproj @@ -0,0 +1,21 @@ + + + + net5.0 + + + + + + + + + + + + + + + + + diff --git a/DI/Lab.MultipleImpl/Server/Server.csproj.DotSettings b/DI/Lab.MultipleImpl/Server/Server.csproj.DotSettings new file mode 100644 index 00000000..a9923e32 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Server.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/ServiceProviderExtension.cs b/DI/Lab.MultipleImpl/Server/ServiceProviderExtension.cs new file mode 100644 index 00000000..601138e2 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/ServiceProviderExtension.cs @@ -0,0 +1,14 @@ +using System; + +namespace Server +{ + public static class ServiceProviderExtension + { + public static T GetService(this IServiceProvider provider, string name) + { + var pool = (Func) provider.GetService(typeof(Func)); + return (T) pool(name); + } + } + +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/Startup.cs b/DI/Lab.MultipleImpl/Server/Startup.cs new file mode 100644 index 00000000..e7ecb450 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/Startup.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; + +namespace Server +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Server", Version = "v1"}); }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server v1")); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/WeatherForecast.cs b/DI/Lab.MultipleImpl/Server/WeatherForecast.cs new file mode 100644 index 00000000..36e011e2 --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/WeatherForecast.cs @@ -0,0 +1,15 @@ +using System; + +namespace Server +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int) (TemperatureC / 0.5556); + + public string Summary { get; set; } + } +} \ No newline at end of file diff --git a/DI/Lab.MultipleImpl/Server/appsettings.Development.json b/DI/Lab.MultipleImpl/Server/appsettings.Development.json new file mode 100644 index 00000000..8983e0fc --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/DI/Lab.MultipleImpl/Server/appsettings.json b/DI/Lab.MultipleImpl/Server/appsettings.json new file mode 100644 index 00000000..d9d9a9bf --- /dev/null +++ b/DI/Lab.MultipleImpl/Server/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/DI/Lib.MsDiForScrutor/Lib.MsDiForScrutor.sln b/DI/Lib.MsDiForScrutor/Lib.MsDiForScrutor.sln new file mode 100644 index 00000000..d7ce1c92 --- /dev/null +++ b/DI/Lib.MsDiForScrutor/Lib.MsDiForScrutor.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30503.244 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiNetCore31", "WebApiNetCore31\WebApiNetCore31.csproj", "{7CBCB1D6-6FF5-44A0-A873-C0DCFD6630B8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7CBCB1D6-6FF5-44A0-A873-C0DCFD6630B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CBCB1D6-6FF5-44A0-A873-C0DCFD6630B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CBCB1D6-6FF5-44A0-A873-C0DCFD6630B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CBCB1D6-6FF5-44A0-A873-C0DCFD6630B8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D1F4D562-D1B5-4DFA-A971-5E7A1FA0E73F} + EndGlobalSection +EndGlobal diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/Controllers/Default1Controller.cs b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Controllers/Default1Controller.cs new file mode 100644 index 00000000..f639c8fe --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Controllers/Default1Controller.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace WebApiNetCore31.Controllers +{ + [ApiController] + [Route("[controller]")] + public class Default1Controller : ControllerBase + { + private IMessager Messager { get; } + + private readonly ILogger _logger; + //public Default1Controller(IMessager messager) + //{ + // this.Messager = messager; + //} + + public Default1Controller(ILogger logger, + IMessager messager + ) + { + this._logger = logger; + this.Messager = messager; + } + + + + [HttpGet] + public IActionResult Get() + { + var content = $"Messager:{this.Messager.OperationId}"; + this._logger.LogInformation("Messager:{message}", content); + return this.Ok(content); + } + } +} \ No newline at end of file diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/Controllers/DefaultController.cs b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Controllers/DefaultController.cs new file mode 100644 index 00000000..c6f658ba --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Controllers/DefaultController.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace WebApiNetCore31.Controllers +{ + [ApiController] + [Route("[controller]")] + public class DefaultController : ControllerBase + { + private IMessager Transient { get; } + + private IMessager Scope { get; } + + private IMessager Single { get; } + + private readonly ILogger _logger; + + public DefaultController(ILogger logger, + ITransientMessager transient, + IScopeMessager scope, + ISingleMessager single) + { + this._logger = logger; + + this.Transient = transient; + this.Scope = scope; + this.Single = single; + } + + [HttpGet] + public IActionResult Get() + { + var content = $"transient:{this.Transient.OperationId}\r\n" + + $"scope:{this.Scope.OperationId}\r\n" + + $"single:{this.Single.OperationId}"; + this._logger.LogInformation("transient = {transient},scope = {scope},single = {single}", + this.Transient.OperationId, + this.Scope.OperationId, + this.Single.OperationId); + return this.Ok(content); + } + } +} \ No newline at end of file diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/IMessager.cs b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/IMessager.cs new file mode 100644 index 00000000..07685fa7 --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/IMessager.cs @@ -0,0 +1,7 @@ +namespace WebApiNetCore31 +{ + public interface IMessager + { + string OperationId { get; } + } +} \ No newline at end of file diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/IScopeMessager.cs b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/IScopeMessager.cs new file mode 100644 index 00000000..101c08fa --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/IScopeMessager.cs @@ -0,0 +1,6 @@ +namespace WebApiNetCore31 +{ + public interface IScopeMessager : IMessager + { + } +} \ No newline at end of file diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/ISingleMessager.cs b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/ISingleMessager.cs new file mode 100644 index 00000000..e803b8c9 --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/ISingleMessager.cs @@ -0,0 +1,6 @@ +namespace WebApiNetCore31 +{ + public interface ISingleMessager : IMessager + { + } +} diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/ITransientMessager.cs b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/ITransientMessager.cs new file mode 100644 index 00000000..b152a276 --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/ITransientMessager.cs @@ -0,0 +1,6 @@ +namespace WebApiNetCore31 +{ + public interface ITransientMessager : IMessager + { + } +} \ No newline at end of file diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/LogMessager.cs b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/LogMessager.cs new file mode 100644 index 00000000..633adcba --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/LogMessager.cs @@ -0,0 +1,9 @@ +using System; + +namespace WebApiNetCore31 +{ + internal class LogMessager : IMessager + { + public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; + } +} \ No newline at end of file diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/MachineMessager.cs b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/MachineMessager.cs new file mode 100644 index 00000000..d3e4038f --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/MachineMessager.cs @@ -0,0 +1,9 @@ +using System; + +namespace WebApiNetCore31 +{ + internal class MachineMessager : IMessager + { + public string OperationId { get; } = $"機器-{Guid.NewGuid()}"; + } +} \ No newline at end of file diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/Messager.cs b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/Messager.cs new file mode 100644 index 00000000..921613a7 --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/Messager.cs @@ -0,0 +1,9 @@ +using System; + +namespace WebApiNetCore31 +{ + public class Messager : IMessager + { + public string OperationId { get; } = $"訊息-{Guid.NewGuid()}"; + } +} \ No newline at end of file diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/MultiMessager.cs b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/MultiMessager.cs new file mode 100644 index 00000000..e4eb78a9 --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Message/MultiMessager.cs @@ -0,0 +1,9 @@ +using System; + +namespace WebApiNetCore31 +{ + public class MultiMessager : IScopeMessager, ISingleMessager, ITransientMessager + { + public string OperationId { get; } = $"多個接口-{Guid.NewGuid()}"; + } +} \ No newline at end of file diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/Program.cs b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Program.cs new file mode 100644 index 00000000..aba702a0 --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace WebApiNetCore31 +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/Properties/launchSettings.json b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Properties/launchSettings.json new file mode 100644 index 00000000..139d8acd --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:51385", + "sslPort": 44396 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "default1", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WebApiNetCore31": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "weatherforecast", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/Startup.cs b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Startup.cs new file mode 100644 index 00000000..71a2aa39 --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Startup.cs @@ -0,0 +1,72 @@ +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace WebApiNetCore31 +{ + public class Startup + { + public IConfiguration Configuration { get; } + + public Startup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + public void AutoConfigureServices(IServiceCollection services) + { + var assembly = Assembly.GetExecutingAssembly(); + + services.Scan(scan => scan.FromAssemblies(assembly) + .AddClasses(classes => classes.AssignableTo()) + .AsImplementedInterfaces() + .WithScopedLifetime() + ); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + + //AutoConfigureServices(services); + this.CustomConfigureServices(services); + } + + public void CustomConfigureServices(IServiceCollection services) + { + var assembly = Assembly.GetExecutingAssembly(); + var filterTypes = from type in assembly.GetTypes() + where type.IsAbstract == false + where typeof(IMessager).IsAssignableFrom(type) + //where type.Name.EndsWith("Messsage") + select type; + + foreach (var type in filterTypes) + { + services.AddTransient( type); + } + } + } +} \ No newline at end of file diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/WeatherForecast.cs b/DI/Lib.MsDiForScrutor/WebApiNetCore31/WeatherForecast.cs new file mode 100644 index 00000000..1bb9e27c --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/WeatherForecast.cs @@ -0,0 +1,15 @@ +using System; + +namespace WebApiNetCore31 +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string Summary { get; set; } + } +} diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/WebApiNetCore31.csproj b/DI/Lib.MsDiForScrutor/WebApiNetCore31/WebApiNetCore31.csproj new file mode 100644 index 00000000..71dcbf40 --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/WebApiNetCore31.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + + + + + diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/Worker.cs b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Worker.cs new file mode 100644 index 00000000..f069f7e4 --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/Worker.cs @@ -0,0 +1,22 @@ +namespace WebApiNetCore31 +{ + public class Worker + { + public IMessager Messager { get; set; } + + public Worker(IMessager messager) + { + this.Messager = messager; + } + } + + public class Worker2 + { + public IMessager Messager { get; set; } + + public Worker2(IMessager messager) + { + this.Messager = messager; + } + } +} \ No newline at end of file diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/appsettings.Development.json b/DI/Lib.MsDiForScrutor/WebApiNetCore31/appsettings.Development.json new file mode 100644 index 00000000..8983e0fc --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/DI/Lib.MsDiForScrutor/WebApiNetCore31/appsettings.json b/DI/Lib.MsDiForScrutor/WebApiNetCore31/appsettings.json new file mode 100644 index 00000000..d9d9a9bf --- /dev/null +++ b/DI/Lib.MsDiForScrutor/WebApiNetCore31/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} From ef896a7358aaf3a5b1c1af0b7782f41ac72810f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E5=B0=8F=E7=AB=A0?= Date: Fri, 20 Aug 2021 00:03:01 +0800 Subject: [PATCH 118/424] add --- DI/Lab.MsDIForAutofac/Lab.MsDIForAutofac.sln | 31 +++ DI/Lab.MsDIForAutofac/TestProject1/Hello.cs | 23 +++ .../TestProject1/HelloConsumer.cs | 24 +++ .../TestProject1/TestProject1.csproj | 17 ++ .../TestProject1/UnitTest1.cs | 25 +++ .../UnitTestProject1/App.config | 30 +++ .../UnitTestProject1/EntityModel/IDENTITY.cs | 39 ++++ .../UnitTestProject1/EntityModel/MEMBER.cs | 39 ++++ .../UnitTestProject1/EntityModel/MemberDb.cs | 24 +++ .../Properties/AssemblyInfo.cs | 20 ++ .../UnitTestProject1/UnitTest1.cs | 21 ++ .../UnitTestProject1/UnitTestProject1.csproj | 88 +++++++++ .../UnitTestProject1/packages.config | 7 + .../App_Start/DefaultDependencyResolver.cs | 39 ++++ .../App_Start/DependencyInjectionConfig.cs | 67 +++++++ .../App_Start/ServiceProviderExtensions.cs | 20 ++ .../WebApiNet48/App_Start/WebApiConfig.cs | 25 +++ .../Controllers/DefaultController.cs | 36 ++++ DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax | 1 + .../WebApiNet48/Global.asax.cs | 17 ++ .../WebApiNet48/Message/IMessager.cs | 7 + .../WebApiNet48/Message/LogMessager.cs | 9 + .../WebApiNet48/Properties/AssemblyInfo.cs | 35 ++++ .../ServiceCollectionExtensions.cs | 26 +++ .../WebApiNet48/Web.Debug.config | 30 +++ .../WebApiNet48/Web.Release.config | 31 +++ DI/Lab.MsDIForAutofac/WebApiNet48/Web.config | 58 ++++++ .../WebApiNet48/WebApiNet48.csproj | 184 ++++++++++++++++++ .../WebApiNet48.csproj.DotSettings | 2 + .../WebApiNet48/WebApiNet48.csproj.user | 45 +++++ ...gnTimeResolveAssemblyReferencesInput.cache | Bin 0 -> 6044 bytes .../WebApiNet48/packages.config | 20 ++ .../Controllers/AutofacDefaultController.cs | 29 +++ .../Controllers/DefaultController.cs | 30 +++ .../Controllers/TestController.cs | 27 +++ .../WebApiNetCore31/Message/IMessager.cs | 7 + .../WebApiNetCore31/Message/IScopeMessager.cs | 6 + .../Message/ISingleMessager.cs | 6 + .../Message/ITransientMessager.cs | 6 + .../WebApiNetCore31/Message/LogMessager.cs | 9 + .../Message/MachineMessager.cs | 9 + .../WebApiNetCore31/Message/MultiMessager.cs | 9 + .../WebApiNetCore31/Program.cs | 22 +++ .../Properties/launchSettings.json | 30 +++ .../WebApiNetCore31/Startup.cs | 49 +++++ .../WebApiNetCore31/WebApiNetCore31.csproj | 20 ++ .../WebApiNetCore31.csproj.DotSettings | 2 + .../WebApiNetCore31.csproj.user | 6 + DI/Lab.MsDIForAutofac/WebApiNetCore31/a.cs | 29 +++ .../appsettings.Development.json | 9 + .../WebApiNetCore31/appsettings.json | 10 + DI/Lab.MsDIForAutofac/WebApiNetCore31/b.cs | 39 ++++ .../WebApiNetCore31.AssemblyInfo.cs | 23 +++ .../WebApiNetCore31.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 3 + .../WebApiNetCore31.assets.cache | Bin 0 -> 358 bytes ...piNetCore31.csproj.AssemblyReference.cache | Bin 0 -> 174405 bytes .../WebApiNetCore31.csproj.nuget.dgspec.json | 70 +++++++ .../obj/WebApiNetCore31.csproj.nuget.g.props | 18 ++ .../WebApiNetCore31.csproj.nuget.g.targets | 6 + .../WebApiNetCore31/obj/project.assets.json | 88 +++++++++ .../WebApiNetCore31/obj/project.nuget.cache | 18 ++ DI/Lab.MsDIForAutofac/WinFormNet48/App.config | 6 + .../WinFormNet48/Form2.Designer.cs | 59 ++++++ DI/Lab.MsDIForAutofac/WinFormNet48/Form2.cs | 20 ++ DI/Lab.MsDIForAutofac/WinFormNet48/Form2.resx | 120 ++++++++++++ .../WinFormNet48/Form3.Designer.cs | 39 ++++ DI/Lab.MsDIForAutofac/WinFormNet48/Form3.cs | 20 ++ .../WinFormNet48/MainForm.Designer.cs | 95 +++++++++ .../WinFormNet48/MainForm.cs | 57 ++++++ .../WinFormNet48/MainForm.resx | 120 ++++++++++++ DI/Lab.MsDIForAutofac/WinFormNet48/Program.cs | 22 +++ .../WinFormNet48/Properties/AssemblyInfo.cs | 36 ++++ .../Properties/Resources.Designer.cs | 71 +++++++ .../WinFormNet48/Properties/Resources.resx | 117 +++++++++++ .../Properties/Settings.Designer.cs | 30 +++ .../WinFormNet48/Properties/Settings.settings | 7 + .../WinFormNet48/WinFormNet48.csproj | 106 ++++++++++ .../WinFormNet48/packages.config | 5 + 79 files changed, 2551 insertions(+) create mode 100644 DI/Lab.MsDIForAutofac/Lab.MsDIForAutofac.sln create mode 100644 DI/Lab.MsDIForAutofac/TestProject1/Hello.cs create mode 100644 DI/Lab.MsDIForAutofac/TestProject1/HelloConsumer.cs create mode 100644 DI/Lab.MsDIForAutofac/TestProject1/TestProject1.csproj create mode 100644 DI/Lab.MsDIForAutofac/TestProject1/UnitTest1.cs create mode 100644 DI/Lab.MsDIForAutofac/UnitTestProject1/App.config create mode 100644 DI/Lab.MsDIForAutofac/UnitTestProject1/EntityModel/IDENTITY.cs create mode 100644 DI/Lab.MsDIForAutofac/UnitTestProject1/EntityModel/MEMBER.cs create mode 100644 DI/Lab.MsDIForAutofac/UnitTestProject1/EntityModel/MemberDb.cs create mode 100644 DI/Lab.MsDIForAutofac/UnitTestProject1/Properties/AssemblyInfo.cs create mode 100644 DI/Lab.MsDIForAutofac/UnitTestProject1/UnitTest1.cs create mode 100644 DI/Lab.MsDIForAutofac/UnitTestProject1/UnitTestProject1.csproj create mode 100644 DI/Lab.MsDIForAutofac/UnitTestProject1/packages.config create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DefaultDependencyResolver.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DependencyInjectionConfig.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/ServiceProviderExtensions.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/WebApiConfig.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Controllers/DefaultController.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Message/IMessager.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Message/LogMessager.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Properties/AssemblyInfo.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/ServiceCollectionExtensions.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Web.Debug.config create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Web.Release.config create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/Web.config create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj.DotSettings create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj.user create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache create mode 100644 DI/Lab.MsDIForAutofac/WebApiNet48/packages.config create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/AutofacDefaultController.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/DefaultController.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/TestController.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IMessager.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IScopeMessager.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ISingleMessager.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ITransientMessager.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/LogMessager.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MachineMessager.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MultiMessager.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Program.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Properties/launchSettings.json create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/Startup.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj.user create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/a.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.Development.json create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.json create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/b.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.AssemblyInfo.cs create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.AssemblyInfoInputs.cache create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.assets.cache create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.csproj.AssemblyReference.cache create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/WebApiNetCore31.csproj.nuget.dgspec.json create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/WebApiNetCore31.csproj.nuget.g.props create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/WebApiNetCore31.csproj.nuget.g.targets create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/project.assets.json create mode 100644 DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/project.nuget.cache create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/App.config create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/Form2.Designer.cs create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/Form2.cs create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/Form2.resx create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/Form3.Designer.cs create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/Form3.cs create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/MainForm.Designer.cs create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/MainForm.cs create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/MainForm.resx create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/Program.cs create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/Properties/AssemblyInfo.cs create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Resources.Designer.cs create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Resources.resx create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Settings.Designer.cs create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Settings.settings create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/WinFormNet48.csproj create mode 100644 DI/Lab.MsDIForAutofac/WinFormNet48/packages.config diff --git a/DI/Lab.MsDIForAutofac/Lab.MsDIForAutofac.sln b/DI/Lab.MsDIForAutofac/Lab.MsDIForAutofac.sln new file mode 100644 index 00000000..b710049a --- /dev/null +++ b/DI/Lab.MsDIForAutofac/Lab.MsDIForAutofac.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30503.244 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiNetCore31", "WebApiNetCore31\WebApiNetCore31.csproj", "{B5E0DA79-326E-41CB-A95C-0C7FFDC70FF0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiNet48", "WebApiNet48\WebApiNet48.csproj", "{9EA9B67E-7812-41CB-899B-4331B5344882}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B5E0DA79-326E-41CB-A95C-0C7FFDC70FF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5E0DA79-326E-41CB-A95C-0C7FFDC70FF0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5E0DA79-326E-41CB-A95C-0C7FFDC70FF0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5E0DA79-326E-41CB-A95C-0C7FFDC70FF0}.Release|Any CPU.Build.0 = Release|Any CPU + {9EA9B67E-7812-41CB-899B-4331B5344882}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9EA9B67E-7812-41CB-899B-4331B5344882}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9EA9B67E-7812-41CB-899B-4331B5344882}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9EA9B67E-7812-41CB-899B-4331B5344882}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {526A154E-E406-4F6B-A76D-4455CA7B02B1} + EndGlobalSection +EndGlobal diff --git a/DI/Lab.MsDIForAutofac/TestProject1/Hello.cs b/DI/Lab.MsDIForAutofac/TestProject1/Hello.cs new file mode 100644 index 00000000..6feacc21 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/TestProject1/Hello.cs @@ -0,0 +1,23 @@ +namespace TestProject1 +{ + public interface IHello + { + string SayHello(); + } + + public class EnglishHello : IHello + { + public string SayHello() + { + return "Hello"; + } + } + + public class FrenchHello : IHello + { + public string SayHello() + { + return "Bonjour"; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/TestProject1/HelloConsumer.cs b/DI/Lab.MsDIForAutofac/TestProject1/HelloConsumer.cs new file mode 100644 index 00000000..b20ad097 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/TestProject1/HelloConsumer.cs @@ -0,0 +1,24 @@ +using System; +using Autofac.Features.AttributeFilters; + +namespace TestProject1 +{ + public class HelloConsumer + { + private readonly IHello helloService; + + public HelloConsumer([KeyFilter("FR")] IHello helloService) + { + if (helloService == null) + { + throw new ArgumentNullException("helloService"); + } + this.helloService = helloService; + } + + public string SayHello() + { + return this.helloService.SayHello(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/TestProject1/TestProject1.csproj b/DI/Lab.MsDIForAutofac/TestProject1/TestProject1.csproj new file mode 100644 index 00000000..a9ae9b11 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/TestProject1/TestProject1.csproj @@ -0,0 +1,17 @@ + + + + net5.0 + + false + + + + + + + + + + + diff --git a/DI/Lab.MsDIForAutofac/TestProject1/UnitTest1.cs b/DI/Lab.MsDIForAutofac/TestProject1/UnitTest1.cs new file mode 100644 index 00000000..f5c735c4 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/TestProject1/UnitTest1.cs @@ -0,0 +1,25 @@ +using System; +using Autofac; +using Autofac.Features.AttributeFilters; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace TestProject1 +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + ContainerBuilder cb = new ContainerBuilder(); + + cb.RegisterType().Keyed("EN"); + cb.RegisterType().Keyed("FR"); + cb.RegisterType().WithAttributeFiltering(); + var container = cb.Build(); + + var consumer = container.Resolve(); + Console.WriteLine(consumer.SayHello()); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/UnitTestProject1/App.config b/DI/Lab.MsDIForAutofac/UnitTestProject1/App.config new file mode 100644 index 00000000..3002dd1f --- /dev/null +++ b/DI/Lab.MsDIForAutofac/UnitTestProject1/App.config @@ -0,0 +1,30 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/UnitTestProject1/EntityModel/IDENTITY.cs b/DI/Lab.MsDIForAutofac/UnitTestProject1/EntityModel/IDENTITY.cs new file mode 100644 index 00000000..cd16740a --- /dev/null +++ b/DI/Lab.MsDIForAutofac/UnitTestProject1/EntityModel/IDENTITY.cs @@ -0,0 +1,39 @@ +using LinqToDB.Mapping; + +namespace UnitTestProject1.EntityModel +{ + /// + /// + [Table("IDENTITY")] + public class Identity + { + /// + /// MEMBER_ID + /// + [Column] + [PrimaryKey] + public int MEMBER_ID { get; set; } + + /// + /// ACCOUNT + /// + [Column] + public string ACCOUNT { get; set; } + + /// + /// PASSWORD + /// + [Column] + public string PASSWORD { get; set; } + + /// + /// REMARK + /// + [Column] + [Nullable] + public string REMARK { get; set; } + + [Association(ThisKey = "MEMBER_ID", OtherKey = "ID", CanBeNull = false)] + public Member MEMBER { get; set; } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/UnitTestProject1/EntityModel/MEMBER.cs b/DI/Lab.MsDIForAutofac/UnitTestProject1/EntityModel/MEMBER.cs new file mode 100644 index 00000000..1621a3eb --- /dev/null +++ b/DI/Lab.MsDIForAutofac/UnitTestProject1/EntityModel/MEMBER.cs @@ -0,0 +1,39 @@ +using LinqToDB.Mapping; + +namespace UnitTestProject1.EntityModel +{ + /// + /// + [Table("MEMBER")] + public class Member + { + /// + /// ID + /// + [PrimaryKey] + [Column] + public int ID { get; set; } + + /// + /// NAME + /// + [Column] + public string NAME { get; set; } + + /// + /// AGE + /// + [Column] + public int AGE { get; set; } + + /// + /// REMARK + /// + [Column] + [Nullable] + public string REMARK { get; set; } + + [Association(ThisKey = "ID", OtherKey = "MEMBER_ID", CanBeNull = true)] + public Identity IDENTITY { get; set; } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/UnitTestProject1/EntityModel/MemberDb.cs b/DI/Lab.MsDIForAutofac/UnitTestProject1/EntityModel/MemberDb.cs new file mode 100644 index 00000000..6045d451 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/UnitTestProject1/EntityModel/MemberDb.cs @@ -0,0 +1,24 @@ +using LinqToDB; +using LinqToDB.Data; + +namespace UnitTestProject1.EntityModel +{ + public class MemberDb : DataConnection + { + public MemberDb() + : base("MemberDb") + { + } + + //public ITable MJVNTRs { get { return this.GetTable(); } } + public ITable Members + { + get { return this.GetTable(); } + } + + public ITable Identities + { + get { return this.GetTable(); } + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/UnitTestProject1/Properties/AssemblyInfo.cs b/DI/Lab.MsDIForAutofac/UnitTestProject1/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..80f817bf --- /dev/null +++ b/DI/Lab.MsDIForAutofac/UnitTestProject1/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("UnitTestProject1")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("UnitTestProject1")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("79bb1d0c-74b0-4b0f-acab-251831bfc96c")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DI/Lab.MsDIForAutofac/UnitTestProject1/UnitTest1.cs b/DI/Lab.MsDIForAutofac/UnitTestProject1/UnitTest1.cs new file mode 100644 index 00000000..04a35b9f --- /dev/null +++ b/DI/Lab.MsDIForAutofac/UnitTestProject1/UnitTest1.cs @@ -0,0 +1,21 @@ +using System; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using UnitTestProject1.EntityModel; + +namespace UnitTestProject1 +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + using (var db=new MemberDb()) + { + var members = db.Members.ToList(); + } + } + + } +} diff --git a/DI/Lab.MsDIForAutofac/UnitTestProject1/UnitTestProject1.csproj b/DI/Lab.MsDIForAutofac/UnitTestProject1/UnitTestProject1.csproj new file mode 100644 index 00000000..0ba22660 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/UnitTestProject1/UnitTestProject1.csproj @@ -0,0 +1,88 @@ + + + + + + Debug + AnyCPU + {79BB1D0C-74B0-4B0F-ACAB-251831BFC96C} + Library + Properties + UnitTestProject1 + UnitTestProject1 + v4.8 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + C:\Program Files (x86)\IBM\Client Access\IBM.Data.DB2.iSeries.dll + + + ..\packages\linq2db.2.6.0\lib\net46\linq2db.dll + + + ..\packages\linq2db4iSeries.2.6.0\lib\net45\LinqToDB.DataProvider.DB2iSeries.dll + + + + ..\packages\MSTest.TestFramework.2.1.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.2.1.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/UnitTestProject1/packages.config b/DI/Lab.MsDIForAutofac/UnitTestProject1/packages.config new file mode 100644 index 00000000..692b8154 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/UnitTestProject1/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DefaultDependencyResolver.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DefaultDependencyResolver.cs new file mode 100644 index 00000000..738882bb --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DefaultDependencyResolver.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Web.Http.Dependencies; +using Microsoft.Extensions.DependencyInjection; + +namespace WebApiNet48 +{ + public class DefaultDependencyResolver : IDependencyResolver + { + protected IServiceProvider ServiceProvider { get; set; } + + public DefaultDependencyResolver(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public object GetService(Type serviceType) + { + return this.ServiceProvider.GetService(serviceType); + } + + public IEnumerable GetServices(Type serviceType) + { + return this.ServiceProvider.GetServices(serviceType); + } + + public IDependencyScope BeginScope() + { + return new DefaultDependencyResolver(this.ServiceProvider.CreateScope().ServiceProvider); + } + + public void Dispose() + { + // you can implement this interface just when you use .net core 2.0 + // this.ServiceProvider.Dispose(); + ((ServiceProvider)this.ServiceProvider).Dispose(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DependencyInjectionConfig.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DependencyInjectionConfig.cs new file mode 100644 index 00000000..6e2d792a --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/DependencyInjectionConfig.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Web.Http; +using System.Web.Http.Controllers; +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace WebApiNet48 +{ + public class DependencyInjectionConfig + { + public static void Register(HttpConfiguration config) + { + var services = ConfigureServices(); + var builder = ConfigureContainerBuilder(services); + var provider = new AutofacServiceProvider(builder.Build()); + + //var provider = services.BuildServiceProvider(); + + var resolver = new DefaultDependencyResolver(provider); + config.DependencyResolver = resolver; + } + + /// + /// 使用 Autofac 註冊 + /// + /// + /// + private static ContainerBuilder ConfigureContainerBuilder(IServiceCollection services) + { + var builder = new ContainerBuilder(); + builder.Populate(services); + + var assembly = Assembly.GetExecutingAssembly(); + builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces(); + + return builder; + } + + /// + /// 使用 MS DI 註冊 + /// + /// + private static ServiceCollection ConfigureServices() + { + var services = new ServiceCollection(); + + //使用 Microsoft.Extensions.DependencyInjection 註冊 + services.AddControllersAsServices(typeof(DependencyInjectionConfig) + .Assembly + .GetExportedTypes() + .Where(t => !t.IsAbstract && !t.IsGenericTypeDefinition) + .Where(t => typeof(IHttpController).IsAssignableFrom(t) + || t.Name.EndsWith("Controller", + StringComparison.OrdinalIgnoreCase))); + + //services.AddScoped(); + + //services.AddTransient() + // .AddSingleton() + // .AddScoped(); + return services; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/ServiceProviderExtensions.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/ServiceProviderExtensions.cs new file mode 100644 index 00000000..eda67822 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/ServiceProviderExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; + +namespace WebApiNet48 +{ + public static class ServiceProviderExtensions + { + public static IServiceCollection AddControllersAsServices(this IServiceCollection services, + IEnumerable controllerTypes) + { + foreach (var type in controllerTypes) + { + services.AddTransient(type); + } + + return services; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/WebApiConfig.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/WebApiConfig.cs new file mode 100644 index 00000000..8ee1c430 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/App_Start/WebApiConfig.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Http; + +namespace WebApiNet48 +{ + public static class WebApiConfig + { + public static void Register(HttpConfiguration config) + { + DependencyInjectionConfig.Register(config); + // Web API configuration and services + + // Web API routes + config.MapHttpAttributeRoutes(); + + config.Routes.MapHttpRoute( + name: "DefaultApi", + routeTemplate: "api/{controller}/{id}", + defaults: new { id = RouteParameter.Optional } + ); + } + } +} diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Controllers/DefaultController.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/Controllers/DefaultController.cs new file mode 100644 index 00000000..9f16bc34 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/Controllers/DefaultController.cs @@ -0,0 +1,36 @@ +using System.Net.Http; +using System.Web.Http; + +namespace WebApiNet48.Controllers +{ + public class DefaultController : ApiController + { + private IMessager Messager { get; set; } + + public DefaultController(IMessager messager) + { + this.Messager = messager; + } + + [HttpGet] + public IHttpActionResult Get() + { + var content = $"Messager:{this.Messager.OperationId}"; + return this.Ok(content); + } + + [HttpGet] + public IHttpActionResult Get1() + { + var messager = InstanceManager.Messager; + + var content = $"Messager:{messager.OperationId}"; + return this.Ok(content); + } + } + + public class InstanceManager + { + public static IMessager Messager { get; set; } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax b/DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax new file mode 100644 index 00000000..7946eef2 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="WebApiNet48.WebApiApplication" Language="C#" %> diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax.cs new file mode 100644 index 00000000..13555dcb --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/Global.asax.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Http; +using System.Web.Routing; + +namespace WebApiNet48 +{ + public class WebApiApplication : System.Web.HttpApplication + { + protected void Application_Start() + { + GlobalConfiguration.Configure(WebApiConfig.Register); + } + } +} diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Message/IMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/Message/IMessager.cs new file mode 100644 index 00000000..8544b3b9 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/Message/IMessager.cs @@ -0,0 +1,7 @@ +namespace WebApiNet48 +{ + public interface IMessager + { + string OperationId { get; } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Message/LogMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/Message/LogMessager.cs new file mode 100644 index 00000000..55bac639 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/Message/LogMessager.cs @@ -0,0 +1,9 @@ +using System; + +namespace WebApiNet48 +{ + internal class LogMessager : IMessager + { + public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Properties/AssemblyInfo.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..09e000e3 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WebApiNet48")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WebApiNet48")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9ea9b67e-7812-41cb-899b-4331b5344882")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/ServiceCollectionExtensions.cs b/DI/Lab.MsDIForAutofac/WebApiNet48/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..3fe60da6 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/ServiceCollectionExtensions.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace WebApiNet48 +{ + public static class ServiceCollectionExtensions + { + /// + /// Adds the to the service collection. ONLY FOR PRE-ASP.NET 3.0 HOSTING. THIS WON'T WORK + /// FOR ASP.NET CORE 3.0+ OR GENERIC HOSTING. + /// + /// The service collection to add the factory to. + /// Action on a that adds component registrations to the container. + /// The service collection. + public static IServiceCollection AddAutofac(this IServiceCollection services, Action configurationAction = null) + { + return services.AddSingleton>(new AutofacServiceProviderFactory(configurationAction)); + } + } + +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Web.Debug.config b/DI/Lab.MsDIForAutofac/WebApiNet48/Web.Debug.config new file mode 100644 index 00000000..c1a56423 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Web.Release.config b/DI/Lab.MsDIForAutofac/WebApiNet48/Web.Release.config new file mode 100644 index 00000000..19058ed3 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/Web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/Web.config b/DI/Lab.MsDIForAutofac/WebApiNet48/Web.config new file mode 100644 index 00000000..2a9874d6 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/Web.config @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj b/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj new file mode 100644 index 00000000..dd49038d --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj @@ -0,0 +1,184 @@ + + + + + Debug + AnyCPU + + + 2.0 + {9EA9B67E-7812-41CB-899B-4331B5344882} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + WebApiNet48 + WebApiNet48 + v4.8 + true + + 44327 + + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + true + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + ..\packages\Autofac.6.0.0\lib\netstandard2.0\Autofac.dll + + + ..\packages\Autofac.Extensions.DependencyInjection.7.1.0\lib\netstandard2.0\Autofac.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + + + ..\packages\Microsoft.Extensions.DependencyInjection.3.1.9\lib\net461\Microsoft.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + ..\packages\System.Diagnostics.DiagnosticSource.4.7.1\lib\net46\System.Diagnostics.DiagnosticSource.dll + + + ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + + + + + + + + + + + + + + + + + + + ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll + + + ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll + + + + + + + + + + + + + + + + Global.asax + + + + + + + Web.config + + + Web.config + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + True + True + 54526 + / + https://localhost:44327/ + False + False + + + False + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj.DotSettings b/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj.DotSettings new file mode 100644 index 00000000..8a5228f0 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj.user b/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj.user new file mode 100644 index 00000000..9749d8ec --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/WebApiNet48.csproj.user @@ -0,0 +1,45 @@ + + + + true + + 44327 + + + + + Debug|Any CPU + ApiControllerEmptyScaffolder + root/Controller + 600 + True + False + True + + False + + + + + + api/default + SpecificPage + True + False + False + False + + + + + + + + + True + False + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache b/DI/Lab.MsDIForAutofac/WebApiNet48/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache new file mode 100644 index 0000000000000000000000000000000000000000..226d11bd723bf60a5be6c834823fbaf939b181b1 GIT binary patch literal 6044 zcmeHLZBrXJ5VpZ67($9^N(eNG+oo;O@!Z(uotDlJj8jho!`L43fn$j7Zfz8FCy%59 zT!!D$U(o4KY**(O2qC1Nv1j<;^W2`i(ymskmGo$1@mMVOFFxjLp8G>cylv)6p+r?% zdrXCtmv!hdVQmWE7eO(vfP;iOOP754C!X!jCEE z;uO3MrNDQ1W9v*qV60LT*m_e0RaPr74%03TJmygi?UshC=Bgp*1xbB474o=URiO^n zJ^Y}qAh?I)eQKB$Dr=xSV&%|Lpm$J-dd_NrkdTKeMOPF9rP01i>zayxy1h|TU9;&U zcC(5nAMvkBr8$mM>1$@@nFLo0$v{okJxyg9sC18ZsXkJZwQG#=x)fFPkQvpKgc?&? zHZBE{I4}*yg|n#Wsxz+iGJUOTTLkPF>K87dGud7YKS+5}g$z1CVI_q6-fB%*yxFYr_4`G)w`R?aV)3TN< zsAbZ=a)so~xm;rl|9gooFVjVLaVfVtzW~{l)q~7!cHB`@`{9R!-n#XUtBc16A=-~9 z>a3=ID8J(|)#C*;2+)4S&>%p2b#NZU4GvBYBNjo7F*1SV)p@CfoYu++q)({?y90J@YiFy{!H}7g1#2bK2B0mlxpj6y9-yA%{F<$# zYb)y4Iar?14i_~$U$oy)$J$o;Vgb=ms6~~!_FHf^>TCz}!ip`N_jVqf5Zi)6)LB7f zR01XVNe2dB!XV3~?hz!vFxF2Znf=hfHrh9ak`&fYnfi2R1Zl&cLVhA@@ueyGigEG~ zOJm5(TJzcY+-mYG{2v=j8NZsBOdDdIvcUJqBP=H@k8?5;mEa6g#`VVY#NhqqZd~!2 z3x{%xDtbm{QFy5vVf3TrM)wCYhqAwPBU`8LF|C1WEBG1}{MwBI(|I&;Z4FOQ!*AVc zC;WoH*EY2b*qBuNS?>EYX&5h}Sw_jT|cWCyXARH1iBt^G{-Mr}|8W<&)RH ze#CvRC%?xCbN^s;H2D;Fz4ntbhquW&k!MKCn`<`S`)io~Nk`eB^oz5VrI&W+NKCiR zW6YRbIk!6}-iIf+ZH_%VWspJCLmCT-0%nWi1|>w$;i@Td0~ z$;|oYjp?fk@*UPro&U}PZ4wqHlTpN_b3vXXfA+j`b8e<_LB7ZOUci$dP|w79iJlq< zn%vC=`4L&&9(EnX;;_I@83BNOn-s8)Wc{=MIZ+|xfjiOJF`0c T_h1A2T#9(Gfpt=M2jl+%2EfII literal 0 HcmV?d00001 diff --git a/DI/Lab.MsDIForAutofac/WebApiNet48/packages.config b/DI/Lab.MsDIForAutofac/WebApiNet48/packages.config new file mode 100644 index 00000000..7d6100d0 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNet48/packages.config @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/AutofacDefaultController.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/AutofacDefaultController.cs new file mode 100644 index 00000000..cb8c33b4 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/AutofacDefaultController.cs @@ -0,0 +1,29 @@ +using Autofac.Features.AttributeFilters; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace WebApiNetCore31.Controllers +{ + [ApiController] + [Route("[controller]")] + public class AutofacDefaultController : ControllerBase + { + private readonly IFileProvider _fileProvider; + + private readonly ILogger _logger; + + + public AutofacDefaultController(ILogger logger, + [KeyFilter("zip")] IFileProvider fileProvider) + { + this._logger = logger; + this._fileProvider = fileProvider; + } + + [HttpGet] + public IActionResult Get() + { + return this.Ok(this._fileProvider.Print()); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/DefaultController.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/DefaultController.cs new file mode 100644 index 00000000..8402dcb9 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/DefaultController.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace WebApiNetCore31.Controllers +{ + [ApiController] + [Route("[controller]")] + public class DefaultController : ControllerBase + { + private IMessager Messager { get; } + + private readonly ILogger _logger; + + public DefaultController(ILogger logger, + IMessager messager + ) + { + this._logger = logger; + this.Messager = messager; + } + + [HttpGet] + public IActionResult Get() + { + var content = $"Messager:{this.Messager.OperationId}"; + this._logger.LogInformation("Messager:{message}", content); + return this.Ok(content); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/TestController.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/TestController.cs new file mode 100644 index 00000000..aafcda8e --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Controllers/TestController.cs @@ -0,0 +1,27 @@ +using Autofac.Features.AttributeFilters; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace WebApiNetCore31.Controllers +{ + [ApiController] + [Route("[controller]")] + public class TestController : ControllerBase + { + private readonly ILogger _logger; + private readonly ITestService _testService; + + public TestController(ILogger logger, + [KeyFilter("service")] ITestService testService) + { + this._logger = logger; + this._testService = testService; + } + + [HttpGet] + public IActionResult Get() + { + return this.Ok(this._testService.GetDate()); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IMessager.cs new file mode 100644 index 00000000..35d3c543 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IMessager.cs @@ -0,0 +1,7 @@ +namespace WebApiNetCore31 +{ + public interface IMessager + { + string OperationId { get; } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IScopeMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IScopeMessager.cs new file mode 100644 index 00000000..c91f6890 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/IScopeMessager.cs @@ -0,0 +1,6 @@ +namespace WebApiNetCore31 +{ + public interface IScopeMessager : IMessager + { + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ISingleMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ISingleMessager.cs new file mode 100644 index 00000000..5d2f7fb1 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ISingleMessager.cs @@ -0,0 +1,6 @@ +namespace WebApiNetCore31 +{ + public interface ISingleMessager : IMessager + { + } +} diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ITransientMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ITransientMessager.cs new file mode 100644 index 00000000..4238a3ee --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/ITransientMessager.cs @@ -0,0 +1,6 @@ +namespace WebApiNetCore31 +{ + public interface ITransientMessager : IMessager + { + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/LogMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/LogMessager.cs new file mode 100644 index 00000000..f8725cea --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/LogMessager.cs @@ -0,0 +1,9 @@ +using System; + +namespace WebApiNetCore31 +{ + internal class LogMessager : IMessager + { + public string OperationId { get; } = $"日誌-{Guid.NewGuid()}"; + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MachineMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MachineMessager.cs new file mode 100644 index 00000000..9feede22 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MachineMessager.cs @@ -0,0 +1,9 @@ +using System; + +namespace WebApiNetCore31 +{ + internal class MachineMessager : IMessager + { + public string OperationId { get; } = $"機器-{Guid.NewGuid()}"; + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MultiMessager.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MultiMessager.cs new file mode 100644 index 00000000..673a9a2c --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Message/MultiMessager.cs @@ -0,0 +1,9 @@ +using System; + +namespace WebApiNetCore31 +{ + public class MultiMessager : IScopeMessager, ISingleMessager, ITransientMessager + { + public string OperationId { get; } = $"多個接口-{Guid.NewGuid()}"; + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Program.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Program.cs new file mode 100644 index 00000000..3d6598b4 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Program.cs @@ -0,0 +1,22 @@ +using Autofac.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace WebApiNetCore31 +{ + public class Program + { + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .UseServiceProviderFactory(new AutofacServiceProviderFactory()) + //.ConfigureServices(services => services.AddAutofac()) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + } + + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Properties/launchSettings.json b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Properties/launchSettings.json new file mode 100644 index 00000000..b39adb37 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:54205", + "sslPort": 44308 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "default", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WebApiNetCore31": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "weatherforecast", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/Startup.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Startup.cs new file mode 100644 index 00000000..0cc35cfc --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/Startup.cs @@ -0,0 +1,49 @@ +using System.Reflection; +using Autofac; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace WebApiNetCore31 +{ + public class Startup + { + public IConfiguration Configuration { get; } + + public Startup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + + public void ConfigureContainer(ContainerBuilder builder) + { + var assembly = Assembly.GetExecutingAssembly(); + builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces(); + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj b/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj new file mode 100644 index 00000000..93ae2103 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + + + + diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings b/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings new file mode 100644 index 00000000..8a5228f0 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj.user b/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj.user new file mode 100644 index 00000000..dc63f8a8 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/WebApiNetCore31.csproj.user @@ -0,0 +1,6 @@ + + + + false + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/a.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/a.cs new file mode 100644 index 00000000..c765a72d --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/a.cs @@ -0,0 +1,29 @@ +using System; + +namespace WebApiNetCore31 +{ + public interface IFileProvider + { + string Print(); + } + + public class FileProvider : IFileProvider + { + public string Print() + { + var msg = "FileProvider"; + Console.WriteLine(msg); + return msg; + } + } + + public class ZipFileProvider : IFileProvider + { + public string Print() + { + var msg = "ZipFileProvider"; + Console.WriteLine(msg); + return msg; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.Development.json b/DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.Development.json new file mode 100644 index 00000000..dba68eb1 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.json b/DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.json new file mode 100644 index 00000000..81ff8777 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/b.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/b.cs new file mode 100644 index 00000000..6e1a86a2 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/b.cs @@ -0,0 +1,39 @@ +namespace WebApiNetCore31 +{ + public interface ITestService + { + string GetDate(); + } + + public class TestService : ITestService + { + public string GetDate() + { + return "service"; + } + } + + public class TestComponent : ITestService + { + public string GetDate() + { + return "component"; + } + } + + public interface IServiceProvider + { + public void setService(); + } + + // Client class + public class Client + { + private IServiceProvider _service; + + public Client(IServiceProvider service) + { + this._service = service; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.AssemblyInfo.cs b/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.AssemblyInfo.cs new file mode 100644 index 00000000..1296b66c --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.AssemblyInfo.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("WebApiNetCore31")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] +[assembly: System.Reflection.AssemblyProductAttribute("WebApiNetCore31")] +[assembly: System.Reflection.AssemblyTitleAttribute("WebApiNetCore31")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.AssemblyInfoInputs.cache b/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.AssemblyInfoInputs.cache new file mode 100644 index 00000000..b94c1aa8 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +563fea439ee8548554f13f24a19fba2d41da1919 diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.GeneratedMSBuildEditorConfig.editorconfig b/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 00000000..f362fefd --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,3 @@ +is_global = true +build_property.RootNamespace = WebApiNetCore31 +build_property.ProjectDir = D:\src\sample.dotblog\DI\Lab.MsDIForAutofac\WebApiNetCore31\ diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.assets.cache b/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.assets.cache new file mode 100644 index 0000000000000000000000000000000000000000..6440b2a6acff8db9af7cf6a8e8ef42d92a1f4b55 GIT binary patch literal 358 zcmaiuF-yZx5XV!iA`X6r8*Im5x~M^inidhU7E6R+mgmbQJsaLD?_O*R{R*z)+(BGi z{3=c^;^gY=W&(8+Jov+L{C~%G>;En-tJzH7Eh16&H3W$^ZWV3uSfS6 zf%lbeAJ)p!KT}njfggvAhT}+R2!=vs5EZejiVST$7IGvq T(DQb@jRD@GAv;^^8l%k*f>2}E literal 0 HcmV?d00001 diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.csproj.AssemblyReference.cache b/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/Debug/netcoreapp3.1/WebApiNetCore31.csproj.AssemblyReference.cache new file mode 100644 index 0000000000000000000000000000000000000000..d5029fad62f2b044f81a2e82e585ea24cbbb99eb GIT binary patch literal 174405 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 gnTsb5^x&dM;gmreT1IYNq(UNKO={~1g}L + + + False + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\Yao Chang Yu\.nuget\packages\ + PackageReference + 5.11.0 + + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/WebApiNetCore31.csproj.nuget.g.targets b/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/WebApiNetCore31.csproj.nuget.g.targets new file mode 100644 index 00000000..d212750c --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/WebApiNetCore31.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/project.assets.json b/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/project.assets.json new file mode 100644 index 00000000..91d6469b --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/project.assets.json @@ -0,0 +1,88 @@ +{ + "version": 3, + "targets": { + ".NETCoreApp,Version=v3.1": {} + }, + "libraries": {}, + "projectFileDependencyGroups": { + ".NETCoreApp,Version=v3.1": [ + "Autofac.Extensions.DependencyInjection >= 7.1.0" + ] + }, + "packageFolders": { + "C:\\Users\\Yao Chang Yu\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "D:\\src\\sample.dotblog\\DI\\Lab.MsDIForAutofac\\WebApiNetCore31\\WebApiNetCore31.csproj", + "projectName": "WebApiNetCore31", + "projectPath": "D:\\src\\sample.dotblog\\DI\\Lab.MsDIForAutofac\\WebApiNetCore31\\WebApiNetCore31.csproj", + "packagesPath": "C:\\Users\\Yao Chang Yu\\.nuget\\packages\\", + "outputPath": "D:\\src\\sample.dotblog\\DI\\Lab.MsDIForAutofac\\WebApiNetCore31\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\Yao Chang Yu\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "netcoreapp3.1" + ], + "sources": { + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {} + }, + "frameworks": { + "netcoreapp3.1": { + "targetAlias": "netcoreapp3.1", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "netcoreapp3.1": { + "targetAlias": "netcoreapp3.1", + "dependencies": { + "Autofac.Extensions.DependencyInjection": { + "target": "Package", + "version": "[7.1.0, )" + } + }, + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.AspNetCore.App": { + "privateAssets": "none" + }, + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\5.0.400\\RuntimeIdentifierGraph.json" + } + } + }, + "logs": [ + { + "code": "NU1101", + "level": "Error", + "message": "Unable to find package Autofac.Extensions.DependencyInjection. No packages exist with this id in source(s): Microsoft Visual Studio Offline Packages", + "libraryId": "Autofac.Extensions.DependencyInjection", + "targetGraphs": [ + ".NETCoreApp,Version=v3.1" + ] + } + ] +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/project.nuget.cache b/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/project.nuget.cache new file mode 100644 index 00000000..f0606a64 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WebApiNetCore31/obj/project.nuget.cache @@ -0,0 +1,18 @@ +{ + "version": 2, + "dgSpecHash": "+oR7kmoX58hU75JnQMAzXJl+PfV8Klr4xZc4raw2Ndh3DuN5Ev+x0AdClXbVyIWS+ICIvv8tTLq6Y376zhkbMQ==", + "success": false, + "projectFilePath": "D:\\src\\sample.dotblog\\DI\\Lab.MsDIForAutofac\\WebApiNetCore31\\WebApiNetCore31.csproj", + "expectedPackageFiles": [], + "logs": [ + { + "code": "NU1101", + "level": "Error", + "message": "Unable to find package Autofac.Extensions.DependencyInjection. No packages exist with this id in source(s): Microsoft Visual Studio Offline Packages", + "libraryId": "Autofac.Extensions.DependencyInjection", + "targetGraphs": [ + ".NETCoreApp,Version=v3.1" + ] + } + ] +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/App.config b/DI/Lab.MsDIForAutofac/WinFormNet48/App.config new file mode 100644 index 00000000..3916e0e4 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/Form2.Designer.cs b/DI/Lab.MsDIForAutofac/WinFormNet48/Form2.Designer.cs new file mode 100644 index 00000000..18e39a8b --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/Form2.Designer.cs @@ -0,0 +1,59 @@ +namespace WinFormNet48 +{ + partial class Form2 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.button1 = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // button1 + // + this.button1.Location = new System.Drawing.Point(693, 394); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(75, 23); + this.button1.TabIndex = 0; + this.button1.Text = "button1"; + this.button1.UseVisualStyleBackColor = true; + // + // Form2 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Controls.Add(this.button1); + this.Name = "Form2"; + this.Text = "Form2"; + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button button1; + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/Form2.cs b/DI/Lab.MsDIForAutofac/WinFormNet48/Form2.cs new file mode 100644 index 00000000..6222446f --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/Form2.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace WinFormNet48 +{ + public partial class Form2 : Form + { + public Form2() + { + InitializeComponent(); + } + } +} diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/Form2.resx b/DI/Lab.MsDIForAutofac/WinFormNet48/Form2.resx new file mode 100644 index 00000000..29dcb1b3 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/Form2.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/Form3.Designer.cs b/DI/Lab.MsDIForAutofac/WinFormNet48/Form3.Designer.cs new file mode 100644 index 00000000..e4bf0689 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/Form3.Designer.cs @@ -0,0 +1,39 @@ +namespace WinFormNet48 +{ + partial class Form3 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Text = "Form3"; + } + + #endregion + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/Form3.cs b/DI/Lab.MsDIForAutofac/WinFormNet48/Form3.cs new file mode 100644 index 00000000..f170710b --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/Form3.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace WinFormNet48 +{ + public partial class Form3 : Form + { + public Form3() + { + InitializeComponent(); + } + } +} diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/MainForm.Designer.cs b/DI/Lab.MsDIForAutofac/WinFormNet48/MainForm.Designer.cs new file mode 100644 index 00000000..fb1bb09c --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/MainForm.Designer.cs @@ -0,0 +1,95 @@ +namespace WinFormNet48 +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.button2 = new System.Windows.Forms.Button(); + this.button1 = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + this.SuspendLayout(); + // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.Location = new System.Drawing.Point(0, 0); + this.splitContainer1.Name = "splitContainer1"; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.button2); + this.splitContainer1.Panel1.Controls.Add(this.button1); + this.splitContainer1.Size = new System.Drawing.Size(800, 450); + this.splitContainer1.SplitterDistance = 266; + this.splitContainer1.TabIndex = 0; + // + // button2 + // + this.button2.Location = new System.Drawing.Point(71, 78); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(75, 23); + this.button2.TabIndex = 1; + this.button2.Text = "button2"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // button1 + // + this.button1.Location = new System.Drawing.Point(71, 37); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(75, 23); + this.button1.TabIndex = 0; + this.button1.Text = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Controls.Add(this.splitContainer1); + this.Name = "MainForm"; + this.Text = "Form1"; + this.splitContainer1.Panel1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Button button1; + } +} + diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/MainForm.cs b/DI/Lab.MsDIForAutofac/WinFormNet48/MainForm.cs new file mode 100644 index 00000000..c9ea9f2d --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/MainForm.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Windows.Forms; + +namespace WinFormNet48 +{ + public partial class MainForm : Form + { + private readonly Dictionary _subFormLook = new Dictionary(); + private Form _previousForm; + + public MainForm() + { + this.InitializeComponent(); + } + + private void button1_Click(object sender, EventArgs e) + { + this.Show("Form2"); + } + + private void button2_Click(object sender, EventArgs e) + { + this.Show("Form3"); + } + + private void Show(string name) + { + Form subForm; + if (this._subFormLook.ContainsKey(name) == false) + { + subForm = new Form2(); + subForm.TopLevel = true; + subForm.Visible = true; + subForm.WindowState = FormWindowState.Maximized; + subForm.Dock = DockStyle.Fill; + //subForm.ControlBox = false; + this._subFormLook.Add(name, subForm); + //this.splitContainer1.Panel2.Controls.Add(subForm); + } + + subForm = this._subFormLook[name]; + + subForm.Show(); + + //if (this._previousForm != null) + //{ + // if (this._previousForm.Name != subForm.Name) + // { + // this._previousForm.Hide(); + // } + //} + + //this._previousForm = subForm; + } + } +} \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/MainForm.resx b/DI/Lab.MsDIForAutofac/WinFormNet48/MainForm.resx new file mode 100644 index 00000000..29dcb1b3 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/MainForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/Program.cs b/DI/Lab.MsDIForAutofac/WinFormNet48/Program.cs new file mode 100644 index 00000000..149cd059 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace WinFormNet48 +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/AssemblyInfo.cs b/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..5cabfbce --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WinFormNet48")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WinFormNet48")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0fb52124-ec0f-4ba8-975e-8b3656dc0def")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Resources.Designer.cs b/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Resources.Designer.cs new file mode 100644 index 00000000..bf954d2b --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WinFormNet48.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WinFormNet48.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Resources.resx b/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Resources.resx new file mode 100644 index 00000000..ffecec85 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Settings.Designer.cs b/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Settings.Designer.cs new file mode 100644 index 00000000..f43f01d5 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WinFormNet48.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Settings.settings b/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Settings.settings new file mode 100644 index 00000000..abf36c5d --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/WinFormNet48.csproj b/DI/Lab.MsDIForAutofac/WinFormNet48/WinFormNet48.csproj new file mode 100644 index 00000000..21afa8cf --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/WinFormNet48.csproj @@ -0,0 +1,106 @@ + + + + + Debug + AnyCPU + {0FB52124-EC0F-4BA8-975E-8B3656DC0DEF} + WinExe + WinFormNet48 + WinFormNet48 + v4.8 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\linq2db.3.1.5\lib\net46\linq2db.dll + + + ..\packages\linq2db4iSeries.3.1.5\lib\net45\LinqToDB.DataProvider.DB2iSeries.dll + + + + + + + + + + + + + + + + + Form + + + MainForm.cs + + + Form + + + Form2.cs + + + Form + + + Form3.cs + + + + + Form2.cs + + + MainForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + \ No newline at end of file diff --git a/DI/Lab.MsDIForAutofac/WinFormNet48/packages.config b/DI/Lab.MsDIForAutofac/WinFormNet48/packages.config new file mode 100644 index 00000000..3636f3c7 --- /dev/null +++ b/DI/Lab.MsDIForAutofac/WinFormNet48/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From 0c60abb38afdb7a54db4db73eced495ad049149c Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 23 Oct 2021 15:01:01 +0800 Subject: [PATCH 119/424] add project --- .../CustomTestServer.cs | 25 +++++++++ .../Lab.Test.WebApi.Net5.TestProject.csproj | 22 ++++++++ .../SurveyWebApplicationFactory.cs | 32 ++++++++++++ .../Controllers/DemoController.cs | 47 +++++++++++++++++ .../Lab.Test.WebApi.Net5/FileProvider.cs | 12 +++++ .../Lab.Test.WebApi.Net5/IFileProvider.cs | 7 +++ .../Lab.Test.WebApi.Net5.csproj | 11 ++++ .../Lab.Test.WebApi.Net5/Program.cs | 19 +++++++ .../Properties/launchSettings.json | 31 +++++++++++ .../Lab.Test.WebApi.Net5/Startup.cs | 51 +++++++++++++++++++ .../appsettings.Development.json | 9 ++++ .../Lab.Test.WebApi.Net5/appsettings.json | 10 ++++ WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.sln | 22 ++++++++ 13 files changed, 298 insertions(+) create mode 100644 WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/CustomTestServer.cs create mode 100644 WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/Lab.Test.WebApi.Net5.TestProject.csproj create mode 100644 WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/SurveyWebApplicationFactory.cs create mode 100644 WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Controllers/DemoController.cs create mode 100644 WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/FileProvider.cs create mode 100644 WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/IFileProvider.cs create mode 100644 WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Lab.Test.WebApi.Net5.csproj create mode 100644 WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Program.cs create mode 100644 WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Properties/launchSettings.json create mode 100644 WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Startup.cs create mode 100644 WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/appsettings.Development.json create mode 100644 WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/appsettings.json create mode 100644 WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.sln diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/CustomTestServer.cs b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/CustomTestServer.cs new file mode 100644 index 00000000..a420a623 --- /dev/null +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/CustomTestServer.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; + +namespace Lab.Test.WebApi.Net5.TestProject +{ + public class CustomTestServer : WebApplicationFactory + { + private void ConfigureServices(IServiceCollection services) + { + services.AddScoped(p => + { + var fileProvider = Substitute.For(); + fileProvider.Name().Returns("Fake FileProfile"); + return fileProvider; + }); + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureServices(this.ConfigureServices); + } + } +} \ No newline at end of file diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/Lab.Test.WebApi.Net5.TestProject.csproj b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/Lab.Test.WebApi.Net5.TestProject.csproj new file mode 100644 index 00000000..53390344 --- /dev/null +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/Lab.Test.WebApi.Net5.TestProject.csproj @@ -0,0 +1,22 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + + + diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/SurveyWebApplicationFactory.cs b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/SurveyWebApplicationFactory.cs new file mode 100644 index 00000000..bc1a2542 --- /dev/null +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/SurveyWebApplicationFactory.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.Test.WebApi.Net5.TestProject +{ + [TestClass] + public class SurveyWebApplicationFactory + { + [TestMethod] + public void CustomTestServer() + { + var server = new CustomTestServer(); + var httpClient = server.CreateClient(); + var url = "demo/file"; + var response = httpClient.GetAsync(url).Result; + var result = response.Content.ReadAsStringAsync().Result; + Console.WriteLine(result); + } + + [TestMethod] + public void WebApplicationFactory基本用法() + { + var server = new WebApplicationFactory(); + var httpClient = server.CreateClient(); + var url = "demo/file"; + var response = httpClient.GetAsync(url).Result; + var result = response.Content.ReadAsStringAsync().Result; + Console.WriteLine(result); + } + } +} \ No newline at end of file diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Controllers/DemoController.cs b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Controllers/DemoController.cs new file mode 100644 index 00000000..afa35b7b --- /dev/null +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Controllers/DemoController.cs @@ -0,0 +1,47 @@ +using System.Threading; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Lab.Test.WebApi.Net5.Controllers +{ + [ApiController] + [Route("[controller]")] + public class DemoController : ControllerBase + { + private readonly IFileProvider _fileProvider; + private readonly ILogger _logger; + + public DemoController(ILogger logger, + IFileProvider fileProvider) + { + this._logger = logger; + this._fileProvider = fileProvider; + } + + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(QueryResponse))] + public IActionResult Get(CancellationToken cancel) + { + return this.Ok(new QueryResponse + { + Message = "Hello" + }); + } + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(QueryResponse))] + [Route("file")] + public IActionResult GetFile(CancellationToken cancel) + { + return this.Ok(new QueryResponse + { + Message = this._fileProvider.Name() + }); + } + } + + public class QueryResponse + { + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/FileProvider.cs b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/FileProvider.cs new file mode 100644 index 00000000..24f0e1f8 --- /dev/null +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/FileProvider.cs @@ -0,0 +1,12 @@ +using System.IO; + +namespace Lab.Test.WebApi.Net5 +{ + public class FileProvider:IFileProvider + { + public string Name() + { + return nameof(FileProvider); + } + } +} \ No newline at end of file diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/IFileProvider.cs b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/IFileProvider.cs new file mode 100644 index 00000000..929301aa --- /dev/null +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/IFileProvider.cs @@ -0,0 +1,7 @@ +namespace Lab.Test.WebApi.Net5 +{ + public interface IFileProvider + { + string Name(); + } +} \ No newline at end of file diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Lab.Test.WebApi.Net5.csproj b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Lab.Test.WebApi.Net5.csproj new file mode 100644 index 00000000..39eb5ab3 --- /dev/null +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Lab.Test.WebApi.Net5.csproj @@ -0,0 +1,11 @@ + + + + net5.0 + + + + + + + diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Program.cs b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Program.cs new file mode 100644 index 00000000..3df6d830 --- /dev/null +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Program.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Lab.Test.WebApi.Net5 +{ + public class Program + { + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + } + + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + } +} \ No newline at end of file diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Properties/launchSettings.json b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Properties/launchSettings.json new file mode 100644 index 00000000..acf4a732 --- /dev/null +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:20722", + "sslPort": 44330 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Lab.Test.WebApi.Net5": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Startup.cs b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Startup.cs new file mode 100644 index 00000000..f39a8829 --- /dev/null +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Startup.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.OpenApi.Models; + +namespace Lab.Test.WebApi.Net5 +{ + public class Startup + { + public IConfiguration Configuration { get; } + + public Startup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Lab.Test.WebApi.Net5 v1")); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Lab.Test.WebApi.Net5", Version = "v1" }); + }); + + services.AddScoped(p => new FileProvider()); + services.AddScoped(p => (IFileProvider)p.GetService()); + } + } +} \ No newline at end of file diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/appsettings.Development.json b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/appsettings.Development.json new file mode 100644 index 00000000..8983e0fc --- /dev/null +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/appsettings.json b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/appsettings.json new file mode 100644 index 00000000..d9d9a9bf --- /dev/null +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.sln b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.sln new file mode 100644 index 00000000..09cca1af --- /dev/null +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Test.WebApi.Net5", "Lab.Test.WebApi.Net5\Lab.Test.WebApi.Net5.csproj", "{B97FA43E-B196-4800-9AF6-6F5F7B412D1D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Test.WebApi.Net5.TestProject", "Lab.Test.WebApi.Net5.TestProject\Lab.Test.WebApi.Net5.TestProject.csproj", "{B0E91B25-C99E-44D7-9C70-27DF6BF0DBEC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B97FA43E-B196-4800-9AF6-6F5F7B412D1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B97FA43E-B196-4800-9AF6-6F5F7B412D1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B97FA43E-B196-4800-9AF6-6F5F7B412D1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B97FA43E-B196-4800-9AF6-6F5F7B412D1D}.Release|Any CPU.Build.0 = Release|Any CPU + {B0E91B25-C99E-44D7-9C70-27DF6BF0DBEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0E91B25-C99E-44D7-9C70-27DF6BF0DBEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0E91B25-C99E-44D7-9C70-27DF6BF0DBEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0E91B25-C99E-44D7-9C70-27DF6BF0DBEC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From ab9dffc0dc6761c7c59e59b8014aa9302da93783 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 23 Oct 2021 15:47:58 +0800 Subject: [PATCH 120/424] refactor --- .../SurveyWebApplicationFactory.cs | 8 ++++---- .../Controllers/DemoController.cs | 18 ++---------------- .../Lab.Test.WebApi.Net5.csproj | 1 + .../ServiceModels/QueryResponse.cs | 7 +++++++ 4 files changed, 14 insertions(+), 20 deletions(-) create mode 100644 WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/ServiceModels/QueryResponse.cs diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/SurveyWebApplicationFactory.cs b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/SurveyWebApplicationFactory.cs index bc1a2542..05c45143 100644 --- a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/SurveyWebApplicationFactory.cs +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5.TestProject/SurveyWebApplicationFactory.cs @@ -12,7 +12,7 @@ public void CustomTestServer() { var server = new CustomTestServer(); var httpClient = server.CreateClient(); - var url = "demo/file"; + var url = "demo"; var response = httpClient.GetAsync(url).Result; var result = response.Content.ReadAsStringAsync().Result; Console.WriteLine(result); @@ -22,9 +22,9 @@ public void CustomTestServer() public void WebApplicationFactory基本用法() { var server = new WebApplicationFactory(); - var httpClient = server.CreateClient(); - var url = "demo/file"; - var response = httpClient.GetAsync(url).Result; + var client = server.CreateClient(); + var url = "demo"; + var response = client.GetAsync(url).Result; var result = response.Content.ReadAsStringAsync().Result; Console.WriteLine(result); } diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Controllers/DemoController.cs b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Controllers/DemoController.cs index afa35b7b..f28586a7 100644 --- a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Controllers/DemoController.cs +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Controllers/DemoController.cs @@ -1,4 +1,5 @@ using System.Threading; +using Lab.Test.WebApi.Net5.ServiceModels; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -21,17 +22,7 @@ public DemoController(ILogger logger, [HttpGet] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(QueryResponse))] - public IActionResult Get(CancellationToken cancel) - { - return this.Ok(new QueryResponse - { - Message = "Hello" - }); - } - [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(QueryResponse))] - [Route("file")] - public IActionResult GetFile(CancellationToken cancel) + public IActionResult Get(CancellationToken cancel = default) { return this.Ok(new QueryResponse { @@ -39,9 +30,4 @@ public IActionResult GetFile(CancellationToken cancel) }); } } - - public class QueryResponse - { - public string Message { get; set; } - } } \ No newline at end of file diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Lab.Test.WebApi.Net5.csproj b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Lab.Test.WebApi.Net5.csproj index 39eb5ab3..a3a18f0e 100644 --- a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Lab.Test.WebApi.Net5.csproj +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/Lab.Test.WebApi.Net5.csproj @@ -2,6 +2,7 @@ net5.0 + enable diff --git a/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/ServiceModels/QueryResponse.cs b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/ServiceModels/QueryResponse.cs new file mode 100644 index 00000000..9d6a0de6 --- /dev/null +++ b/WebAPI/Lab.Test.WebApi/Lab.Test.WebApi.Net5/ServiceModels/QueryResponse.cs @@ -0,0 +1,7 @@ +namespace Lab.Test.WebApi.Net5.ServiceModels +{ + public class QueryResponse + { + public string Message { get; set; } + } +} \ No newline at end of file From f331ac519493f3b1461d44e7723ee9eba3b40020 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 29 Nov 2021 15:26:52 +0800 Subject: [PATCH 121/424] first add --- .../GracefulShutdownService.cs | 47 ++++++++++++++++ .../GracefulShutdownService_Fail.cs | 40 ++++++++++++++ .../Lab.GracefulShutdown.Net6.csproj | 13 +++++ .../Lab.GracefulShutdown.Net6/Program.cs | 53 +++++++++++++++++++ .../Properties/launchSettings.json | 10 ++++ .../Lab.GracefulShutdown.sln | 16 ++++++ 6 files changed, 179 insertions(+) create mode 100644 Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService.cs create mode 100644 Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService_Fail.cs create mode 100644 Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Lab.GracefulShutdown.Net6.csproj create mode 100644 Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Program.cs create mode 100644 Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Properties/launchSettings.json create mode 100644 Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.sln diff --git a/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService.cs b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService.cs new file mode 100644 index 00000000..0b5a46eb --- /dev/null +++ b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService.cs @@ -0,0 +1,47 @@ +using Microsoft.Extensions.Hosting; + +namespace Lab.GracefulShutdown.Net6; + +internal class GracefulShutdownService : IHostedService +{ + private readonly IHostApplicationLifetime _appLifetime; + private Task _backgroundTask; + private bool _stop; + + public GracefulShutdownService(IHostApplicationLifetime appLifetime) + { + this._appLifetime = appLifetime; + } + + public Task StartAsync(CancellationToken cancel) + { + Console.WriteLine($"{DateTime.Now} 服務啟動中..."); + + this._backgroundTask = Task.Run(async () => { await this.ExecuteAsync(cancel); }, cancel); + + return Task.CompletedTask; + } + + public async Task StopAsync(CancellationToken cancel) + { + Console.WriteLine($"{DateTime.Now} 服務停止中..."); + + this._stop = true; + await this._backgroundTask; + + Console.WriteLine($"{DateTime.Now} 服務已停止"); + } + + private async Task ExecuteAsync(CancellationToken cancel) + { + Console.WriteLine($"{DateTime.Now} 服務已啟動!"); + + while (!this._stop) + { + Console.WriteLine($"{DateTime.Now} 服務運行中..."); + await Task.Delay(TimeSpan.FromSeconds(1), cancel); + } + + Console.WriteLine($"{DateTime.Now} 服務已完美的停止(Graceful Shutdown)"); + } +} \ No newline at end of file diff --git a/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService_Fail.cs b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService_Fail.cs new file mode 100644 index 00000000..38004cce --- /dev/null +++ b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService_Fail.cs @@ -0,0 +1,40 @@ +using Microsoft.Extensions.Hosting; + +namespace Lab.GracefulShutdown.Net6; + +internal class GracefulShutdownService_Fail : IHostedService +{ + private readonly IHostApplicationLifetime _appLifetime; + private bool _stop; + + public GracefulShutdownService_Fail(IHostApplicationLifetime appLifetime) + { + this._appLifetime = appLifetime; + } + + public async Task StartAsync(CancellationToken cancel) + { + Console.WriteLine($"{DateTime.Now} 服務啟動中..."); + await this.ExecuteAsync(cancel); + } + + public Task StopAsync(CancellationToken cancel) + { + this._stop = true; + Console.WriteLine("服務關閉"); + return Task.CompletedTask; + } + + private async Task ExecuteAsync(CancellationToken cancel) + { + Console.WriteLine($"{DateTime.Now} 服務已啟動!"); + + while (!this._stop) + { + Console.WriteLine($"{DateTime.Now} 服務運行中..."); + await Task.Delay(TimeSpan.FromSeconds(1), cancel); + } + + Console.WriteLine($"{DateTime.Now} 服務已完美的停止(Graceful Shutdown)"); + } +} \ No newline at end of file diff --git a/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Lab.GracefulShutdown.Net6.csproj b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Lab.GracefulShutdown.Net6.csproj new file mode 100644 index 00000000..1a94648c --- /dev/null +++ b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Lab.GracefulShutdown.Net6.csproj @@ -0,0 +1,13 @@ + + + + Exe + net6.0 + enable + enable + + + + + + diff --git a/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Program.cs b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Program.cs new file mode 100644 index 00000000..bec06acf --- /dev/null +++ b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Program.cs @@ -0,0 +1,53 @@ +using Lab.GracefulShutdown.Net6; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System.Runtime.Loader; + +var tcs = new TaskCompletionSource(); +var sigintReceived = false; + +Console.WriteLine("等待以下訊號 SIGINT/SIGTERM"); + +Console.CancelKeyPress += (sender, e) => +{ + e.Cancel = true; + Console.WriteLine("已接收 SIGINT (Ctrl+C)"); + tcs.SetResult(); + sigintReceived = true; +}; + +AssemblyLoadContext.Default.Unloading += ctx => +{ + if (!sigintReceived) + { + Console.WriteLine("已接收 SIGTERM"); + tcs.SetResult(); + } + else + { + Console.WriteLine("@AssemblyLoadContext.Default.Unloading,已處理 SIGINT,忽略 SIGTERM"); + } +}; + +AppDomain.CurrentDomain.ProcessExit += (sender, e) => +{ + if (!sigintReceived) + { + Console.WriteLine("已接收 SIGTERM"); + tcs.SetResult(); + } + else + { + Console.WriteLine("@AppDomain.CurrentDomain.ProcessExit,已處理 SIGINT,忽略 SIGTERM"); + } +}; + +await Host.CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + // services.AddHostedService(); + services.AddHostedService(); + }) + .RunConsoleAsync(); +Console.WriteLine("下次再來唷~"); + diff --git a/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Properties/launchSettings.json b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Properties/launchSettings.json new file mode 100644 index 00000000..b665f30e --- /dev/null +++ b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Lab.GracefulShutdown.Net6": { + "commandName": "Project", + "environmentVariables": { + } + } + } +} diff --git a/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.sln b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.sln new file mode 100644 index 00000000..d979b64b --- /dev/null +++ b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.GracefulShutdown.Net6", "Lab.GracefulShutdown.Net6\Lab.GracefulShutdown.Net6.csproj", "{D21B2207-2D80-49B2-94A1-24234DBD9B8D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D21B2207-2D80-49B2-94A1-24234DBD9B8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D21B2207-2D80-49B2-94A1-24234DBD9B8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D21B2207-2D80-49B2-94A1-24234DBD9B8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D21B2207-2D80-49B2-94A1-24234DBD9B8D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From 637a997a3a24114adc451ac51f4c1872a5684ebe Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 8 Dec 2021 19:28:19 +0800 Subject: [PATCH 122/424] feat: add project --- Configuration/Lab.Environment/.gitignore | 1 + .../Lab.Environment.ConsoleApp.NET48.csproj | 55 +++++++++++++++++++ .../Program.cs | 19 +++++++ .../Properties/AssemblyInfo.cs | 35 ++++++++++++ .../Lab.Environment.ConsoleApp.NET6.csproj | 10 ++++ .../Program.cs | 5 ++ .../Properties/launchSettings.json | 10 ++++ .../Lab.Environment/Lab.Environment.ps1 | 2 + .../Lab.Environment/Lab.Environment.sln | 22 ++++++++ Configuration/Lab.Environment/Taskfile.yml | 26 +++++++++ Configuration/Lab.Environment/global.json | 7 +++ 11 files changed, 192 insertions(+) create mode 100644 Configuration/Lab.Environment/.gitignore create mode 100644 Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET48/Lab.Environment.ConsoleApp.NET48.csproj create mode 100644 Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET48/Program.cs create mode 100644 Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET48/Properties/AssemblyInfo.cs create mode 100644 Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET6/Lab.Environment.ConsoleApp.NET6.csproj create mode 100644 Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET6/Program.cs create mode 100644 Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET6/Properties/launchSettings.json create mode 100644 Configuration/Lab.Environment/Lab.Environment.ps1 create mode 100644 Configuration/Lab.Environment/Lab.Environment.sln create mode 100644 Configuration/Lab.Environment/Taskfile.yml create mode 100644 Configuration/Lab.Environment/global.json diff --git a/Configuration/Lab.Environment/.gitignore b/Configuration/Lab.Environment/.gitignore new file mode 100644 index 00000000..c027a961 --- /dev/null +++ b/Configuration/Lab.Environment/.gitignore @@ -0,0 +1 @@ +secrets.env \ No newline at end of file diff --git a/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET48/Lab.Environment.ConsoleApp.NET48.csproj b/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET48/Lab.Environment.ConsoleApp.NET48.csproj new file mode 100644 index 00000000..099fd9b8 --- /dev/null +++ b/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET48/Lab.Environment.ConsoleApp.NET48.csproj @@ -0,0 +1,55 @@ + + + + + Debug + AnyCPU + {BB8D3ED0-BF9F-4910-9231-717DD0577FB5} + Exe + Properties + Lab.Environment.ConsoleApp.NET48 + Lab.Environment.ConsoleApp.NET48 + v4.8 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + diff --git a/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET48/Program.cs b/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET48/Program.cs new file mode 100644 index 00000000..e6a16b3a --- /dev/null +++ b/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET48/Program.cs @@ -0,0 +1,19 @@ + +using System; + +namespace Lab.Environment.ConsoleApp.NET48 +{ + internal class Program + { + public static void Main(string[] args) + { + var appEnv = System.Environment.GetEnvironmentVariable("APP_ENV"); + var scoopPath = System.Environment.GetEnvironmentVariable("scoop"); + + if (string.IsNullOrWhiteSpace(appEnv) == false) + { + Console.WriteLine(appEnv); + } + } + } +} \ No newline at end of file diff --git a/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET48/Properties/AssemblyInfo.cs b/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET48/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..814f8719 --- /dev/null +++ b/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET48/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Lab.Environment.ConsoleApp.NET48")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Lab.Environment.ConsoleApp.NET48")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("BB8D3ED0-BF9F-4910-9231-717DD0577FB5")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET6/Lab.Environment.ConsoleApp.NET6.csproj b/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET6/Lab.Environment.ConsoleApp.NET6.csproj new file mode 100644 index 00000000..b9de0634 --- /dev/null +++ b/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET6/Lab.Environment.ConsoleApp.NET6.csproj @@ -0,0 +1,10 @@ + + + + Exe + net6.0 + enable + enable + + + diff --git a/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET6/Program.cs b/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET6/Program.cs new file mode 100644 index 00000000..f6ff2edf --- /dev/null +++ b/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET6/Program.cs @@ -0,0 +1,5 @@ +using System; + +var appEnv = Environment.GetEnvironmentVariable("APP_ENV"); +var scoopPath = Environment.GetEnvironmentVariable("scoop"); +Console.ReadKey(); \ No newline at end of file diff --git a/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET6/Properties/launchSettings.json b/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET6/Properties/launchSettings.json new file mode 100644 index 00000000..e1fbded0 --- /dev/null +++ b/Configuration/Lab.Environment/Lab.Environment.ConsoleApp.NET6/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Lab.Environment.ConsoleApp.NET6": { + "commandName": "Project", + "environmentVariables": { + } + } + } +} diff --git a/Configuration/Lab.Environment/Lab.Environment.ps1 b/Configuration/Lab.Environment/Lab.Environment.ps1 new file mode 100644 index 00000000..ca7050f6 --- /dev/null +++ b/Configuration/Lab.Environment/Lab.Environment.ps1 @@ -0,0 +1,2 @@ +$Env:APP:ENV = "QA" +./Lab.Environment.sln \ No newline at end of file diff --git a/Configuration/Lab.Environment/Lab.Environment.sln b/Configuration/Lab.Environment/Lab.Environment.sln new file mode 100644 index 00000000..9623d29e --- /dev/null +++ b/Configuration/Lab.Environment/Lab.Environment.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Environment.ConsoleApp.NET48", "Lab.Environment.ConsoleApp.NET48\Lab.Environment.ConsoleApp.NET48.csproj", "{BB8D3ED0-BF9F-4910-9231-717DD0577FB5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Environment.ConsoleApp.NET6", "Lab.Environment.ConsoleApp.NET6\Lab.Environment.ConsoleApp.NET6.csproj", "{5033EB4D-63B9-4F17-9FDD-1A81E174F3C9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BB8D3ED0-BF9F-4910-9231-717DD0577FB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB8D3ED0-BF9F-4910-9231-717DD0577FB5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB8D3ED0-BF9F-4910-9231-717DD0577FB5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB8D3ED0-BF9F-4910-9231-717DD0577FB5}.Release|Any CPU.Build.0 = Release|Any CPU + {5033EB4D-63B9-4F17-9FDD-1A81E174F3C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5033EB4D-63B9-4F17-9FDD-1A81E174F3C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5033EB4D-63B9-4F17-9FDD-1A81E174F3C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5033EB4D-63B9-4F17-9FDD-1A81E174F3C9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Configuration/Lab.Environment/Taskfile.yml b/Configuration/Lab.Environment/Taskfile.yml new file mode 100644 index 00000000..f4feaebd --- /dev/null +++ b/Configuration/Lab.Environment/Taskfile.yml @@ -0,0 +1,26 @@ +version: "3" +env: + GREETING: Hey, there! +dotenv: ["secrets.env"] +vars: + PATH: "/mnt/c/Users/Yao Chang Yu/scoop/apps/Rider-EAP/current/IDE/bin/" + #PATH: "C:\Users\Yao Chang Yu\scoop\apps\Rider-EAP\2021.3-EAP9-213.5744.160\IDE\bin\" +tasks: + print-os: + cmds: + - echo '{{OS}} {{ARCH}}' + - echo '{{if eq OS "windows"}}windows-command{{else}}unix-command{{end}}' + # This will be path/to/file on Unix but path\to\file on Windows + - echo '{{fromSlash "path/to/file"}}' + - echo '{{fromSlash "/mnt/c/Users/Yao Chang Yu/scoop/apps/Rider-EAP/current/IDE/bin/"}}' + greet: + desc: greet + cmds: + - echo $GREETING + rider: + desc: Rider + dir: "/mnt/c/Users/Yao Chang Yu/scoop/apps/Rider-EAP/current/IDE/bin/" + cmds: + - rider64.exe + env: + Url: http://localhost:9527 \ No newline at end of file diff --git a/Configuration/Lab.Environment/global.json b/Configuration/Lab.Environment/global.json new file mode 100644 index 00000000..f443bd42 --- /dev/null +++ b/Configuration/Lab.Environment/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "6.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file From 03084c5c99345ee57b079c678bfd813303d0e9c2 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 8 Dec 2021 20:01:16 +0800 Subject: [PATCH 123/424] refactor --- Configuration/Lab.Environment/Lab.Environment.ps1 | 2 +- Configuration/Lab.Environment/Taskfile.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Configuration/Lab.Environment/Lab.Environment.ps1 b/Configuration/Lab.Environment/Lab.Environment.ps1 index ca7050f6..1fd4ffcf 100644 --- a/Configuration/Lab.Environment/Lab.Environment.ps1 +++ b/Configuration/Lab.Environment/Lab.Environment.ps1 @@ -1,2 +1,2 @@ -$Env:APP:ENV = "QA" +$Env:APP_ENV = "QA" ./Lab.Environment.sln \ No newline at end of file diff --git a/Configuration/Lab.Environment/Taskfile.yml b/Configuration/Lab.Environment/Taskfile.yml index f4feaebd..b97d93b8 100644 --- a/Configuration/Lab.Environment/Taskfile.yml +++ b/Configuration/Lab.Environment/Taskfile.yml @@ -20,7 +20,7 @@ tasks: rider: desc: Rider dir: "/mnt/c/Users/Yao Chang Yu/scoop/apps/Rider-EAP/current/IDE/bin/" - cmds: + cmds: - rider64.exe env: Url: http://localhost:9527 \ No newline at end of file From ee8f6fa5107c004627f154c89003029c424fde55 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 3 Jan 2022 22:46:02 +0800 Subject: [PATCH 124/424] feat: add specflow project --- Test/Lab.AllureReport/.gitignore | 365 ++++++++++++++++++ Test/Lab.AllureReport/Lab.AllureReport.sln | 16 + .../Lab.AllureReport4Specflow/Calculation.cs | 9 + .../Lab.AllureReport4Specflow.csproj | 19 + .../Lab.AllureReport4Specflow/specflow.json | 10 + ...0\250\210\347\256\227\346\251\237.feature" | 9 + ...50\250\210\347\256\227\346\251\237Step.cs" | 37 ++ 7 files changed, 465 insertions(+) create mode 100644 Test/Lab.AllureReport/.gitignore create mode 100644 Test/Lab.AllureReport/Lab.AllureReport.sln create mode 100644 Test/Lab.AllureReport/Lab.AllureReport4Specflow/Calculation.cs create mode 100644 Test/Lab.AllureReport/Lab.AllureReport4Specflow/Lab.AllureReport4Specflow.csproj create mode 100644 Test/Lab.AllureReport/Lab.AllureReport4Specflow/specflow.json create mode 100644 "Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237.feature" create mode 100644 "Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237Step.cs" diff --git a/Test/Lab.AllureReport/.gitignore b/Test/Lab.AllureReport/.gitignore new file mode 100644 index 00000000..a33719ce --- /dev/null +++ b/Test/Lab.AllureReport/.gitignore @@ -0,0 +1,365 @@ +### VisualStudio template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +*.feature.cs diff --git a/Test/Lab.AllureReport/Lab.AllureReport.sln b/Test/Lab.AllureReport/Lab.AllureReport.sln new file mode 100644 index 00000000..625a34bd --- /dev/null +++ b/Test/Lab.AllureReport/Lab.AllureReport.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AllureReport4Specflow", "Lab.AllureReport4Specflow\Lab.AllureReport4Specflow.csproj", "{24AC2522-D7B4-4854-BE1E-18389125605F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {24AC2522-D7B4-4854-BE1E-18389125605F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24AC2522-D7B4-4854-BE1E-18389125605F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24AC2522-D7B4-4854-BE1E-18389125605F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24AC2522-D7B4-4854-BE1E-18389125605F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Calculation.cs b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Calculation.cs new file mode 100644 index 00000000..4880a559 --- /dev/null +++ b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Calculation.cs @@ -0,0 +1,9 @@ +namespace Lab.AllureReport4Specflow; + +public class Calculation +{ + public double Add(double firstNumber, double secondNumber) + { + return firstNumber + secondNumber; + } +} \ No newline at end of file diff --git a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Lab.AllureReport4Specflow.csproj b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Lab.AllureReport4Specflow.csproj new file mode 100644 index 00000000..1c6c2cf4 --- /dev/null +++ b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Lab.AllureReport4Specflow.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + diff --git a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/specflow.json b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/specflow.json new file mode 100644 index 00000000..06565c4c --- /dev/null +++ b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/specflow.json @@ -0,0 +1,10 @@ +{ + "language": { + "feature": "en-US" + }, + "stepAssemblies": [ + { + "assembly": "Allure.SpecFlowPlugin" + } + ] +} \ No newline at end of file diff --git "a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237.feature" "b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237.feature" new file mode 100644 index 00000000..9c952f95 --- /dev/null +++ "b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237.feature" @@ -0,0 +1,9 @@ +Feature: 計算機 + Simple calculator for adding two numbers + +@mytag +Scenario: Add two numbers + Given the first number is 50 + And the second number is 70 + When the two numbers are added + Then the result should be 120 \ No newline at end of file diff --git "a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237Step.cs" "b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237Step.cs" new file mode 100644 index 00000000..e1ff71df --- /dev/null +++ "b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237Step.cs" @@ -0,0 +1,37 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TechTalk.SpecFlow; + +namespace Lab.AllureReport4Specflow; + +[Binding] +public class 計算機Step : Steps +{ + [Given(@"the first number is (.*)")] + public void GivenTheFirstNumberIs(double firstNumber) + { + this.ScenarioContext.Set(firstNumber, "firstNumber"); + } + + [Given(@"the second number is (.*)")] + public void GivenTheSecondNumberIs(double secondNumber) + { + this.ScenarioContext.Set(secondNumber, "secondNumber"); + } + + [Then(@"the result should be (.*)")] + public void ThenTheResultShouldBe(int expected) + { + var actual = this.ScenarioContext.Get("actual"); + Assert.AreEqual(expected, actual); + } + + [When(@"the two numbers are added")] + public void WhenTheTwoNumbersAreAdded() + { + var firstNumber = this.ScenarioContext.Get("firstNumber"); + var secondNumber = this.ScenarioContext.Get("secondNumber"); + var calculation = new Calculation(); + var actual = calculation.Add(firstNumber, secondNumber); + this.ScenarioContext.Set(actual, "actual"); + } +} \ No newline at end of file From bf3993773050e7147e16fbff8b43ee6cb2f618cf Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 3 Jan 2022 23:02:48 +0800 Subject: [PATCH 125/424] refactor --- .../Lab.AllureReport4Specflow.csproj | 12 ++++----- ...0\250\210\347\256\227\346\251\237.feature" | 26 ++++++++++++++----- ...50\250\210\347\256\227\346\251\237Step.cs" | 16 ++++++------ 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Lab.AllureReport4Specflow.csproj b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Lab.AllureReport4Specflow.csproj index 1c6c2cf4..3a6db6c1 100644 --- a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Lab.AllureReport4Specflow.csproj +++ b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Lab.AllureReport4Specflow.csproj @@ -8,12 +8,12 @@ - - - - - - + + + + + + diff --git "a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237.feature" "b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237.feature" index 9c952f95..4c4f7712 100644 --- "a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237.feature" +++ "b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237.feature" @@ -1,9 +1,21 @@ Feature: 計算機 - Simple calculator for adding two numbers +Simple calculator for adding two numbers -@mytag -Scenario: Add two numbers - Given the first number is 50 - And the second number is 70 - When the two numbers are added - Then the result should be 120 \ No newline at end of file + @mytag + Scenario: 相加兩個數字 + Given 第一個數字為 50 + And 第二個數字為 70 + When 兩個數字相加 + Then 結果應該為 120 + + Scenario Outline: 相加兩個數字(Examples) + Given 第一個數字為 + And 第二個數字為 + When 兩個數字相加 + Then 結果應該為 + + Examples: + | First | Second | Result | + | 50 | 70 | 120 | + | 30 | 40 | 70 | + | 60 | 30 | 90 | \ No newline at end of file diff --git "a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237Step.cs" "b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237Step.cs" index e1ff71df..10ba428f 100644 --- "a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237Step.cs" +++ "b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/\350\250\210\347\256\227\346\251\237Step.cs" @@ -6,27 +6,27 @@ namespace Lab.AllureReport4Specflow; [Binding] public class 計算機Step : Steps { - [Given(@"the first number is (.*)")] - public void GivenTheFirstNumberIs(double firstNumber) + [Given(@"第一個數字為 (.*)")] + public void Given第一個數字為(double firstNumber) { this.ScenarioContext.Set(firstNumber, "firstNumber"); } - [Given(@"the second number is (.*)")] - public void GivenTheSecondNumberIs(double secondNumber) + [Given(@"第二個數字為 (.*)")] + public void Given第二個數字為(double secondNumber) { this.ScenarioContext.Set(secondNumber, "secondNumber"); } - [Then(@"the result should be (.*)")] - public void ThenTheResultShouldBe(int expected) + [Then(@"結果應該為 (.*)")] + public void Then結果應該為(double expected) { var actual = this.ScenarioContext.Get("actual"); Assert.AreEqual(expected, actual); } - [When(@"the two numbers are added")] - public void WhenTheTwoNumbersAreAdded() + [When(@"兩個數字相加")] + public void When兩個數字相加() { var firstNumber = this.ScenarioContext.Get("firstNumber"); var secondNumber = this.ScenarioContext.Get("secondNumber"); From d15093d54276b688d41745ba68ea771c82d9da71 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 4 Jan 2022 09:52:14 +0800 Subject: [PATCH 126/424] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20NUnit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Lab.AllureReport4Specflow.csproj | 13 +++++----- .../Lab.AllureReport4Specflow/Tests.cs | 26 +++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 Test/Lab.AllureReport/Lab.AllureReport4Specflow/Tests.cs diff --git a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Lab.AllureReport4Specflow.csproj b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Lab.AllureReport4Specflow.csproj index 3a6db6c1..585d3b0a 100644 --- a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Lab.AllureReport4Specflow.csproj +++ b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Lab.AllureReport4Specflow.csproj @@ -8,12 +8,13 @@ - - - - - - + + + + + + + diff --git a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Tests.cs b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Tests.cs new file mode 100644 index 00000000..5bfbd532 --- /dev/null +++ b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/Tests.cs @@ -0,0 +1,26 @@ +using Allure.Commons; +using NUnit.Allure.Attributes; +using NUnit.Allure.Core; +using NUnit.Framework; +using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert; + +namespace Lab.AllureReport4Specflow; + +[TestFixture] +[AllureNUnit] +[AllureSubSuite("Example")] +[AllureSeverity(SeverityLevel.critical)] +public class Tests +{ + [Test] + [AllureTag("NUnit","Debug")] + [AllureIssue("GitHub#1", "https://github.com/unickq/allure-nunit")] + [AllureFeature("Core")] + [TestCase(20, 50, 70)] + public void 相加兩個數字(double firstNumber, double secondNumber, double expected) + { + var calculation = new Calculation(); + var actual = calculation.Add(firstNumber, secondNumber); + Assert.AreEqual(expected, actual); + } +} \ No newline at end of file From 8f2ab0821ce18faa8a8a5097a3b6e7871e2debbd Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 6 Jan 2022 02:19:18 +0800 Subject: [PATCH 127/424] feat: add new ef core 6 project --- .../Lab.DynamoDB.SurveyTest.csproj | 18 +++ .../Lab.DynamoDB.SurveyTest/UnitTest1.cs | 138 ++++++++++++++++++ .../Lab.DynamoDB.SurveyTest/appsettings.json | 13 ++ DynamoDB/Lab.DynamoDB/Lab.DynamoDB.sln | 16 ++ DynamoDB/Lab.DynamoDB/docker-compose.yml | 16 ++ DynamoDB/Lab.DynamoDB/global.json | 7 + .../Lab.EFCoreBulk.UnitTest.csproj | 26 ++++ .../Lab.EFCoreBulk.UnitTest/MsTestHook.cs | 35 +++++ .../TestInstanceManager.cs | 34 +++++ .../Lab.EFCoreBulk.UnitTest/UnitTest1.cs | 32 ++++ ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.sln | 22 +++ .../AppDependencyInjectionExtensions.cs | 49 +++++++ .../Lab.EFCoreBulk/AppEnvironmentOption.cs | 36 +++++ .../Lab.EFCoreBulk/EntityModel/Employee.cs | 30 ++++ .../EntityModel/EmployeeDbContext.cs | 93 ++++++++++++ .../Lab.EFCoreBulk/EntityModel/Identity.cs | 33 +++++ .../EntityModel/OrderHistory.cs | 30 ++++ .../Lab.EFCoreBulk/EnvironmentAssistant.cs | 15 ++ .../Lab.EFCoreBulk/Lab.EFCoreBulk.csproj | 14 ++ ORM/EFCore/Lab.EFCoreBulk/docker-compose.yml | 10 ++ .../Lab.AllureReport4Specflow/.runsettings | 9 ++ .../Lab.AllureReport4Specflow/UnitTests.cs | 16 ++ 22 files changed, 692 insertions(+) create mode 100644 DynamoDB/Lab.DynamoDB/Lab.DynamoDB.SurveyTest/Lab.DynamoDB.SurveyTest.csproj create mode 100644 DynamoDB/Lab.DynamoDB/Lab.DynamoDB.SurveyTest/UnitTest1.cs create mode 100644 DynamoDB/Lab.DynamoDB/Lab.DynamoDB.SurveyTest/appsettings.json create mode 100644 DynamoDB/Lab.DynamoDB/Lab.DynamoDB.sln create mode 100644 DynamoDB/Lab.DynamoDB/docker-compose.yml create mode 100644 DynamoDB/Lab.DynamoDB/global.json create mode 100644 ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/Lab.EFCoreBulk.UnitTest.csproj create mode 100644 ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/MsTestHook.cs create mode 100644 ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/TestInstanceManager.cs create mode 100644 ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs create mode 100644 ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.sln create mode 100644 ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppDependencyInjectionExtensions.cs create mode 100644 ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppEnvironmentOption.cs create mode 100644 ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/Employee.cs create mode 100644 ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/EmployeeDbContext.cs create mode 100644 ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/Identity.cs create mode 100644 ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/OrderHistory.cs create mode 100644 ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EnvironmentAssistant.cs create mode 100644 ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/Lab.EFCoreBulk.csproj create mode 100644 ORM/EFCore/Lab.EFCoreBulk/docker-compose.yml create mode 100644 Test/Lab.AllureReport/Lab.AllureReport4Specflow/.runsettings create mode 100644 Test/Lab.AllureReport/Lab.AllureReport4Specflow/UnitTests.cs diff --git a/DynamoDB/Lab.DynamoDB/Lab.DynamoDB.SurveyTest/Lab.DynamoDB.SurveyTest.csproj b/DynamoDB/Lab.DynamoDB/Lab.DynamoDB.SurveyTest/Lab.DynamoDB.SurveyTest.csproj new file mode 100644 index 00000000..efa12ca2 --- /dev/null +++ b/DynamoDB/Lab.DynamoDB/Lab.DynamoDB.SurveyTest/Lab.DynamoDB.SurveyTest.csproj @@ -0,0 +1,18 @@ + + + + net5.0 + enable + + false + + + + + + + + + + + diff --git a/DynamoDB/Lab.DynamoDB/Lab.DynamoDB.SurveyTest/UnitTest1.cs b/DynamoDB/Lab.DynamoDB/Lab.DynamoDB.SurveyTest/UnitTest1.cs new file mode 100644 index 00000000..55041dc2 --- /dev/null +++ b/DynamoDB/Lab.DynamoDB/Lab.DynamoDB.SurveyTest/UnitTest1.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void Survey_CreateTable() + { + var client = CreateAmazonDynamoDbClient(); + var request = new CreateTableRequest + { + AttributeDefinitions = new List() + { + new AttributeDefinition + { + AttributeName = "Id", + AttributeType = "N" + }, + new AttributeDefinition + { + AttributeName = "DateTime", + AttributeType = "S" + } + }, + + KeySchema = new List + { + new KeySchemaElement + { + AttributeName = "Id", + KeyType = "HASH" //Partition key + }, + new KeySchemaElement + { + AttributeName = "DateTime", + KeyType = "RANGE" //Range key + } + } + }; + var response = client.CreateTableAsync(request).Result; + } + + private static void CreateExampleTable(AmazonDynamoDBClient client, + string tableName, + CancellationToken cancel) + { + Console.WriteLine("\n*** Creating table ***"); + var request = new CreateTableRequest + { + AttributeDefinitions = new List() + { + new AttributeDefinition + { + AttributeName = "Id", + AttributeType = "N" + }, + new AttributeDefinition + { + AttributeName = "ReplyDateTime", + AttributeType = "N" + } + }, + KeySchema = new List + { + new KeySchemaElement + { + AttributeName = "Id", + KeyType = "HASH" //Partition key + }, + new KeySchemaElement + { + AttributeName = "ReplyDateTime", + KeyType = "RANGE" //Sort key + } + }, + ProvisionedThroughput = new ProvisionedThroughput + { + ReadCapacityUnits = 5, + WriteCapacityUnits = 6 + }, + TableName = tableName + }; + + var response = client.CreateTableAsync(request, cancel).Result; + + var tableDescription = response.TableDescription; + Console.WriteLine("{1}: {0} \t ReadsPerSec: {2} \t WritesPerSec: {3}", + tableDescription.TableStatus, + tableDescription.TableName, + tableDescription.ProvisionedThroughput.ReadCapacityUnits, + tableDescription.ProvisionedThroughput.WriteCapacityUnits); + + string status = tableDescription.TableStatus; + Console.WriteLine(tableName + " - " + status); + + WaitUntilTableReady(client, tableName,cancel); + } + private static void WaitUntilTableReady(AmazonDynamoDBClient client, string tableName,CancellationToken cancel) + { + string status = null; + // Let us wait until table is created. Call DescribeTable. + do + { + System.Threading.Thread.Sleep(5000); // Wait 5 seconds. + try + { + var res = client.DescribeTableAsync(new DescribeTableRequest + { + TableName = tableName + }, cancel).Result; + + Console.WriteLine("Table name: {0}, status: {1}", + res.Table.TableName, + res.Table.TableStatus); + status = res.Table.TableStatus; + } + catch (ResourceNotFoundException) + { + // DescribeTable is eventually consistent. So you might + // get resource not found. So we handle the potential exception. + } + } while (status != "ACTIVE"); + } + private static AmazonDynamoDBClient? CreateAmazonDynamoDbClient() + { + var clientConfig = new AmazonDynamoDBConfig + { + ServiceURL = "http://localhost:8000" + }; + var client = new AmazonDynamoDBClient(clientConfig); + return client; + } +} \ No newline at end of file diff --git a/DynamoDB/Lab.DynamoDB/Lab.DynamoDB.SurveyTest/appsettings.json b/DynamoDB/Lab.DynamoDB/Lab.DynamoDB.SurveyTest/appsettings.json new file mode 100644 index 00000000..31ebdc71 --- /dev/null +++ b/DynamoDB/Lab.DynamoDB/Lab.DynamoDB.SurveyTest/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "DynamoDb": { + "LocalMode": true, + "LocalServiceUrl": "http://localhost:8000" + } +} \ No newline at end of file diff --git a/DynamoDB/Lab.DynamoDB/Lab.DynamoDB.sln b/DynamoDB/Lab.DynamoDB/Lab.DynamoDB.sln new file mode 100644 index 00000000..e63b587d --- /dev/null +++ b/DynamoDB/Lab.DynamoDB/Lab.DynamoDB.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.DynamoDB.SurveyTest", "Lab.DynamoDB.SurveyTest\Lab.DynamoDB.SurveyTest.csproj", "{36FF7374-A6A2-436D-902D-8805336F3A38}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {36FF7374-A6A2-436D-902D-8805336F3A38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36FF7374-A6A2-436D-902D-8805336F3A38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36FF7374-A6A2-436D-902D-8805336F3A38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36FF7374-A6A2-436D-902D-8805336F3A38}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/DynamoDB/Lab.DynamoDB/docker-compose.yml b/DynamoDB/Lab.DynamoDB/docker-compose.yml new file mode 100644 index 00000000..f8874b72 --- /dev/null +++ b/DynamoDB/Lab.DynamoDB/docker-compose.yml @@ -0,0 +1,16 @@ +version: "3.8" + +services: + ddb: + image: amazon/dynamodb-local + command: ["-jar", "DynamoDBLocal.jar", "-inMemory", "-sharedDb"] + ports: + - 8000:8000 + ddb-admin: + image: aaronshaf/dynamodb-admin + environment: + - DYNAMO_ENDPOINT=http://ddb:8000 + ports: + - 8005:8001 + depends_on: + - ddb diff --git a/DynamoDB/Lab.DynamoDB/global.json b/DynamoDB/Lab.DynamoDB/global.json new file mode 100644 index 00000000..531745d4 --- /dev/null +++ b/DynamoDB/Lab.DynamoDB/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "3.1.100", + "rollForward": "latestMajor", + "allowPrerelease": false + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/Lab.EFCoreBulk.UnitTest.csproj b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/Lab.EFCoreBulk.UnitTest.csproj new file mode 100644 index 00000000..d29b78a7 --- /dev/null +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/Lab.EFCoreBulk.UnitTest.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + + + + + + diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/MsTestHook.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/MsTestHook.cs new file mode 100644 index 00000000..e14fc88d --- /dev/null +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/MsTestHook.cs @@ -0,0 +1,35 @@ +using Lab.EFCoreBulk.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.EFCoreBulk.UnitTest; + +[TestClass] +public class MsTestHook +{ + [AssemblyCleanup] + public static void Cleanup() + { + TestInstanceManager.SetTestEnvironmentVariable(); + var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); + if (db.Database.CanConnect()) + { + db.Database.EnsureDeleted(); + } + } + + [AssemblyInitialize] + public static void Setup(TestContext context) + { + TestInstanceManager.SetTestEnvironmentVariable(); + var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); + if (db.Database.CanConnect()) + { + db.Database.EnsureDeleted(); + } + + db.Database.EnsureCreated(); + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/TestInstanceManager.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/TestInstanceManager.cs new file mode 100644 index 00000000..7db399f1 --- /dev/null +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/TestInstanceManager.cs @@ -0,0 +1,34 @@ +using System; +using Lab.EFCoreBulk.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Lab.EFCoreBulk.UnitTest; + +internal class TestInstanceManager +{ + private static IServiceProvider _serviceProvider; + + public static IDbContextFactory EmployeeDbContextFactory => + _serviceProvider.GetService>(); + + static TestInstanceManager() + { + ConfigureTestServices(); + } + + public static void ConfigureTestServices() + { + var services = new ServiceCollection(); + services.AddAppEnvironment(); + services.AddEntityFramework(); + _serviceProvider = services.BuildServiceProvider(); + } + + public static void SetTestEnvironmentVariable() + { + var option = _serviceProvider.GetService(); + option.EmployeeDbConnectionString = + "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True"; + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs new file mode 100644 index 00000000..b2524ef0 --- /dev/null +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs @@ -0,0 +1,32 @@ +using System; +using Lab.EFCoreBulk.EntityModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.EFCoreBulk.UnitTest; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestMethod1() + { + var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); + var id = Guid.NewGuid(); + db.Employees.Add(new Employee + { + Id = id, + Age = 18, + CreateAt = DateTimeOffset.UtcNow, + Name = "yao", + CreateBy = "Sys", + // Identity = new Identity + // { + // Account = "yao", + // CreateAt = DateTimeOffset.UtcNow, + // CreateBy = "Sys", + // Password = "123456", + // }, + }); + db.SaveChanges(); + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.sln b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.sln new file mode 100644 index 00000000..57f01a38 --- /dev/null +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.EFCoreBulk", "Lab.EFCoreBulk\Lab.EFCoreBulk.csproj", "{3C9700D7-3563-4C7B-9EF4-A7EE512993DC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.EFCoreBulk.UnitTest", "Lab.EFCoreBulk.UnitTest\Lab.EFCoreBulk.UnitTest.csproj", "{73344F9D-E263-4EE9-8193-F23343731E56}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3C9700D7-3563-4C7B-9EF4-A7EE512993DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C9700D7-3563-4C7B-9EF4-A7EE512993DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C9700D7-3563-4C7B-9EF4-A7EE512993DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C9700D7-3563-4C7B-9EF4-A7EE512993DC}.Release|Any CPU.Build.0 = Release|Any CPU + {73344F9D-E263-4EE9-8193-F23343731E56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73344F9D-E263-4EE9-8193-F23343731E56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73344F9D-E263-4EE9-8193-F23343731E56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73344F9D-E263-4EE9-8193-F23343731E56}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppDependencyInjectionExtensions.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppDependencyInjectionExtensions.cs new file mode 100644 index 00000000..99b11430 --- /dev/null +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppDependencyInjectionExtensions.cs @@ -0,0 +1,49 @@ +using Lab.EFCoreBulk.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.EFCoreBulk; + +public static class AppDependencyInjectionExtensions +{ + public static void AddAppEnvironment(this IServiceCollection services) + { + services.AddLogging(); + services.AddSingleton(); + } + + public static void AddEntityFramework(this IServiceCollection services) + { + services.AddPooledDbContextFactory((provider, optionsBuilder) => + { + var option = provider.GetService(); + var connectionString = option.EmployeeDbConnectionString; + var loggerFactory = provider.GetService(); + optionsBuilder.UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + ; + }); + + ; + + // services.AddPooledDbContextFactory((provider, options) => + // { + // var option = provider.GetService(); + // var loggerFactory = provider.GetService(); + // options.UseNpgsql( + // option.MemberDbConnectionString, //只會呼叫一次 + // builder => + // builder.EnableRetryOnFailure( + // 10, + // TimeSpan.FromSeconds(30), + // new List { "57P01" })) + // + // // .UseLazyLoadingProxies() + // .UseSnakeCaseNamingConvention() + // .UseLoggerFactory(loggerFactory) + // ; + // }); + } + +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppEnvironmentOption.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppEnvironmentOption.cs new file mode 100644 index 00000000..3a8f49cd --- /dev/null +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppEnvironmentOption.cs @@ -0,0 +1,36 @@ +namespace Lab.EFCoreBulk; + +public class AppEnvironmentOption +{ + public string EmployeeDbConnectionString + { + get + { + if (string.IsNullOrWhiteSpace(this._memberDbConnectionString)) + { + this._memberDbConnectionString = + EnvironmentAssistant.GetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR); + } + + return this._memberDbConnectionString; + } + set + { + this._memberDbConnectionString = value; + Environment.SetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR, value); + } + } + + private string _crmDbConnectionString; + private string _currentMarket; + private string _memberApiToken; + private string _memberDbConnectionString; + private string _memberServiceBaseEndpoint; + private string _webStoreDbConnectionString; + private readonly string EMPLOYEE_DB_CONN_STR = "EMPLOYEE_DB_CONNECTION_STR"; + + public void Initial() + { + var memberDbConnectionString = this.EmployeeDbConnectionString; + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/Employee.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/Employee.cs new file mode 100644 index 00000000..b2fec6b5 --- /dev/null +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/Employee.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.EFCoreBulk.EntityModel +{ + [Table("Employee")] + public class Employee + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Id { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + public string Remark { get; set; } + + [Required] + public DateTimeOffset CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + public virtual Identity Identity { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/EmployeeDbContext.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/EmployeeDbContext.cs new file mode 100644 index 00000000..2f2d5043 --- /dev/null +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/EmployeeDbContext.cs @@ -0,0 +1,93 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; + +namespace Lab.EFCoreBulk.EntityModel +{ + public class EmployeeDbContext : DbContext + { + private static readonly bool[] s_migrated = { false }; + + public virtual DbSet Employees { get; set; } + + public virtual DbSet Identities { get; set; } + + public virtual DbSet OrderHistories { get; set; } + + public EmployeeDbContext(DbContextOptions options) + : base(options) + { + if (s_migrated[0]) + { + return; + } + + lock (s_migrated) + { + if (s_migrated[0] == false) + { + var memoryOptions = options.FindExtension(); + + if (memoryOptions == null) + { + var sqlOptions = options.FindExtension(); + if (sqlOptions != null) + { + Console.WriteLine( + $"EmployeeDbContext of connection string be '{sqlOptions.ConnectionString}'"); + this.Database.Migrate(); + } + } + + s_migrated[0] = true; + } + } + } + + // 給 Migration CLI 使用 + // 建構函數配置失敗才需要以下處理 + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + // var connectionString = + // "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL.UnitTest;Trusted_Connection=True;MultipleActiveResultSets=true"; + // + // // var connectionString = this._connectionString; + // if (optionsBuilder.IsConfigured == false) + // { + // optionsBuilder.UseSqlServer(connectionString); + // } + } + + //管理索引 + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(p => + { + p.HasKey(e => e.Id) + .IsClustered(false); + + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + + p.Property(p => p.Remark) + .IsRequired(false) + ; + }); + + modelBuilder.Entity(p => + { + p.HasKey(e => e.Employee_Id) + .IsClustered(false); + + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + }); + + modelBuilder.Entity(p => + { + }); + } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/Identity.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/Identity.cs new file mode 100644 index 00000000..47575538 --- /dev/null +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/Identity.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.EFCoreBulk.EntityModel +{ + [Table("Identity")] + public class Identity + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Employee_Id { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + [Required] + public DateTimeOffset CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + [ForeignKey("Employee_Id")] + public virtual Employee Employee { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/OrderHistory.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/OrderHistory.cs new file mode 100644 index 00000000..c1de6f7b --- /dev/null +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/OrderHistory.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.EFCoreBulk.EntityModel +{ + [Table("OrderHistory")] + public class OrderHistory + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + public Guid? Employee_Id { get; set; } + + public string Remark { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Product_Id { get; set; } + + public string Product_Name { get; set; } + + [Required] + public DateTime CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EnvironmentAssistant.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EnvironmentAssistant.cs new file mode 100644 index 00000000..942a8c40 --- /dev/null +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EnvironmentAssistant.cs @@ -0,0 +1,15 @@ +namespace Lab.EFCoreBulk; + +public class EnvironmentAssistant +{ + public static string GetEnvironmentVariable(string key) + { + var result = Environment.GetEnvironmentVariable(key); + if (string.IsNullOrWhiteSpace(result)) + { + throw new Exception($"the key '{key}' not exist in environment variable"); + } + + return result; + } +} \ No newline at end of file diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/Lab.EFCoreBulk.csproj b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/Lab.EFCoreBulk.csproj new file mode 100644 index 00000000..39ae089b --- /dev/null +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/Lab.EFCoreBulk.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + + + + + + + + diff --git a/ORM/EFCore/Lab.EFCoreBulk/docker-compose.yml b/ORM/EFCore/Lab.EFCoreBulk/docker-compose.yml new file mode 100644 index 00000000..8ecb1567 --- /dev/null +++ b/ORM/EFCore/Lab.EFCoreBulk/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.8" + +services: + db-sql: + image: mcr.microsoft.com/mssql/server:2019-latest + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=pass@w0rd1~ + ports: + - 1433:1433 \ No newline at end of file diff --git a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/.runsettings b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/.runsettings new file mode 100644 index 00000000..2f5e2ef5 --- /dev/null +++ b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/.runsettings @@ -0,0 +1,9 @@ + + + + .\TestResults + + trx + + + diff --git a/Test/Lab.AllureReport/Lab.AllureReport4Specflow/UnitTests.cs b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/UnitTests.cs new file mode 100644 index 00000000..7e06cfee --- /dev/null +++ b/Test/Lab.AllureReport/Lab.AllureReport4Specflow/UnitTests.cs @@ -0,0 +1,16 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.AllureReport4Specflow; + +[TestClass] +public class UnitTests +{ + [TestMethod] + [DataRow(50,70,120)] + public void 相加兩個數字(double firstNumber, double secondNumber, double expected) + { + var calculation = new Calculation(); + var actual = calculation.Add(firstNumber, secondNumber); + Assert.AreEqual(expected, actual); + } +} \ No newline at end of file From f676365471204ea072a789156c2a0c52cf713519 Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 7 Jan 2022 02:26:20 +0800 Subject: [PATCH 128/424] feat: add test case --- .../Lab.EFCoreBulk.UnitTest.csproj | 21 ++-- .../TestInstanceManager.cs | 8 +- .../Lab.EFCoreBulk.UnitTest/UnitTest1.cs | 104 ++++++++++++++++++ .../Lab.EFCoreBulk/AppEnvironmentOption.cs | 15 +-- 4 files changed, 124 insertions(+), 24 deletions(-) diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/Lab.EFCoreBulk.UnitTest.csproj b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/Lab.EFCoreBulk.UnitTest.csproj index d29b78a7..07ee4013 100644 --- a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/Lab.EFCoreBulk.UnitTest.csproj +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/Lab.EFCoreBulk.UnitTest.csproj @@ -8,19 +8,20 @@ - - - - - - - - - + + + + + + + + + + - + diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/TestInstanceManager.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/TestInstanceManager.cs index 7db399f1..8d6ac967 100644 --- a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/TestInstanceManager.cs +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/TestInstanceManager.cs @@ -14,12 +14,12 @@ internal class TestInstanceManager static TestInstanceManager() { - ConfigureTestServices(); + var services = new ServiceCollection(); + ConfigureTestServices(services); } - public static void ConfigureTestServices() + public static void ConfigureTestServices(IServiceCollection services) { - var services = new ServiceCollection(); services.AddAppEnvironment(); services.AddEntityFramework(); _serviceProvider = services.BuildServiceProvider(); @@ -29,6 +29,6 @@ public static void SetTestEnvironmentVariable() { var option = _serviceProvider.GetService(); option.EmployeeDbConnectionString = - "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True"; + "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True"; } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs index b2524ef0..88d0b7b0 100644 --- a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs @@ -1,12 +1,99 @@ using System; +using System.Diagnostics; +using System.Linq; +using EFCore.BulkExtensions; using Lab.EFCoreBulk.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Lab.EFCoreBulk.UnitTest; + [TestClass] public class UnitTest1 { + [TestMethod] + public void AddRanges() + { + var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); + var totalCount = 1000000; + var toDb = Enumerable.Range(0, totalCount) + .Select(x => new Employee + { + Id = Guid.NewGuid(), + + // Id = Guid.NewGuid(), + Age = 10, + + // Age = RandomNumber.Next(1, 100), + CreateBy = "yao", + + // CreateBy = Name.FullName(), + CreateAt = DateTimeOffset.Now, + + // CreateAt = DateTimeOffset.Now, + Name = "yao" + + // Name = Name.First(), + }).ToList(); + + var watch = new Stopwatch(); + watch.Restart(); + + db.AddRange(toDb); + + // db.BulkInsert(employees); + + watch.Stop(); + + var count = db.Employees.Count(); + Console.WriteLine($"資料庫存在筆數={count},共花費={watch.Elapsed}"); + } + + [TestMethod] + public void BulkInsert() + { + var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); + var totalCount = 1000000; + var employees = Enumerable.Range(0, totalCount) + .Select(x => new Employee + { + Id = Guid.NewGuid(), + + // Id = Guid.NewGuid(), + Age = 10, + + // Age = RandomNumber.Next(1, 100), + CreateBy = "yao", + + // CreateBy = Name.FullName(), + CreateAt = DateTimeOffset.Now, + + // CreateAt = DateTimeOffset.Now, + Name = "yao" + + // Name = Name.First(), + }).ToList(); + + var config = new BulkConfig { SetOutputIdentity = false, BatchSize = 4000, UseTempDB = true }; + var watch = new Stopwatch(); + watch.Restart(); + + db.BulkInsert(employees, config); + + // db.BulkInsert(employees); + + watch.Stop(); + + var count = db.Employees.Count(); + Console.WriteLine($"資料庫存在筆數={count},共花費={watch.Elapsed}"); + } + + + + [TestMethod] public void TestMethod1() { @@ -19,6 +106,7 @@ public void TestMethod1() CreateAt = DateTimeOffset.UtcNow, Name = "yao", CreateBy = "Sys", + // Identity = new Identity // { // Account = "yao", @@ -29,4 +117,20 @@ public void TestMethod1() }); db.SaveChanges(); } + + [TestMethod] + public void TestMethod2() + { + var host = CreateHostBuilder(null).Start(); + host.Services.GetService>(); + } + + private static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureServices((hostBuilder, services) => + { + TestInstanceManager.ConfigureTestServices(services); + }); + } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppEnvironmentOption.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppEnvironmentOption.cs index 3a8f49cd..ef209511 100644 --- a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppEnvironmentOption.cs +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppEnvironmentOption.cs @@ -6,27 +6,22 @@ public string EmployeeDbConnectionString { get { - if (string.IsNullOrWhiteSpace(this._memberDbConnectionString)) + if (string.IsNullOrWhiteSpace(this._employeeDbConnectionString)) { - this._memberDbConnectionString = + this._employeeDbConnectionString = EnvironmentAssistant.GetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR); } - return this._memberDbConnectionString; + return this._employeeDbConnectionString; } set { - this._memberDbConnectionString = value; + this._employeeDbConnectionString = value; Environment.SetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR, value); } } - private string _crmDbConnectionString; - private string _currentMarket; - private string _memberApiToken; - private string _memberDbConnectionString; - private string _memberServiceBaseEndpoint; - private string _webStoreDbConnectionString; + private string _employeeDbConnectionString; private readonly string EMPLOYEE_DB_CONN_STR = "EMPLOYEE_DB_CONNECTION_STR"; public void Initial() From 2340a89b235abfee5ca8f037438c11fb01fd00f2 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 9 Jan 2022 17:48:25 +0800 Subject: [PATCH 129/424] feat: add test case --- .../Lab.EFCoreBulk.UnitTest/MsTestHook.cs | 6 +- .../Lab.EFCoreBulk.UnitTest/UnitTest1.cs | 193 ++++++++++++++---- .../AppDependencyInjectionExtensions.cs | 5 +- .../EntityModel/EmployeeDbContext.cs | 18 -- 4 files changed, 157 insertions(+), 65 deletions(-) diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/MsTestHook.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/MsTestHook.cs index e14fc88d..481072e2 100644 --- a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/MsTestHook.cs +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/MsTestHook.cs @@ -1,8 +1,4 @@ -using Lab.EFCoreBulk.EntityModel; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Lab.EFCoreBulk.UnitTest; diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs index 88d0b7b0..85d50674 100644 --- a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using EFCore.BulkExtensions; @@ -10,7 +11,6 @@ namespace Lab.EFCoreBulk.UnitTest; - [TestClass] public class UnitTest1 { @@ -18,33 +18,43 @@ public class UnitTest1 public void AddRanges() { var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); - var totalCount = 1000000; - var toDb = Enumerable.Range(0, totalCount) - .Select(x => new Employee - { - Id = Guid.NewGuid(), - - // Id = Guid.NewGuid(), - Age = 10, + var toDb = GetEmployees(1000000); + var watch = new Stopwatch(); + watch.Restart(); - // Age = RandomNumber.Next(1, 100), - CreateBy = "yao", + db.AddRange(toDb); + var changeCount = db.SaveChanges(); - // CreateBy = Name.FullName(), - CreateAt = DateTimeOffset.Now, + watch.Stop(); - // CreateAt = DateTimeOffset.Now, - Name = "yao" + var count = db.Employees.Count(); + Console.WriteLine($"資料庫存在筆數={count},共花費={watch.Elapsed}"); + } - // Name = Name.First(), - }).ToList(); + [TestMethod] + public void BatchUpdate() + { + var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); + var toDb = GetEmployees(10000); + var update = new Employee + { + Id = Guid.NewGuid(), + Age = 10, + CreateBy = "yao", + CreateAt = DateTimeOffset.Now, + Name = "yao", + Remark = "等待更新" + }; + toDb.Add(update); + var config = new BulkConfig { SetOutputIdentity = false, BatchSize = 4000, UseTempDB = true }; + db.BulkInsert(toDb, config); var watch = new Stopwatch(); watch.Restart(); - db.AddRange(toDb); - - // db.BulkInsert(employees); + db.Employees + .Where(p => p.Id == update.Id) + .BatchUpdate(new Employee() { Remark = "Updated" }); watch.Stop(); @@ -56,34 +66,68 @@ public void AddRanges() public void BulkInsert() { var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); - var totalCount = 1000000; - var employees = Enumerable.Range(0, totalCount) - .Select(x => new Employee - { - Id = Guid.NewGuid(), + var toDb = GetEmployees(1000000); - // Id = Guid.NewGuid(), - Age = 10, + var config = new BulkConfig { SetOutputIdentity = false, BatchSize = 4000, UseTempDB = true }; - // Age = RandomNumber.Next(1, 100), - CreateBy = "yao", + var watch = new Stopwatch(); + watch.Restart(); - // CreateBy = Name.FullName(), - CreateAt = DateTimeOffset.Now, + db.BulkInsert(toDb, config); - // CreateAt = DateTimeOffset.Now, - Name = "yao" + watch.Stop(); - // Name = Name.First(), - }).ToList(); + var count = db.Employees.Count(); + Console.WriteLine($"資料庫存在筆數={count},共花費={watch.Elapsed}"); + } + + [TestMethod] + public void BulkReadAsync() + { + var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); + var toDb = GetEmployees(100000); + + db.AddRange(toDb); + + var config = new BulkConfig + { + PropertiesToExclude = new List { "SequenceId" }, + SetOutputIdentity = false, + BatchSize = 4000, + UseTempDB = true + }; - var config = new BulkConfig { SetOutputIdentity = false, BatchSize = 4000, UseTempDB = true }; var watch = new Stopwatch(); watch.Restart(); - db.BulkInsert(employees, config); + db.BulkRead(new List { "yao" }); + + watch.Stop(); + + var count = db.Employees.Count(); + Console.WriteLine($"資料庫存在筆數={count},共花費={watch.Elapsed}"); + } - // db.BulkInsert(employees); + [TestMethod] + public void BulkSaveChanges() + { + var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); + var toDb = GetEmployees(1000); + + db.AddRange(toDb); + + var config = new BulkConfig + { + PropertiesToExclude = new List { "SequenceId" }, + BulkCopyTimeout = 30, + BatchSize = 4000, + UseTempDB = true + }; + + var watch = new Stopwatch(); + watch.Restart(); + + db.BulkSaveChanges(config); watch.Stop(); @@ -91,9 +135,18 @@ public void BulkInsert() Console.WriteLine($"資料庫存在筆數={count},共花費={watch.Elapsed}"); } - - - + [TestCleanup] + public void TestCleanup() + { + CleanData(); + } + + [TestInitialize] + public void TestInitialize() + { + CleanData(); + } + [TestMethod] public void TestMethod1() { @@ -125,6 +178,40 @@ public void TestMethod2() host.Services.GetService>(); } + private static void CleanData() + { + using var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); + // db.Truncate(); + // db.Truncate(); + using var transaction = db.Database.BeginTransaction(); + + db.OrderHistories + .BatchDelete(); + + db.Identities + .BatchDelete(); + + // db.Truncate(); + db.Employees + .BatchDelete(); + + transaction.Commit(); + + // db.Employees + // .Where(p => p.Id != Guid.Empty) + // .BatchDelete(); + // + // while (db.Employees.Any()) + // { + // var deletedCount = db.Employees + // .Where(p => p.Id != Guid.Empty) + // .Take(1000000) + // .BatchDelete(); + // var count = db.Employees.Count(); + // Console.WriteLine($"已刪除 {deletedCount} 筆,剩下 {count} 筆"); + // } + } + private static IHostBuilder CreateHostBuilder(string[] args) { return Host.CreateDefaultBuilder(args) @@ -133,4 +220,28 @@ private static IHostBuilder CreateHostBuilder(string[] args) TestInstanceManager.ConfigureTestServices(services); }); } + + private static List GetEmployees(int totalCount) + { + var employees = Enumerable.Range(0, totalCount) + .Select(x => new Employee + { + Id = Guid.NewGuid(), + + // Id = Guid.NewGuid(), + Age = 10, + + // Age = RandomNumber.Next(1, 100), + CreateBy = "yao", + + // CreateBy = Name.FullName(), + CreateAt = DateTimeOffset.Now, + + // CreateAt = DateTimeOffset.Now, + Name = "yao" + + // Name = Name.First(), + }).ToList(); + return employees; + } } \ No newline at end of file diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppDependencyInjectionExtensions.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppDependencyInjectionExtensions.cs index 99b11430..3a0becd7 100644 --- a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppDependencyInjectionExtensions.cs +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/AppDependencyInjectionExtensions.cs @@ -9,7 +9,10 @@ public static class AppDependencyInjectionExtensions { public static void AddAppEnvironment(this IServiceCollection services) { - services.AddLogging(); + services.AddLogging(builder => + { + builder.AddConsole(); + }); services.AddSingleton(); } diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/EmployeeDbContext.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/EmployeeDbContext.cs index 2f2d5043..e34a7e7b 100644 --- a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/EmployeeDbContext.cs +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk/EntityModel/EmployeeDbContext.cs @@ -44,20 +44,6 @@ public EmployeeDbContext(DbContextOptions options) } } - // 給 Migration CLI 使用 - // 建構函數配置失敗才需要以下處理 - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - // var connectionString = - // "Server=(localdb)\\mssqllocaldb;Database=Lab.DAL.UnitTest;Trusted_Connection=True;MultipleActiveResultSets=true"; - // - // // var connectionString = this._connectionString; - // if (optionsBuilder.IsConfigured == false) - // { - // optionsBuilder.UseSqlServer(connectionString); - // } - } - //管理索引 protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -84,10 +70,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .IsUnique() .IsClustered(); }); - - modelBuilder.Entity(p => - { - }); } } } \ No newline at end of file From 39f3b07024a58a4346d37d05f4d0567168830344 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 10 Jan 2022 02:07:02 +0800 Subject: [PATCH 130/424] feat: add test case --- .../Lab.EFCoreBulk.UnitTest/UnitTest1.cs | 102 ++++++++++++++---- 1 file changed, 82 insertions(+), 20 deletions(-) diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs index 85d50674..5ecaf99f 100644 --- a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.UnitTest/UnitTest1.cs @@ -31,6 +31,39 @@ public void AddRanges() Console.WriteLine($"資料庫存在筆數={count},共花費={watch.Elapsed}"); } + [TestMethod] + public void BatchDelete() + { + var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); + var toDb = GetEmployees(10000); + var update = new Employee + { + Id = Guid.NewGuid(), + Age = 10, + CreateBy = "yao", + CreateAt = DateTimeOffset.Now, + Name = "yao", + Remark = "等待更新" + }; + toDb.Add(update); + var config = new BulkConfig { SetOutputIdentity = false, BatchSize = 4000, UseTempDB = true }; + db.BulkInsert(toDb, config); + + var watch = new Stopwatch(); + watch.Restart(); + + db.Employees + .Where(p => p.Id == update.Id) + .BatchDelete(); + + watch.Stop(); + + var count = db.Employees.Count(); + var isExist = db.Employees.Any(p => p.Id == update.Id); + Assert.AreEqual(false, isExist); + Console.WriteLine($"資料庫存在筆數={count},共花費={watch.Elapsed},{update.Id} 資料不存在"); + } + [TestMethod] public void BatchUpdate() { @@ -54,7 +87,7 @@ public void BatchUpdate() db.Employees .Where(p => p.Id == update.Id) - .BatchUpdate(new Employee() { Remark = "Updated" }); + .BatchUpdate(new Employee { Remark = "Updated" }); watch.Stop(); @@ -82,30 +115,37 @@ public void BulkInsert() } [TestMethod] - public void BulkReadAsync() + public void BulkRead() { var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); - var toDb = GetEmployees(100000); - - db.AddRange(toDb); - - var config = new BulkConfig + var toDb = GetEmployees(100); { - PropertiesToExclude = new List { "SequenceId" }, - SetOutputIdentity = false, - BatchSize = 4000, - UseTempDB = true - }; + var config = new BulkConfig { SetOutputIdentity = false, BatchSize = 4000, UseTempDB = true }; + db.BulkInsert(toDb, config); + } var watch = new Stopwatch(); watch.Restart(); - - db.BulkRead(new List { "yao" }); + { + var items = new List + { + new() { Name = "yao1" }, + new() { Name = "yao2" } + }; + var config = new BulkConfig + { + UpdateByProperties = new List + { + nameof(Employee.Name), + }, + UseTempDB = true + }; + db.BulkRead(items, config); + } watch.Stop(); - var count = db.Employees.Count(); - Console.WriteLine($"資料庫存在筆數={count},共花費={watch.Elapsed}"); + Console.WriteLine($"共花費={watch.Elapsed}"); } [TestMethod] @@ -135,6 +175,27 @@ public void BulkSaveChanges() Console.WriteLine($"資料庫存在筆數={count},共花費={watch.Elapsed}"); } + [TestMethod] + public void Contains() + { + var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); + var toDb = GetEmployees(100); + { + var config = new BulkConfig { SetOutputIdentity = false, BatchSize = 4000, UseTempDB = true }; + db.BulkInsert(toDb, config); + } + + var watch = new Stopwatch(); + watch.Restart(); + + var items = new List { "yao1", "yao2" }; + var employees = db.Employees.Where(a => items.Contains(a.Name)).AsNoTracking().ToList(); //SQL IN operator + + watch.Stop(); + + Console.WriteLine($"共花費={watch.Elapsed}"); + } + [TestCleanup] public void TestCleanup() { @@ -181,10 +242,11 @@ public void TestMethod2() private static void CleanData() { using var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); + // db.Truncate(); // db.Truncate(); using var transaction = db.Database.BeginTransaction(); - + db.OrderHistories .BatchDelete(); @@ -194,7 +256,7 @@ private static void CleanData() // db.Truncate(); db.Employees .BatchDelete(); - + transaction.Commit(); // db.Employees @@ -224,7 +286,7 @@ private static IHostBuilder CreateHostBuilder(string[] args) private static List GetEmployees(int totalCount) { var employees = Enumerable.Range(0, totalCount) - .Select(x => new Employee + .Select((x, i) => new Employee { Id = Guid.NewGuid(), @@ -238,7 +300,7 @@ private static List GetEmployees(int totalCount) CreateAt = DateTimeOffset.Now, // CreateAt = DateTimeOffset.Now, - Name = "yao" + Name = $"yao{i}" // Name = Name.First(), }).ToList(); From df03033c75b2d125dad3354fbfcc9637cf0e2d5e Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 16 Jan 2022 10:26:34 +0800 Subject: [PATCH 131/424] feat: add mimiprofiler project --- .../Controllers/ValuesController.cs | 82 +++++++++++++++ .../Controllers/WeatherForecastController.cs | 34 +++++++ .../Lab.NETMiniProfiler.ASPNetCore5.csproj | 21 ++++ .../Program.cs | 16 +++ .../Properties/launchSettings.json | 31 ++++++ .../Startup.cs | 62 ++++++++++++ .../WeatherForecast.cs | 13 +++ .../appsettings.Development.json | 8 ++ .../appsettings.json | 9 ++ .../index.html | 99 +++++++++++++++++++ .../Lab.NETMiniProfiler.sln | 25 +++++ 11 files changed, 400 insertions(+) create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/ValuesController.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/WeatherForecastController.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Lab.NETMiniProfiler.ASPNetCore5.csproj create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Program.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/WeatherForecast.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/appsettings.Development.json create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/appsettings.json create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/index.html create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/ValuesController.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/ValuesController.cs new file mode 100644 index 00000000..0a1d0d4e --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/ValuesController.cs @@ -0,0 +1,82 @@ +using System.Net; +using Microsoft.AspNetCore.Mvc; +using StackExchange.Profiling; + +namespace Lab.NETMiniProfiler.ASPNetCore5.Controllers +{ + /// + /// Value Controller + /// + [Route("[controller]")] + [ApiController] + public class ValuesController : ControllerBase + { + /// + /// Get Api + /// + /// + // GET api/values + [HttpGet] + public ActionResult> Get() + { + string url1 = string.Empty; + string url2 = string.Empty; + using (MiniProfiler.Current.Step("Get方法")) + { + using (MiniProfiler.Current.Step("准备数据")) + { + using (MiniProfiler.Current.CustomTiming("SQL", "SELECT * FROM Config")) + { + // 模拟一个SQL查询 + Thread.Sleep(500); + + url1 = "https://www.baidu.com"; + url2 = "https://www.sina.com.cn/"; + } + } + + + using (MiniProfiler.Current.Step("使用从数据库中查询的数据,进行Http请求")) + { + using (MiniProfiler.Current.CustomTiming("HTTP", "GET " + url1)) + { + var client = new WebClient(); + var reply = client.DownloadString(url1); + } + + using (MiniProfiler.Current.CustomTiming("HTTP", "GET " + url2)) + { + var client = new WebClient(); + var reply = client.DownloadString(url2); + } + } + } + return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public ActionResult Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody] string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/WeatherForecastController.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..f1a485f1 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/WeatherForecastController.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.NETMiniProfiler.ASPNetCore5.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + this._logger = logger; + } + + [HttpGet] + public IEnumerable Get() + { + var rng = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = rng.Next(-20, 55), + Summary = Summaries[rng.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Lab.NETMiniProfiler.ASPNetCore5.csproj b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Lab.NETMiniProfiler.ASPNetCore5.csproj new file mode 100644 index 00000000..8e212a10 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Lab.NETMiniProfiler.ASPNetCore5.csproj @@ -0,0 +1,21 @@ + + + + net5.0 + enable + enable + 10 + + + + + + + + + + + + + + diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Program.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Program.cs new file mode 100644 index 00000000..0d2a45b3 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Program.cs @@ -0,0 +1,16 @@ +namespace Lab.NETMiniProfiler.ASPNetCore5 +{ + public class Program + { + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + } + + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json new file mode 100644 index 00000000..2ac1d82f --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:41185", + "sslPort": 44361 + } + }, + "profiles": { + "Lab.NETMiniProfiler.ASPNetCore5": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7186;http://localhost:5186", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs new file mode 100644 index 00000000..74aafb55 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs @@ -0,0 +1,62 @@ +using System.Reflection; +using Microsoft.OpenApi.Models; + +namespace Lab.NETMiniProfiler.ASPNetCore5 +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" }); + }); + + services.AddMiniProfiler(options => + options.RouteBasePath = "/profiler" + ); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseMiniProfiler(); + + app.UseSwagger(); + //app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1")); + app.UseSwaggerUI(c => + { + c.RoutePrefix = "swagger"; + c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); + c.IndexStream = () => this.GetType() + .GetTypeInfo() + .Assembly + .GetManifestResourceStream("Lab.NETMiniProfiler.ASPNetCore5.index.html"); + }); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/WeatherForecast.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/WeatherForecast.cs new file mode 100644 index 00000000..de49d97c --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace Lab.NETMiniProfiler.ASPNetCore5 +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(this.TemperatureC / 0.5556); + + public string Summary { get; set; } + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/appsettings.Development.json b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/appsettings.json b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/index.html b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/index.html new file mode 100644 index 00000000..49c7aa3d --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/index.html @@ -0,0 +1,99 @@ + + + + + + + + %(DocumentTitle) + + + + + + + %(HeadContent) + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln new file mode 100644 index 00000000..40be8a71 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31911.196 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lab.NETMiniProfiler.ASPNetCore5", "Lab.NETMiniProfiler.ASPNetCore5\Lab.NETMiniProfiler.ASPNetCore5.csproj", "{8D4D97D5-C7F1-4BD7-9FE8-7A3766DB8D04}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8D4D97D5-C7F1-4BD7-9FE8-7A3766DB8D04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D4D97D5-C7F1-4BD7-9FE8-7A3766DB8D04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D4D97D5-C7F1-4BD7-9FE8-7A3766DB8D04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D4D97D5-C7F1-4BD7-9FE8-7A3766DB8D04}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1A39E53A-25EB-4546-9E76-DA1904FE5DCA} + EndGlobalSection +EndGlobal From 4238a4c69c1b200ad52b4360fc07d6a8a8037ad7 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 16 Jan 2022 10:45:20 +0800 Subject: [PATCH 132/424] add files --- .../Lab.NETMiniProfiler.ASPNetCore5.csproj | 4 + .../Properties/launchSettings.json | 3 + .../Startup.cs | 3 + .../AppDependencyInjectionExtensions.cs | 52 ++++++++++ .../AppEnvironmentOption.cs | 31 ++++++ .../EntityModel/Employee.cs | 30 ++++++ .../EntityModel/EmployeeDbContext.cs | 69 +++++++++++++ .../EntityModel/Identity.cs | 33 +++++++ .../EntityModel/OrderHistory.cs | 30 ++++++ .../EnvironmentAssistant.cs | 15 +++ ...MiniProfiler.Infrastructure.EFCore5.csproj | 17 ++++ .../AppDependencyInjectionExtensions.cs | 52 ++++++++++ .../AppEnvironmentOption.cs | 31 ++++++ .../EntityModel/Employee.cs | 30 ++++++ .../EntityModel/EmployeeDbContext.cs | 75 ++++++++++++++ .../EntityModel/Identity.cs | 33 +++++++ .../EntityModel/OrderHistory.cs | 30 ++++++ .../EnvironmentAssistant.cs | 15 +++ ...MiniProfiler.Infrastructure.EFCore6.csproj | 15 +++ .../Lab.NETMiniProfiler.sln | 6 ++ .../Controllers/ValueController.cs | 54 ++++++++++ .../Controllers/WeatherForecastController.cs | 39 ++++++++ .../WebApplication1/Program.cs | 26 +++++ .../Properties/launchSettings.json | 31 ++++++ .../WebApplication1/Startup.cs | 74 ++++++++++++++ .../WebApplication1/WeatherForecast.cs | 15 +++ .../WebApplication1/WebApplication1.csproj | 21 ++++ .../appsettings.Development.json | 9 ++ .../WebApplication1/appsettings.json | 10 ++ .../WebApplication1/index.html | 99 +++++++++++++++++++ 30 files changed, 952 insertions(+) create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppDependencyInjectionExtensions.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppEnvironmentOption.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/Employee.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/EmployeeDbContext.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/Identity.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/OrderHistory.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EnvironmentAssistant.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/Lab.NETMiniProfiler.Infrastructure.EFCore5.csproj create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppDependencyInjectionExtensions.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppEnvironmentOption.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Employee.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/EmployeeDbContext.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Identity.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/OrderHistory.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EnvironmentAssistant.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/Lab.NETMiniProfiler.Infrastructure.EFCore6.csproj create mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/ValueController.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/WeatherForecastController.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/Program.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/Properties/launchSettings.json create mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/Startup.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/WeatherForecast.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/WebApplication1.csproj create mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.Development.json create mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.json create mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/index.html diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Lab.NETMiniProfiler.ASPNetCore5.csproj b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Lab.NETMiniProfiler.ASPNetCore5.csproj index 8e212a10..44130051 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Lab.NETMiniProfiler.ASPNetCore5.csproj +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Lab.NETMiniProfiler.ASPNetCore5.csproj @@ -18,4 +18,8 @@
+ + + + diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json index 2ac1d82f..be2ddc83 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json @@ -6,6 +6,9 @@ "iisExpress": { "applicationUrl": "http://localhost:41185", "sslPort": 44361 + }, + "environmentVariables": { + "EMPLOYEE_DB_CONNECTION_STR": "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True" } }, "profiles": { diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs index 74aafb55..46794e32 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs @@ -1,4 +1,5 @@ using System.Reflection; +using Lab.NETMiniProfiler.Infrastructure.EFCore5; using Microsoft.OpenApi.Models; namespace Lab.NETMiniProfiler.ASPNetCore5 @@ -24,6 +25,8 @@ public void ConfigureServices(IServiceCollection services) services.AddMiniProfiler(options => options.RouteBasePath = "/profiler" ); + services.AddAppEnvironment(); + services.AddEntityFramework(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppDependencyInjectionExtensions.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppDependencyInjectionExtensions.cs new file mode 100644 index 00000000..22a801f0 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppDependencyInjectionExtensions.cs @@ -0,0 +1,52 @@ +using Lab.NETMiniProfiler.Infrastructure.EFCore5.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.NETMiniProfiler.Infrastructure.EFCore5; + +public static class AppDependencyInjectionExtensions +{ + public static void AddAppEnvironment(this IServiceCollection services) + { + services.AddLogging(builder => + { + builder.AddConsole(); + }); + services.AddSingleton(); + } + + public static void AddEntityFramework(this IServiceCollection services) + { + services.AddPooledDbContextFactory((provider, optionsBuilder) => + { + var option = provider.GetService(); + var connectionString = option.EmployeeDbConnectionString; + var loggerFactory = provider.GetService(); + optionsBuilder.UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + ; + }); + + ; + + // services.AddPooledDbContextFactory((provider, options) => + // { + // var option = provider.GetService(); + // var loggerFactory = provider.GetService(); + // options.UseNpgsql( + // option.MemberDbConnectionString, //只會呼叫一次 + // builder => + // builder.EnableRetryOnFailure( + // 10, + // TimeSpan.FromSeconds(30), + // new List { "57P01" })) + // + // // .UseLazyLoadingProxies() + // .UseSnakeCaseNamingConvention() + // .UseLoggerFactory(loggerFactory) + // ; + // }); + } + +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppEnvironmentOption.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppEnvironmentOption.cs new file mode 100644 index 00000000..dcc2f14d --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppEnvironmentOption.cs @@ -0,0 +1,31 @@ +namespace Lab.NETMiniProfiler.Infrastructure.EFCore5; + +public class AppEnvironmentOption +{ + public string EmployeeDbConnectionString + { + get + { + if (string.IsNullOrWhiteSpace(this._employeeDbConnectionString)) + { + this._employeeDbConnectionString = + EnvironmentAssistant.GetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR); + } + + return this._employeeDbConnectionString; + } + set + { + this._employeeDbConnectionString = value; + Environment.SetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR, value); + } + } + + private string _employeeDbConnectionString; + private readonly string EMPLOYEE_DB_CONN_STR = "EMPLOYEE_DB_CONNECTION_STR"; + + public void Initial() + { + var memberDbConnectionString = this.EmployeeDbConnectionString; + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/Employee.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/Employee.cs new file mode 100644 index 00000000..51384037 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/Employee.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.NETMiniProfiler.Infrastructure.EFCore5.EntityModel +{ + [Table("Employee")] + public class Employee + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Id { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + public string Remark { get; set; } + + [Required] + public DateTimeOffset CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + public virtual Identity Identity { get; set; } + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/EmployeeDbContext.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/EmployeeDbContext.cs new file mode 100644 index 00000000..8de5bc83 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/EmployeeDbContext.cs @@ -0,0 +1,69 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; + +namespace Lab.NETMiniProfiler.Infrastructure.EFCore5.EntityModel +{ + public class EmployeeDbContext : DbContext + { + private static readonly bool[] s_migrated = { false }; + + public virtual DbSet Employees { get; set; } + + public virtual DbSet Identities { get; set; } + + public virtual DbSet OrderHistories { get; set; } + + public EmployeeDbContext(DbContextOptions options) + : base(options) + { + if (s_migrated[0]) + { + return; + } + + lock (s_migrated) + { + if (s_migrated[0] == false) + { + var sqlOptions = options.FindExtension(); + if (sqlOptions != null) + { + Console.WriteLine( + $"EmployeeDbContext of connection string be '{sqlOptions.ConnectionString}'"); + this.Database.Migrate(); + } + + s_migrated[0] = true; + } + } + } + + //管理索引 + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(p => + { + p.HasKey(e => e.Id) + .IsClustered(false); + + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + + p.Property(p => p.Remark) + .IsRequired(false) + ; + }); + + modelBuilder.Entity(p => + { + p.HasKey(e => e.Employee_Id) + .IsClustered(false); + + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + }); + } + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/Identity.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/Identity.cs new file mode 100644 index 00000000..8537c235 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/Identity.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.NETMiniProfiler.Infrastructure.EFCore5.EntityModel +{ + [Table("Identity")] + public class Identity + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Employee_Id { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + [Required] + public DateTimeOffset CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + [ForeignKey("Employee_Id")] + public virtual Employee Employee { get; set; } + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/OrderHistory.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/OrderHistory.cs new file mode 100644 index 00000000..4da48bbe --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/OrderHistory.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.NETMiniProfiler.Infrastructure.EFCore5.EntityModel +{ + [Table("OrderHistory")] + public class OrderHistory + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + public Guid? Employee_Id { get; set; } + + public string Remark { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Product_Id { get; set; } + + public string Product_Name { get; set; } + + [Required] + public DateTime CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EnvironmentAssistant.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EnvironmentAssistant.cs new file mode 100644 index 00000000..32256cff --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EnvironmentAssistant.cs @@ -0,0 +1,15 @@ +namespace Lab.NETMiniProfiler.Infrastructure.EFCore5; + +public class EnvironmentAssistant +{ + public static string GetEnvironmentVariable(string key) + { + var result = Environment.GetEnvironmentVariable(key); + if (string.IsNullOrWhiteSpace(result)) + { + throw new Exception($"the key '{key}' not exist in environment variable"); + } + + return result; + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/Lab.NETMiniProfiler.Infrastructure.EFCore5.csproj b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/Lab.NETMiniProfiler.Infrastructure.EFCore5.csproj new file mode 100644 index 00000000..d2a6aa4b --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/Lab.NETMiniProfiler.Infrastructure.EFCore5.csproj @@ -0,0 +1,17 @@ + + + + net5.0 + enable + enable + 10 + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppDependencyInjectionExtensions.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppDependencyInjectionExtensions.cs new file mode 100644 index 00000000..094946b8 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppDependencyInjectionExtensions.cs @@ -0,0 +1,52 @@ +using Lab.NETMiniProfiler.Infrastructure.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.NETMiniProfiler.Infrastructure; + +public static class AppDependencyInjectionExtensions +{ + public static void AddAppEnvironment(this IServiceCollection services) + { + services.AddLogging(builder => + { + builder.AddConsole(); + }); + services.AddSingleton(); + } + + public static void AddEntityFramework(this IServiceCollection services) + { + services.AddPooledDbContextFactory((provider, optionsBuilder) => + { + var option = provider.GetService(); + var connectionString = option.EmployeeDbConnectionString; + var loggerFactory = provider.GetService(); + optionsBuilder.UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + ; + }); + + ; + + // services.AddPooledDbContextFactory((provider, options) => + // { + // var option = provider.GetService(); + // var loggerFactory = provider.GetService(); + // options.UseNpgsql( + // option.MemberDbConnectionString, //只會呼叫一次 + // builder => + // builder.EnableRetryOnFailure( + // 10, + // TimeSpan.FromSeconds(30), + // new List { "57P01" })) + // + // // .UseLazyLoadingProxies() + // .UseSnakeCaseNamingConvention() + // .UseLoggerFactory(loggerFactory) + // ; + // }); + } + +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppEnvironmentOption.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppEnvironmentOption.cs new file mode 100644 index 00000000..42da1ef1 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppEnvironmentOption.cs @@ -0,0 +1,31 @@ +namespace Lab.NETMiniProfiler.Infrastructure; + +public class AppEnvironmentOption +{ + public string EmployeeDbConnectionString + { + get + { + if (string.IsNullOrWhiteSpace(this._employeeDbConnectionString)) + { + this._employeeDbConnectionString = + EnvironmentAssistant.GetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR); + } + + return this._employeeDbConnectionString; + } + set + { + this._employeeDbConnectionString = value; + Environment.SetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR, value); + } + } + + private string _employeeDbConnectionString; + private readonly string EMPLOYEE_DB_CONN_STR = "EMPLOYEE_DB_CONNECTION_STR"; + + public void Initial() + { + var memberDbConnectionString = this.EmployeeDbConnectionString; + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Employee.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Employee.cs new file mode 100644 index 00000000..cf11b247 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Employee.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.NETMiniProfiler.Infrastructure.EntityModel +{ + [Table("Employee")] + public class Employee + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Id { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + public string Remark { get; set; } + + [Required] + public DateTimeOffset CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + public virtual Identity Identity { get; set; } + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/EmployeeDbContext.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/EmployeeDbContext.cs new file mode 100644 index 00000000..f07f9b6b --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/EmployeeDbContext.cs @@ -0,0 +1,75 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; + +namespace Lab.NETMiniProfiler.Infrastructure.EntityModel +{ + public class EmployeeDbContext : DbContext + { + private static readonly bool[] s_migrated = { false }; + + public virtual DbSet Employees { get; set; } + + public virtual DbSet Identities { get; set; } + + public virtual DbSet OrderHistories { get; set; } + + public EmployeeDbContext(DbContextOptions options) + : base(options) + { + if (s_migrated[0]) + { + return; + } + + lock (s_migrated) + { + if (s_migrated[0] == false) + { + var memoryOptions = options.FindExtension(); + + if (memoryOptions == null) + { + var sqlOptions = options.FindExtension(); + if (sqlOptions != null) + { + Console.WriteLine( + $"EmployeeDbContext of connection string be '{sqlOptions.ConnectionString}'"); + this.Database.Migrate(); + } + } + + s_migrated[0] = true; + } + } + } + + //管理索引 + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(p => + { + p.HasKey(e => e.Id) + .IsClustered(false); + + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + + p.Property(p => p.Remark) + .IsRequired(false) + ; + }); + + modelBuilder.Entity(p => + { + p.HasKey(e => e.Employee_Id) + .IsClustered(false); + + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + }); + } + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Identity.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Identity.cs new file mode 100644 index 00000000..57b2a437 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Identity.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.NETMiniProfiler.Infrastructure.EntityModel +{ + [Table("Identity")] + public class Identity + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Employee_Id { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + [Required] + public DateTimeOffset CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + [ForeignKey("Employee_Id")] + public virtual Employee Employee { get; set; } + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/OrderHistory.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/OrderHistory.cs new file mode 100644 index 00000000..63c19ce4 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/OrderHistory.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.NETMiniProfiler.Infrastructure.EntityModel +{ + [Table("OrderHistory")] + public class OrderHistory + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + public Guid? Employee_Id { get; set; } + + public string Remark { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Product_Id { get; set; } + + public string Product_Name { get; set; } + + [Required] + public DateTime CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EnvironmentAssistant.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EnvironmentAssistant.cs new file mode 100644 index 00000000..7f2ff85c --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EnvironmentAssistant.cs @@ -0,0 +1,15 @@ +namespace Lab.NETMiniProfiler.Infrastructure; + +public class EnvironmentAssistant +{ + public static string GetEnvironmentVariable(string key) + { + var result = Environment.GetEnvironmentVariable(key); + if (string.IsNullOrWhiteSpace(result)) + { + throw new Exception($"the key '{key}' not exist in environment variable"); + } + + return result; + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/Lab.NETMiniProfiler.Infrastructure.EFCore6.csproj b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/Lab.NETMiniProfiler.Infrastructure.EFCore6.csproj new file mode 100644 index 00000000..c5a492b3 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/Lab.NETMiniProfiler.Infrastructure.EFCore6.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + Lab.NETMiniProfiler.Infrastructure.EFCore6 + + + + + + + + diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln index 40be8a71..2cae23eb 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.31911.196 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lab.NETMiniProfiler.ASPNetCore5", "Lab.NETMiniProfiler.ASPNetCore5\Lab.NETMiniProfiler.ASPNetCore5.csproj", "{8D4D97D5-C7F1-4BD7-9FE8-7A3766DB8D04}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.NETMiniProfiler.Infrastructure.EFCore5", "Lab.NETMiniProfiler.Infrastructure.EFCore5\Lab.NETMiniProfiler.Infrastructure.EFCore5.csproj", "{083D436C-B451-4BCE-8A97-E6E77B9F9A23}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {8D4D97D5-C7F1-4BD7-9FE8-7A3766DB8D04}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D4D97D5-C7F1-4BD7-9FE8-7A3766DB8D04}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D4D97D5-C7F1-4BD7-9FE8-7A3766DB8D04}.Release|Any CPU.Build.0 = Release|Any CPU + {083D436C-B451-4BCE-8A97-E6E77B9F9A23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {083D436C-B451-4BCE-8A97-E6E77B9F9A23}.Debug|Any CPU.Build.0 = Debug|Any CPU + {083D436C-B451-4BCE-8A97-E6E77B9F9A23}.Release|Any CPU.ActiveCfg = Release|Any CPU + {083D436C-B451-4BCE-8A97-E6E77B9F9A23}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/ValueController.cs b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/ValueController.cs new file mode 100644 index 00000000..7ff8d74c --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/ValueController.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Mvc; +using StackExchange.Profiling; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace WebApplication1.Controllers +{ + [ApiController] + [Route("[controller]")] + public class ValueController : ControllerBase + { + [HttpGet] + public IEnumerable Get() + { + string url1 = string.Empty; + string url2 = string.Empty; + using (MiniProfiler.Current.Step("Get方法")) + { + using (MiniProfiler.Current.Step("准备数据")) + { + using (MiniProfiler.Current.CustomTiming("SQL", "SELECT * FROM Config")) + { + // 模拟一个SQL查询 + Thread.Sleep(500); + + url1 = "https://www.baidu.com"; + url2 = "https://www.sina.com.cn/"; + } + } + + + using (MiniProfiler.Current.Step("使用从数据库中查询的数据,进行Http请求")) + { + using (MiniProfiler.Current.CustomTiming("HTTP", "GET " + url1)) + { + var client = new WebClient(); + var reply = client.DownloadString(url1); + } + + using (MiniProfiler.Current.CustomTiming("HTTP", "GET " + url2)) + { + var client = new WebClient(); + var reply = client.DownloadString(url2); + } + } + } + return new string[] { "value1", "value2" }; + } + } +} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/WeatherForecastController.cs b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..47134b11 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/WeatherForecastController.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication1.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public IEnumerable Get() + { + var rng = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = rng.Next(-20, 55), + Summary = Summaries[rng.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Program.cs b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Program.cs new file mode 100644 index 00000000..53f3427e --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Program.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace WebApplication1 +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Properties/launchSettings.json b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Properties/launchSettings.json new file mode 100644 index 00000000..1496716e --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:64791", + "sslPort": 44397 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WebApplication1": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Startup.cs b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Startup.cs new file mode 100644 index 00000000..10a9665d --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Startup.cs @@ -0,0 +1,74 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace WebApplication1 +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" }); + }); + + services.AddMiniProfiler(options => + options.RouteBasePath = "/profiler" + ); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseMiniProfiler(); + + app.UseSwagger(); + //app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1")); + app.UseSwaggerUI(c => + { + c.RoutePrefix = "swagger"; + c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); + c.IndexStream = () => GetType() + .GetTypeInfo() + .Assembly + .GetManifestResourceStream("WebApplication1.index.html"); + }); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/WeatherForecast.cs b/Benchmark/Lab.NETMiniProfiler/WebApplication1/WeatherForecast.cs new file mode 100644 index 00000000..11a0296b --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/WebApplication1/WeatherForecast.cs @@ -0,0 +1,15 @@ +using System; + +namespace WebApplication1 +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string Summary { get; set; } + } +} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/WebApplication1.csproj b/Benchmark/Lab.NETMiniProfiler/WebApplication1/WebApplication1.csproj new file mode 100644 index 00000000..cd66119c --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/WebApplication1/WebApplication1.csproj @@ -0,0 +1,21 @@ + + + + net5.0 + + + + + + + + + + + + + + + + + diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.Development.json b/Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.Development.json new file mode 100644 index 00000000..8983e0fc --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.json b/Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.json new file mode 100644 index 00000000..d9d9a9bf --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/index.html b/Benchmark/Lab.NETMiniProfiler/WebApplication1/index.html new file mode 100644 index 00000000..49c7aa3d --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/WebApplication1/index.html @@ -0,0 +1,99 @@ + + + + + + + + %(DocumentTitle) + + + + + + + %(HeadContent) + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + From 3c5627a8c67caff7d24d7f4c5c6971cd74a3d990 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 16 Jan 2022 11:24:29 +0800 Subject: [PATCH 133/424] =?UTF-8?q?feat:=20=E4=BF=AE=E6=AD=A3=E6=96=B9?= =?UTF-8?q?=E6=A1=88=E7=B5=90=E6=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.sln | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.sln b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.sln index 57f01a38..592822e0 100644 --- a/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.sln +++ b/ORM/EFCore/Lab.EFCoreBulk/Lab.EFCoreBulk.sln @@ -4,6 +4,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.EFCoreBulk", "Lab.EFCor EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.EFCoreBulk.UnitTest", "Lab.EFCoreBulk.UnitTest\Lab.EFCoreBulk.UnitTest.csproj", "{73344F9D-E263-4EE9-8193-F23343731E56}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{1F776B7D-C182-4DC3-BA57-844657BD9AC0}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3B774B51-B4E4-4F3F-9E1F-43E4A620245A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -19,4 +26,8 @@ Global {73344F9D-E263-4EE9-8193-F23343731E56}.Release|Any CPU.ActiveCfg = Release|Any CPU {73344F9D-E263-4EE9-8193-F23343731E56}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {3C9700D7-3563-4C7B-9EF4-A7EE512993DC} = {3B774B51-B4E4-4F3F-9E1F-43E4A620245A} + {73344F9D-E263-4EE9-8193-F23343731E56} = {3B774B51-B4E4-4F3F-9E1F-43E4A620245A} + EndGlobalSection EndGlobal From 4ccffec9a546cfb258d9f5931ee6a99581c55b9b Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 16 Jan 2022 12:22:11 +0800 Subject: [PATCH 134/424] add get/post action --- .../Controllers/ValuesController.cs | 83 +++++++++---------- .../Lab.NETMiniProfiler.ASPNetCore5.csproj | 1 + .../Properties/launchSettings.json | 9 +- .../Startup.cs | 58 ++++++++----- .../AppEnvironmentOption.cs | 6 +- .../EntityModel/EmployeeDbContext.cs | 17 ++-- ...MiniProfiler.Infrastructure.EFCore5.csproj | 2 +- .../Lab.NETMiniProfiler.sln | 11 +++ .../Lab.NETMiniProfiler/docker-compose.yml | 10 +++ 9 files changed, 116 insertions(+), 81 deletions(-) create mode 100644 Benchmark/Lab.NETMiniProfiler/docker-compose.yml diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/ValuesController.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/ValuesController.cs index 0a1d0d4e..811a23c0 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/ValuesController.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/ValuesController.cs @@ -1,57 +1,44 @@ -using System.Net; +using Lab.NETMiniProfiler.Infrastructure.EFCore5.EntityModel; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using StackExchange.Profiling; namespace Lab.NETMiniProfiler.ASPNetCore5.Controllers { /// - /// Value Controller + /// Value Controller /// [Route("[controller]")] [ApiController] public class ValuesController : ControllerBase { + private readonly IDbContextFactory _employeeDbContextFactory; + + public ValuesController(IDbContextFactory employeeDbContextFactory) + { + this._employeeDbContextFactory = employeeDbContextFactory; + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + /// - /// Get Api + /// Get Api /// /// + // GET api/values [HttpGet] - public ActionResult> Get() + public async Task Get(CancellationToken cancel = default) { - string url1 = string.Empty; - string url2 = string.Empty; - using (MiniProfiler.Current.Step("Get方法")) + using (MiniProfiler.Current.Step("查詢資料庫")) { - using (MiniProfiler.Current.Step("准备数据")) - { - using (MiniProfiler.Current.CustomTiming("SQL", "SELECT * FROM Config")) - { - // 模拟一个SQL查询 - Thread.Sleep(500); - - url1 = "https://www.baidu.com"; - url2 = "https://www.sina.com.cn/"; - } - } - - - using (MiniProfiler.Current.Step("使用从数据库中查询的数据,进行Http请求")) - { - using (MiniProfiler.Current.CustomTiming("HTTP", "GET " + url1)) - { - var client = new WebClient(); - var reply = client.DownloadString(url1); - } - - using (MiniProfiler.Current.CustomTiming("HTTP", "GET " + url2)) - { - var client = new WebClient(); - var reply = client.DownloadString(url2); - } - } + await using var db = this._employeeDbContextFactory.CreateDbContext(); + return this.Ok(await db.Employees.AsTracking().ToListAsync(cancel)); } - return new string[] { "value1", "value2" }; } // GET api/values/5 @@ -63,8 +50,24 @@ public ActionResult Get(int id) // POST api/values [HttpPost] - public void Post([FromBody] string value) + public async Task Post(CancellationToken cancellationToken = default) { + using (MiniProfiler.Current.Step("異動資料庫")) + { + await using var db = this._employeeDbContextFactory.CreateDbContext(); + + var toDb = new Employee + { + Id = Guid.NewGuid(), + CreateAt = DateTimeOffset.Now, + CreateBy = Faker.Name.FullName(), + Age = Faker.RandomNumber.Next(1, 100), + Name = Faker.Name.Suffix(), + }; + db.Employees.Add(toDb); + await db.SaveChangesAsync(cancellationToken); + return this.Ok(toDb); + } } // PUT api/values/5 @@ -72,11 +75,5 @@ public void Post([FromBody] string value) public void Put(int id, [FromBody] string value) { } - - // DELETE api/values/5 - [HttpDelete("{id}")] - public void Delete(int id) - { - } } -} +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Lab.NETMiniProfiler.ASPNetCore5.csproj b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Lab.NETMiniProfiler.ASPNetCore5.csproj index 44130051..98d92d65 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Lab.NETMiniProfiler.ASPNetCore5.csproj +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Lab.NETMiniProfiler.ASPNetCore5.csproj @@ -8,6 +8,7 @@ + diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json index be2ddc83..05ed2abd 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json @@ -7,9 +7,6 @@ "applicationUrl": "http://localhost:41185", "sslPort": 44361 }, - "environmentVariables": { - "EMPLOYEE_DB_CONNECTION_STR": "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True" - } }, "profiles": { "Lab.NETMiniProfiler.ASPNetCore5": { @@ -19,7 +16,8 @@ "launchUrl": "swagger", "applicationUrl": "https://localhost:7186;http://localhost:5186", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "EMPLOYEE_DB_CONNECTION_STR": "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True" } }, "IIS Express": { @@ -27,7 +25,8 @@ "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "EMPLOYEE_DB_CONNECTION_STR": "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True" } } } diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs index 46794e32..fd257d87 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs @@ -1,32 +1,19 @@ +using System.Diagnostics; using System.Reflection; using Lab.NETMiniProfiler.Infrastructure.EFCore5; +using Lab.NETMiniProfiler.Infrastructure.EFCore5.EntityModel; +using Microsoft.EntityFrameworkCore; using Microsoft.OpenApi.Models; namespace Lab.NETMiniProfiler.ASPNetCore5 { public class Startup { - public Startup(IConfiguration configuration) - { - this.Configuration = configuration; - } - public IConfiguration Configuration { get; } - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) + public Startup(IConfiguration configuration) { - services.AddControllers(); - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" }); - }); - - services.AddMiniProfiler(options => - options.RouteBasePath = "/profiler" - ); - services.AddAppEnvironment(); - services.AddEntityFramework(); + this.Configuration = configuration; } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -35,9 +22,9 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); - app.UseMiniProfiler(); app.UseSwagger(); + //app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1")); app.UseSwaggerUI(c => { @@ -48,18 +35,45 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) .Assembly .GetManifestResourceStream("Lab.NETMiniProfiler.ASPNetCore5.index.html"); }); + + app.UseMiniProfiler(); } + VerifyDbConnection(app); + app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); - app.UseEndpoints(endpoints => + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + services.AddSwaggerGen(c => { - endpoints.MapControllers(); + c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" }); }); + + services.AddMiniProfiler(o => o.RouteBasePath = "/profiler") + .AddEntityFramework(); + services.AddAppEnvironment(); + services.AddEntityFramework(); + } + + private static void VerifyDbConnection(IApplicationBuilder app) + { + var employeeDbContextFactory = + app.ApplicationServices.GetService>(); + var db = employeeDbContextFactory.CreateDbContext(); + if (db.Database.CanConnect()) + { + Debug.WriteLine("資料庫已連線"); + } } } -} +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppEnvironmentOption.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppEnvironmentOption.cs index dcc2f14d..fb820c36 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppEnvironmentOption.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppEnvironmentOption.cs @@ -21,11 +21,7 @@ public string EmployeeDbConnectionString } } - private string _employeeDbConnectionString; private readonly string EMPLOYEE_DB_CONN_STR = "EMPLOYEE_DB_CONNECTION_STR"; - public void Initial() - { - var memberDbConnectionString = this.EmployeeDbConnectionString; - } + private string _employeeDbConnectionString; } \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/EmployeeDbContext.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/EmployeeDbContext.cs index 8de5bc83..4e6b1019 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/EmployeeDbContext.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/EmployeeDbContext.cs @@ -30,7 +30,14 @@ public EmployeeDbContext(DbContextOptions options) { Console.WriteLine( $"EmployeeDbContext of connection string be '{sqlOptions.ConnectionString}'"); - this.Database.Migrate(); + if (this.Database.CanConnect() == false) + { + this.Database.EnsureCreated(); + } + else + { + this.Database.Migrate(); + } } s_migrated[0] = true; @@ -45,21 +52,21 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { p.HasKey(e => e.Id) .IsClustered(false); - + p.HasIndex(e => e.SequenceId) .IsUnique() .IsClustered(); - + p.Property(p => p.Remark) .IsRequired(false) - ; + ; }); modelBuilder.Entity(p => { p.HasKey(e => e.Employee_Id) .IsClustered(false); - + p.HasIndex(e => e.SequenceId) .IsUnique() .IsClustered(); diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/Lab.NETMiniProfiler.Infrastructure.EFCore5.csproj b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/Lab.NETMiniProfiler.Infrastructure.EFCore5.csproj index d2a6aa4b..60ea4efc 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/Lab.NETMiniProfiler.Infrastructure.EFCore5.csproj +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/Lab.NETMiniProfiler.Infrastructure.EFCore5.csproj @@ -12,6 +12,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln index 2cae23eb..c174fe2d 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln @@ -7,6 +7,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lab.NETMiniProfiler.ASPNetC EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.NETMiniProfiler.Infrastructure.EFCore5", "Lab.NETMiniProfiler.Infrastructure.EFCore5\Lab.NETMiniProfiler.Infrastructure.EFCore5.csproj", "{083D436C-B451-4BCE-8A97-E6E77B9F9A23}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8E1AD56E-E673-4533-B933-3712BD42BD4D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{BF639261-D8D3-4F57-8682-C0262A5AFE04}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -28,4 +35,8 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1A39E53A-25EB-4546-9E76-DA1904FE5DCA} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {8D4D97D5-C7F1-4BD7-9FE8-7A3766DB8D04} = {8E1AD56E-E673-4533-B933-3712BD42BD4D} + {083D436C-B451-4BCE-8A97-E6E77B9F9A23} = {8E1AD56E-E673-4533-B933-3712BD42BD4D} + EndGlobalSection EndGlobal diff --git a/Benchmark/Lab.NETMiniProfiler/docker-compose.yml b/Benchmark/Lab.NETMiniProfiler/docker-compose.yml new file mode 100644 index 00000000..8ecb1567 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.8" + +services: + db-sql: + image: mcr.microsoft.com/mssql/server:2019-latest + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=pass@w0rd1~ + ports: + - 1433:1433 \ No newline at end of file From 41fa6a043c48af667dc172100e3a59665ef898a5 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 16 Jan 2022 13:03:19 +0800 Subject: [PATCH 135/424] =?UTF-8?q?feat:=20add=20=E5=A4=A7=E8=B1=A1?= =?UTF-8?q?=E8=B3=87=E6=96=99=E5=BA=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/WeatherForecastController.cs | 34 ----------- .../Properties/launchSettings.json | 5 +- .../WeatherForecast.cs | 13 ---- .../AppDependencyInjectionExtensions.cs | 59 ++++++++++--------- .../EntityModel/EmployeeDbContext.cs | 17 +++--- ...MiniProfiler.Infrastructure.EFCore5.csproj | 1 + .../Lab.NETMiniProfiler/docker-compose.yml | 11 +++- 7 files changed, 53 insertions(+), 87 deletions(-) delete mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/WeatherForecastController.cs delete mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/WeatherForecast.cs diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/WeatherForecastController.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/WeatherForecastController.cs deleted file mode 100644 index f1a485f1..00000000 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace Lab.NETMiniProfiler.ASPNetCore5.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - this._logger = logger; - } - - [HttpGet] - public IEnumerable Get() - { - var rng = new Random(); - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateTime.Now.AddDays(index), - TemperatureC = rng.Next(-20, 55), - Summary = Summaries[rng.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json index 05ed2abd..e71f1320 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json @@ -6,7 +6,7 @@ "iisExpress": { "applicationUrl": "http://localhost:41185", "sslPort": 44361 - }, + } }, "profiles": { "Lab.NETMiniProfiler.ASPNetCore5": { @@ -17,7 +17,8 @@ "applicationUrl": "https://localhost:7186;http://localhost:5186", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "EMPLOYEE_DB_CONNECTION_STR": "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True" + "EMPLOYEE_DB_CONNECTION_STR": "Host=localhost;Port=5432;Database=member_service;Username=postgres;Password=guest", + "//EMPLOYEE_DB_CONNECTION_STR": "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True" } }, "IIS Express": { diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/WeatherForecast.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/WeatherForecast.cs deleted file mode 100644 index de49d97c..00000000 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/WeatherForecast.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Lab.NETMiniProfiler.ASPNetCore5 -{ - public class WeatherForecast - { - public DateTime Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(this.TemperatureC / 0.5556); - - public string Summary { get; set; } - } -} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppDependencyInjectionExtensions.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppDependencyInjectionExtensions.cs index 22a801f0..40a1fb3f 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppDependencyInjectionExtensions.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppDependencyInjectionExtensions.cs @@ -1,7 +1,9 @@ using Lab.NETMiniProfiler.Infrastructure.EFCore5.EntityModel; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; namespace Lab.NETMiniProfiler.Infrastructure.EFCore5; @@ -9,44 +11,45 @@ public static class AppDependencyInjectionExtensions { public static void AddAppEnvironment(this IServiceCollection services) { - services.AddLogging(builder => - { - builder.AddConsole(); - }); + services.AddLogging(builder => { builder.AddConsole(); }); services.AddSingleton(); } public static void AddEntityFramework(this IServiceCollection services) { + // services.AddPooledDbContextFactory((provider, optionsBuilder) => + // { + // var option = provider.GetService(); + // var connectionString = option.EmployeeDbConnectionString; + // var loggerFactory = provider.GetService(); + // optionsBuilder.UseSqlServer(connectionString) + // .UseLoggerFactory(loggerFactory) + // ; + // }); + services.AddPooledDbContextFactory((provider, optionsBuilder) => { - var option = provider.GetService(); - var connectionString = option.EmployeeDbConnectionString; + // var mssqlOptions = optionsBuilder.Options.FindExtension(); + // var npgsqlOptions = optionsBuilder.Options.FindExtension(); + + var appOption = provider.GetService(); var loggerFactory = provider.GetService(); - optionsBuilder.UseSqlServer(connectionString) + var connectionString = appOption.EmployeeDbConnectionString; + // optionsBuilder.UseSqlServer(connectionString) + // .UseLoggerFactory(loggerFactory); + + optionsBuilder.UseNpgsql( + connectionString, //只會呼叫一次 + builder => + builder.EnableRetryOnFailure( + 10, + TimeSpan.FromSeconds(30), + new List { "57P01" })) + + // .UseLazyLoadingProxies() + // .UseSnakeCaseNamingConvention() .UseLoggerFactory(loggerFactory) ; }); - - ; - - // services.AddPooledDbContextFactory((provider, options) => - // { - // var option = provider.GetService(); - // var loggerFactory = provider.GetService(); - // options.UseNpgsql( - // option.MemberDbConnectionString, //只會呼叫一次 - // builder => - // builder.EnableRetryOnFailure( - // 10, - // TimeSpan.FromSeconds(30), - // new List { "57P01" })) - // - // // .UseLazyLoadingProxies() - // .UseSnakeCaseNamingConvention() - // .UseLoggerFactory(loggerFactory) - // ; - // }); } - } \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/EmployeeDbContext.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/EmployeeDbContext.cs index 4e6b1019..a98e18d5 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/EmployeeDbContext.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/EntityModel/EmployeeDbContext.cs @@ -30,14 +30,15 @@ public EmployeeDbContext(DbContextOptions options) { Console.WriteLine( $"EmployeeDbContext of connection string be '{sqlOptions.ConnectionString}'"); - if (this.Database.CanConnect() == false) - { - this.Database.EnsureCreated(); - } - else - { - this.Database.Migrate(); - } + } + + if (this.Database.CanConnect() == false) + { + this.Database.EnsureCreated(); + } + else + { + this.Database.Migrate(); } s_migrated[0] = true; diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/Lab.NETMiniProfiler.Infrastructure.EFCore5.csproj b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/Lab.NETMiniProfiler.Infrastructure.EFCore5.csproj index 60ea4efc..8f5315eb 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/Lab.NETMiniProfiler.Infrastructure.EFCore5.csproj +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/Lab.NETMiniProfiler.Infrastructure.EFCore5.csproj @@ -13,5 +13,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Benchmark/Lab.NETMiniProfiler/docker-compose.yml b/Benchmark/Lab.NETMiniProfiler/docker-compose.yml index 8ecb1567..80c43a60 100644 --- a/Benchmark/Lab.NETMiniProfiler/docker-compose.yml +++ b/Benchmark/Lab.NETMiniProfiler/docker-compose.yml @@ -1,10 +1,17 @@ version: "3.8" services: - db-sql: + db-mssql: image: mcr.microsoft.com/mssql/server:2019-latest environment: - ACCEPT_EULA=Y - SA_PASSWORD=pass@w0rd1~ ports: - - 1433:1433 \ No newline at end of file + - 1433:1433 + + db-postgres: + image: postgres:12-alpine + environment: + - POSTGRES_PASSWORD=guest + ports: + - 5432:5432 \ No newline at end of file From 7084f4b44d12fa0f4589f4da28bcca656ddb28a0 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 16 Jan 2022 22:05:45 +0800 Subject: [PATCH 136/424] add db type --- .../Properties/launchSettings.json | 4 +- .../AppDependencyInjectionExtensions.cs | 38 ++++++++++++------- .../AppEnvironmentOption.cs | 26 +++++++++++++ 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json index e71f1320..2c7f3c3c 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Properties/launchSettings.json @@ -18,7 +18,9 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "EMPLOYEE_DB_CONNECTION_STR": "Host=localhost;Port=5432;Database=member_service;Username=postgres;Password=guest", - "//EMPLOYEE_DB_CONNECTION_STR": "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True" + "DB_TYPE": "postgresSQL", + "//EMPLOYEE_DB_CONNECTION_STR": "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True", + "//DB_RTPE": "MsSQL" } }, "IIS Express": { diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppDependencyInjectionExtensions.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppDependencyInjectionExtensions.cs index 40a1fb3f..e42e5a84 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppDependencyInjectionExtensions.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppDependencyInjectionExtensions.cs @@ -1,4 +1,5 @@ using Lab.NETMiniProfiler.Infrastructure.EFCore5.EntityModel; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; using Microsoft.Extensions.DependencyInjection; @@ -6,7 +7,6 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; namespace Lab.NETMiniProfiler.Infrastructure.EFCore5; - public static class AppDependencyInjectionExtensions { public static void AddAppEnvironment(this IServiceCollection services) @@ -35,21 +35,31 @@ public static void AddEntityFramework(this IServiceCollection services) var appOption = provider.GetService(); var loggerFactory = provider.GetService(); var connectionString = appOption.EmployeeDbConnectionString; - // optionsBuilder.UseSqlServer(connectionString) - // .UseLoggerFactory(loggerFactory); + - optionsBuilder.UseNpgsql( - connectionString, //只會呼叫一次 - builder => - builder.EnableRetryOnFailure( - 10, - TimeSpan.FromSeconds(30), - new List { "57P01" })) + switch (appOption.DatabaseType) + { + case DatabaseType.MsSql: + optionsBuilder.UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory); + break; + case DatabaseType.PostgresSQL: + optionsBuilder.UseNpgsql( + connectionString, //只會呼叫一次 + builder => + builder.EnableRetryOnFailure( + 10, + TimeSpan.FromSeconds(30), + new List { "57P01" })) - // .UseLazyLoadingProxies() - // .UseSnakeCaseNamingConvention() - .UseLoggerFactory(loggerFactory) - ; + // .UseLazyLoadingProxies() + // .UseSnakeCaseNamingConvention() + .UseLoggerFactory(loggerFactory) + ; + break; + default: + throw new ArgumentOutOfRangeException(); + } }); } } \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppEnvironmentOption.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppEnvironmentOption.cs index fb820c36..4f5eca78 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppEnvironmentOption.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore5/AppEnvironmentOption.cs @@ -1,7 +1,31 @@ namespace Lab.NETMiniProfiler.Infrastructure.EFCore5; +public enum DatabaseType +{ + MsSql = 1, + PostgresSQL = 2 +} + public class AppEnvironmentOption { + public DatabaseType DatabaseType + { + get + { + if (this._databaseType.HasValue == false) + { + var variable = EnvironmentAssistant.GetEnvironmentVariable(this.DATABASE_TYPE); + if (Enum.TryParse(variable,true, out DatabaseType result)) + { + this._databaseType = result; + } + } + + return this._databaseType.Value; + } + set => this._databaseType = value; + } + public string EmployeeDbConnectionString { get @@ -21,7 +45,9 @@ public string EmployeeDbConnectionString } } + private readonly string DATABASE_TYPE = "DB_TYPE"; private readonly string EMPLOYEE_DB_CONN_STR = "EMPLOYEE_DB_CONNECTION_STR"; + private DatabaseType? _databaseType; private string _employeeDbConnectionString; } \ No newline at end of file From 657348f97e9e1bf911a7969ea06372950793e760 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 16 Jan 2022 23:13:53 +0800 Subject: [PATCH 137/424] feat: add asp.net core 6 --- .../Controllers/ValuesController.cs | 4 +- .../Controllers/ValuesController.cs | 79 +++++++++++++++++++ .../Lab.NETMiniProfiler.ASPNetCore6.csproj | 20 +++++ .../Program.cs | 32 ++++++++ .../Properties/launchSettings.json | 36 +++++++++ .../appsettings.Development.json | 8 ++ .../appsettings.json | 9 +++ .../AppDependencyInjectionExtensions.cs | 61 +++++++------- .../AppEnvironmentOption.cs | 34 ++++++-- .../EntityModel/Employee.cs | 2 +- .../EntityModel/EmployeeDbContext.cs | 32 ++++---- .../EntityModel/Identity.cs | 2 +- .../EntityModel/OrderHistory.cs | 2 +- .../EnvironmentAssistant.cs | 2 +- ...MiniProfiler.Infrastructure.EFCore6.csproj | 9 ++- .../Lab.NETMiniProfiler.sln | 14 ++++ 16 files changed, 284 insertions(+), 62 deletions(-) create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Controllers/ValuesController.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Lab.NETMiniProfiler.ASPNetCore6.csproj create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Program.cs create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Properties/launchSettings.json create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/appsettings.Development.json create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/appsettings.json diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/ValuesController.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/ValuesController.cs index 811a23c0..a926f283 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/ValuesController.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Controllers/ValuesController.cs @@ -50,7 +50,7 @@ public ActionResult Get(int id) // POST api/values [HttpPost] - public async Task Post(CancellationToken cancellationToken = default) + public async Task Post(CancellationToken cancel = default) { using (MiniProfiler.Current.Step("異動資料庫")) { @@ -65,7 +65,7 @@ public async Task Post(CancellationToken cancellationToken = defa Name = Faker.Name.Suffix(), }; db.Employees.Add(toDb); - await db.SaveChangesAsync(cancellationToken); + await db.SaveChangesAsync(cancel); return this.Ok(toDb); } } diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Controllers/ValuesController.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Controllers/ValuesController.cs new file mode 100644 index 00000000..b9dd0977 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Controllers/ValuesController.cs @@ -0,0 +1,79 @@ +using Lab.NETMiniProfiler.Infrastructure.EFCore6.EntityModel; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using StackExchange.Profiling; + +namespace Lab.NETMiniProfiler.ASPNetCore6.Controllers +{ + /// + /// Value Controller + /// + [Route("[controller]")] + [ApiController] + public class ValuesController : ControllerBase + { + private readonly IDbContextFactory _employeeDbContextFactory; + + public ValuesController(IDbContextFactory employeeDbContextFactory) + { + this._employeeDbContextFactory = employeeDbContextFactory; + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + + /// + /// Get Api + /// + /// + + // GET api/values + [HttpGet] + public async Task Get(CancellationToken cancel = default) + { + using (MiniProfiler.Current.Step("查詢資料庫")) + { + await using var db = await this._employeeDbContextFactory.CreateDbContextAsync(cancel); + return this.Ok(await db.Employees.AsTracking().ToListAsync(cancel)); + } + } + + // GET api/values/5 + [HttpGet("{id}")] + public ActionResult Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public async Task Post(CancellationToken cancel = default) + { + using (MiniProfiler.Current.Step("異動資料庫")) + { + await using var db = await this._employeeDbContextFactory.CreateDbContextAsync(cancel); + + var toDb = new Employee + { + Id = Guid.NewGuid(), + CreateAt = DateTimeOffset.Now, + CreateBy = Faker.Name.FullName(), + Age = Faker.RandomNumber.Next(1, 100), + Name = Faker.Name.Suffix(), + }; + db.Employees.Add(toDb); + await db.SaveChangesAsync(cancel); + return this.Ok(toDb); + } + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Lab.NETMiniProfiler.ASPNetCore6.csproj b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Lab.NETMiniProfiler.ASPNetCore6.csproj new file mode 100644 index 00000000..9ea80629 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Lab.NETMiniProfiler.ASPNetCore6.csproj @@ -0,0 +1,20 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Program.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Program.cs new file mode 100644 index 00000000..84c81f06 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Program.cs @@ -0,0 +1,32 @@ +using Lab.NETMiniProfiler.Infrastructure.EFCore6; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddMiniProfiler(o => o.RouteBasePath = "/profiler") + .AddEntityFramework(); +builder.Services.AddAppEnvironment(); +builder.Services.AddEntityFramework(); +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); + app.UseMiniProfiler(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Properties/launchSettings.json b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Properties/launchSettings.json new file mode 100644 index 00000000..b6348197 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Properties/launchSettings.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:30924", + "sslPort": 44345 + } + }, + "profiles": { + "Lab.NETMiniProfiler.ASPNetCore6": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7139;http://localhost:5139", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "EMPLOYEE_DB_CONNECTION_STR": "Host=localhost;Port=5432;Database=member_service;Username=postgres;Password=guest", + "DB_TYPE": "postgresSQL", + "//EMPLOYEE_DB_CONNECTION_STR": "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True", + "//DB_RTPE": "MsSQL" + + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/appsettings.Development.json b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/appsettings.json b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppDependencyInjectionExtensions.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppDependencyInjectionExtensions.cs index 094946b8..7230b81e 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppDependencyInjectionExtensions.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppDependencyInjectionExtensions.cs @@ -1,18 +1,14 @@ -using Lab.NETMiniProfiler.Infrastructure.EntityModel; +using Lab.NETMiniProfiler.Infrastructure.EFCore6.EntityModel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Lab.NETMiniProfiler.Infrastructure; - +namespace Lab.NETMiniProfiler.Infrastructure.EFCore6; public static class AppDependencyInjectionExtensions { public static void AddAppEnvironment(this IServiceCollection services) { - services.AddLogging(builder => - { - builder.AddConsole(); - }); + services.AddLogging(builder => { builder.AddConsole(); }); services.AddSingleton(); } @@ -20,33 +16,34 @@ public static void AddEntityFramework(this IServiceCollection services) { services.AddPooledDbContextFactory((provider, optionsBuilder) => { - var option = provider.GetService(); - var connectionString = option.EmployeeDbConnectionString; + var appOption = provider.GetService(); var loggerFactory = provider.GetService(); - optionsBuilder.UseSqlServer(connectionString) - .UseLoggerFactory(loggerFactory) - ; - }); + var connectionString = appOption.EmployeeDbConnectionString; + - ; + switch (appOption.DatabaseType) + { + case DatabaseType.MsSql: + optionsBuilder.UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory); + break; + case DatabaseType.PostgresSQL: + optionsBuilder.UseNpgsql( + connectionString, //只會呼叫一次 + builder => + builder.EnableRetryOnFailure( + 10, + TimeSpan.FromSeconds(30), + new List { "57P01" })) - // services.AddPooledDbContextFactory((provider, options) => - // { - // var option = provider.GetService(); - // var loggerFactory = provider.GetService(); - // options.UseNpgsql( - // option.MemberDbConnectionString, //只會呼叫一次 - // builder => - // builder.EnableRetryOnFailure( - // 10, - // TimeSpan.FromSeconds(30), - // new List { "57P01" })) - // - // // .UseLazyLoadingProxies() - // .UseSnakeCaseNamingConvention() - // .UseLoggerFactory(loggerFactory) - // ; - // }); + // .UseLazyLoadingProxies() + // .UseSnakeCaseNamingConvention() + .UseLoggerFactory(loggerFactory) + ; + break; + default: + throw new ArgumentOutOfRangeException(); + } + }); } - } \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppEnvironmentOption.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppEnvironmentOption.cs index 42da1ef1..6614f3be 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppEnvironmentOption.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/AppEnvironmentOption.cs @@ -1,7 +1,31 @@ -namespace Lab.NETMiniProfiler.Infrastructure; +namespace Lab.NETMiniProfiler.Infrastructure.EFCore6; + +public enum DatabaseType +{ + MsSql = 1, + PostgresSQL = 2 +} public class AppEnvironmentOption { + public DatabaseType DatabaseType + { + get + { + if (this._databaseType.HasValue == false) + { + var variable = EnvironmentAssistant.GetEnvironmentVariable(this.DATABASE_TYPE); + if (Enum.TryParse(variable,true, out DatabaseType result)) + { + this._databaseType = result; + } + } + + return this._databaseType.Value; + } + set => this._databaseType = value; + } + public string EmployeeDbConnectionString { get @@ -21,11 +45,9 @@ public string EmployeeDbConnectionString } } - private string _employeeDbConnectionString; + private readonly string DATABASE_TYPE = "DB_TYPE"; private readonly string EMPLOYEE_DB_CONN_STR = "EMPLOYEE_DB_CONNECTION_STR"; + private DatabaseType? _databaseType; - public void Initial() - { - var memberDbConnectionString = this.EmployeeDbConnectionString; - } + private string _employeeDbConnectionString; } \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Employee.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Employee.cs index cf11b247..1799f47d 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Employee.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Employee.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Lab.NETMiniProfiler.Infrastructure.EntityModel +namespace Lab.NETMiniProfiler.Infrastructure.EFCore6.EntityModel { [Table("Employee")] public class Employee diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/EmployeeDbContext.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/EmployeeDbContext.cs index f07f9b6b..14beaacf 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/EmployeeDbContext.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/EmployeeDbContext.cs @@ -1,8 +1,7 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; -namespace Lab.NETMiniProfiler.Infrastructure.EntityModel +namespace Lab.NETMiniProfiler.Infrastructure.EFCore6.EntityModel { public class EmployeeDbContext : DbContext { @@ -26,17 +25,20 @@ public EmployeeDbContext(DbContextOptions options) { if (s_migrated[0] == false) { - var memoryOptions = options.FindExtension(); + var sqlOptions = options.FindExtension(); + if (sqlOptions != null) + { + Console.WriteLine( + $"EmployeeDbContext of connection string be '{sqlOptions.ConnectionString}'"); + } - if (memoryOptions == null) + if (this.Database.CanConnect() == false) + { + this.Database.EnsureCreated(); + } + else { - var sqlOptions = options.FindExtension(); - if (sqlOptions != null) - { - Console.WriteLine( - $"EmployeeDbContext of connection string be '{sqlOptions.ConnectionString}'"); - this.Database.Migrate(); - } + this.Database.Migrate(); } s_migrated[0] = true; @@ -51,21 +53,21 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { p.HasKey(e => e.Id) .IsClustered(false); - + p.HasIndex(e => e.SequenceId) .IsUnique() .IsClustered(); - + p.Property(p => p.Remark) .IsRequired(false) - ; + ; }); modelBuilder.Entity(p => { p.HasKey(e => e.Employee_Id) .IsClustered(false); - + p.HasIndex(e => e.SequenceId) .IsUnique() .IsClustered(); diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Identity.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Identity.cs index 57b2a437..47f791e7 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Identity.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/Identity.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Lab.NETMiniProfiler.Infrastructure.EntityModel +namespace Lab.NETMiniProfiler.Infrastructure.EFCore6.EntityModel { [Table("Identity")] public class Identity diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/OrderHistory.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/OrderHistory.cs index 63c19ce4..f582242b 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/OrderHistory.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EntityModel/OrderHistory.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Lab.NETMiniProfiler.Infrastructure.EntityModel +namespace Lab.NETMiniProfiler.Infrastructure.EFCore6.EntityModel { [Table("OrderHistory")] public class OrderHistory diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EnvironmentAssistant.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EnvironmentAssistant.cs index 7f2ff85c..bc7c8f51 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EnvironmentAssistant.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/EnvironmentAssistant.cs @@ -1,4 +1,4 @@ -namespace Lab.NETMiniProfiler.Infrastructure; +namespace Lab.NETMiniProfiler.Infrastructure.EFCore6; public class EnvironmentAssistant { diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/Lab.NETMiniProfiler.Infrastructure.EFCore6.csproj b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/Lab.NETMiniProfiler.Infrastructure.EFCore6.csproj index c5a492b3..2af7dc9f 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/Lab.NETMiniProfiler.Infrastructure.EFCore6.csproj +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.Infrastructure.EFCore6/Lab.NETMiniProfiler.Infrastructure.EFCore6.csproj @@ -4,12 +4,15 @@ net6.0 enable enable - Lab.NETMiniProfiler.Infrastructure.EFCore6 + 10 - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln index c174fe2d..5f6fca96 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.sln @@ -14,6 +14,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{BF6392 docker-compose.yml = docker-compose.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.NETMiniProfiler.ASPNetCore6", "Lab.NETMiniProfiler.ASPNetCore6\Lab.NETMiniProfiler.ASPNetCore6.csproj", "{A677D49D-8088-44DD-9900-FC7694C30D70}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.NETMiniProfiler.Infrastructure.EFCore6", "Lab.NETMiniProfiler.Infrastructure.EFCore6\Lab.NETMiniProfiler.Infrastructure.EFCore6.csproj", "{D4CB746B-5711-4338-B716-763A86822134}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -28,6 +32,14 @@ Global {083D436C-B451-4BCE-8A97-E6E77B9F9A23}.Debug|Any CPU.Build.0 = Debug|Any CPU {083D436C-B451-4BCE-8A97-E6E77B9F9A23}.Release|Any CPU.ActiveCfg = Release|Any CPU {083D436C-B451-4BCE-8A97-E6E77B9F9A23}.Release|Any CPU.Build.0 = Release|Any CPU + {A677D49D-8088-44DD-9900-FC7694C30D70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A677D49D-8088-44DD-9900-FC7694C30D70}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A677D49D-8088-44DD-9900-FC7694C30D70}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A677D49D-8088-44DD-9900-FC7694C30D70}.Release|Any CPU.Build.0 = Release|Any CPU + {D4CB746B-5711-4338-B716-763A86822134}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4CB746B-5711-4338-B716-763A86822134}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4CB746B-5711-4338-B716-763A86822134}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4CB746B-5711-4338-B716-763A86822134}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -38,5 +50,7 @@ Global GlobalSection(NestedProjects) = preSolution {8D4D97D5-C7F1-4BD7-9FE8-7A3766DB8D04} = {8E1AD56E-E673-4533-B933-3712BD42BD4D} {083D436C-B451-4BCE-8A97-E6E77B9F9A23} = {8E1AD56E-E673-4533-B933-3712BD42BD4D} + {A677D49D-8088-44DD-9900-FC7694C30D70} = {8E1AD56E-E673-4533-B933-3712BD42BD4D} + {D4CB746B-5711-4338-B716-763A86822134} = {8E1AD56E-E673-4533-B933-3712BD42BD4D} EndGlobalSection EndGlobal From 47bb47ed8fe87fb9585a093dc67a9ef48ea5f7e3 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 16 Jan 2022 23:21:12 +0800 Subject: [PATCH 138/424] =?UTF-8?q?MiniProfiler=20=E6=95=B4=E5=90=88=20Swa?= =?UTF-8?q?gger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Startup.cs | 4 +- .../Lab.NETMiniProfiler.ASPNetCore6.csproj | 4 + .../Program.cs | 27 ++++- .../index.html | 99 +++++++++++++++++++ 4 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/index.html diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs index fd257d87..8d925b6b 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore5/Startup.cs @@ -39,7 +39,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseMiniProfiler(); } - VerifyDbConnection(app); + PreConnectionDb(app); app.UseHttpsRedirection(); @@ -65,7 +65,7 @@ public void ConfigureServices(IServiceCollection services) services.AddEntityFramework(); } - private static void VerifyDbConnection(IApplicationBuilder app) + private static void PreConnectionDb(IApplicationBuilder app) { var employeeDbContextFactory = app.ApplicationServices.GetService>(); diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Lab.NETMiniProfiler.ASPNetCore6.csproj b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Lab.NETMiniProfiler.ASPNetCore6.csproj index 9ea80629..de1b6b14 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Lab.NETMiniProfiler.ASPNetCore6.csproj +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Lab.NETMiniProfiler.ASPNetCore6.csproj @@ -17,4 +17,8 @@ + + + + diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Program.cs b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Program.cs index 84c81f06..add4dbc4 100644 --- a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Program.cs +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/Program.cs @@ -1,4 +1,8 @@ +using System.Diagnostics; +using System.Reflection; using Lab.NETMiniProfiler.Infrastructure.EFCore6; +using Lab.NETMiniProfiler.Infrastructure.EFCore6.EntityModel; +using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); @@ -14,12 +18,21 @@ builder.Services.AddAppEnvironment(); builder.Services.AddEntityFramework(); var app = builder.Build(); +PreConnectionDb(app); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); - app.UseSwaggerUI(); + // app.UseSwaggerUI(); + app.UseSwaggerUI(c => + { + c.RoutePrefix = "swagger"; + c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); + c.IndexStream = () => typeof(Program).GetTypeInfo() + .Assembly + .GetManifestResourceStream("Lab.NETMiniProfiler.ASPNetCore6.index.html"); + }); app.UseMiniProfiler(); } @@ -28,5 +41,15 @@ app.UseAuthorization(); app.MapControllers(); - app.Run(); + +static void PreConnectionDb(IApplicationBuilder app) +{ + var employeeDbContextFactory = + app.ApplicationServices.GetService>(); + var db = employeeDbContextFactory.CreateDbContext(); + if (db.Database.CanConnect()) + { + Debug.WriteLine("資料庫已連線"); + } +} \ No newline at end of file diff --git a/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/index.html b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/index.html new file mode 100644 index 00000000..49c7aa3d --- /dev/null +++ b/Benchmark/Lab.NETMiniProfiler/Lab.NETMiniProfiler.ASPNetCore6/index.html @@ -0,0 +1,99 @@ + + + + + + + + %(DocumentTitle) + + + + + + + %(HeadContent) + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + From 036dfd3dddd392635caf3a9493e0e083b4673d5e Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 17 Jan 2022 23:12:48 +0800 Subject: [PATCH 139/424] remove file --- .../Controllers/ValueController.cs | 54 ---------- .../Controllers/WeatherForecastController.cs | 39 -------- .../WebApplication1/Program.cs | 26 ----- .../Properties/launchSettings.json | 31 ------ .../WebApplication1/Startup.cs | 74 -------------- .../WebApplication1/WeatherForecast.cs | 15 --- .../WebApplication1/WebApplication1.csproj | 21 ---- .../appsettings.Development.json | 9 -- .../WebApplication1/appsettings.json | 10 -- .../WebApplication1/index.html | 99 ------------------- 10 files changed, 378 deletions(-) delete mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/ValueController.cs delete mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/WeatherForecastController.cs delete mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/Program.cs delete mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/Properties/launchSettings.json delete mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/Startup.cs delete mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/WeatherForecast.cs delete mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/WebApplication1.csproj delete mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.Development.json delete mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.json delete mode 100644 Benchmark/Lab.NETMiniProfiler/WebApplication1/index.html diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/ValueController.cs b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/ValueController.cs deleted file mode 100644 index 7ff8d74c..00000000 --- a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/ValueController.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using StackExchange.Profiling; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace WebApplication1.Controllers -{ - [ApiController] - [Route("[controller]")] - public class ValueController : ControllerBase - { - [HttpGet] - public IEnumerable Get() - { - string url1 = string.Empty; - string url2 = string.Empty; - using (MiniProfiler.Current.Step("Get方法")) - { - using (MiniProfiler.Current.Step("准备数据")) - { - using (MiniProfiler.Current.CustomTiming("SQL", "SELECT * FROM Config")) - { - // 模拟一个SQL查询 - Thread.Sleep(500); - - url1 = "https://www.baidu.com"; - url2 = "https://www.sina.com.cn/"; - } - } - - - using (MiniProfiler.Current.Step("使用从数据库中查询的数据,进行Http请求")) - { - using (MiniProfiler.Current.CustomTiming("HTTP", "GET " + url1)) - { - var client = new WebClient(); - var reply = client.DownloadString(url1); - } - - using (MiniProfiler.Current.CustomTiming("HTTP", "GET " + url2)) - { - var client = new WebClient(); - var reply = client.DownloadString(url2); - } - } - } - return new string[] { "value1", "value2" }; - } - } -} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/WeatherForecastController.cs b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/WeatherForecastController.cs deleted file mode 100644 index 47134b11..00000000 --- a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace WebApplication1.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet] - public IEnumerable Get() - { - var rng = new Random(); - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateTime.Now.AddDays(index), - TemperatureC = rng.Next(-20, 55), - Summary = Summaries[rng.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Program.cs b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Program.cs deleted file mode 100644 index 53f3427e..00000000 --- a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Program.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace WebApplication1 -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Properties/launchSettings.json b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Properties/launchSettings.json deleted file mode 100644 index 1496716e..00000000 --- a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Properties/launchSettings.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:64791", - "sslPort": 44397 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "WebApplication1": { - "commandName": "Project", - "dotnetRunMessages": "true", - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Startup.cs b/Benchmark/Lab.NETMiniProfiler/WebApplication1/Startup.cs deleted file mode 100644 index 10a9665d..00000000 --- a/Benchmark/Lab.NETMiniProfiler/WebApplication1/Startup.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.OpenApi.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; - -namespace WebApplication1 -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" }); - }); - - services.AddMiniProfiler(options => - options.RouteBasePath = "/profiler" - ); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseMiniProfiler(); - - app.UseSwagger(); - //app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1")); - app.UseSwaggerUI(c => - { - c.RoutePrefix = "swagger"; - c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); - c.IndexStream = () => GetType() - .GetTypeInfo() - .Assembly - .GetManifestResourceStream("WebApplication1.index.html"); - }); - } - - app.UseHttpsRedirection(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - } - } -} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/WeatherForecast.cs b/Benchmark/Lab.NETMiniProfiler/WebApplication1/WeatherForecast.cs deleted file mode 100644 index 11a0296b..00000000 --- a/Benchmark/Lab.NETMiniProfiler/WebApplication1/WeatherForecast.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace WebApplication1 -{ - public class WeatherForecast - { - public DateTime Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string Summary { get; set; } - } -} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/WebApplication1.csproj b/Benchmark/Lab.NETMiniProfiler/WebApplication1/WebApplication1.csproj deleted file mode 100644 index cd66119c..00000000 --- a/Benchmark/Lab.NETMiniProfiler/WebApplication1/WebApplication1.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - net5.0 - - - - - - - - - - - - - - - - - diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.Development.json b/Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.Development.json deleted file mode 100644 index 8983e0fc..00000000 --- a/Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.json b/Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.json deleted file mode 100644 index d9d9a9bf..00000000 --- a/Benchmark/Lab.NETMiniProfiler/WebApplication1/appsettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*" -} diff --git a/Benchmark/Lab.NETMiniProfiler/WebApplication1/index.html b/Benchmark/Lab.NETMiniProfiler/WebApplication1/index.html deleted file mode 100644 index 49c7aa3d..00000000 --- a/Benchmark/Lab.NETMiniProfiler/WebApplication1/index.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - %(DocumentTitle) - - - - - - - %(HeadContent) - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - From 720e9f2c5c879469f7a298d97faa82247053a6a4 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 19 Jan 2022 02:26:59 +0800 Subject: [PATCH 140/424] feat: add new project --- .../Lab.MemoryConfigSource.TestProject.csproj | 19 +++++++ .../UnitTest1.cs | 51 +++++++++++++++++++ .../Lab.MemoryConfigSource.sln | 16 ++++++ 3 files changed, 86 insertions(+) create mode 100644 Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/Lab.MemoryConfigSource.TestProject.csproj create mode 100644 Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/UnitTest1.cs create mode 100644 Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.sln diff --git a/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/Lab.MemoryConfigSource.TestProject.csproj b/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/Lab.MemoryConfigSource.TestProject.csproj new file mode 100644 index 00000000..c752da40 --- /dev/null +++ b/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/Lab.MemoryConfigSource.TestProject.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + diff --git a/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/UnitTest1.cs b/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/UnitTest1.cs new file mode 100644 index 00000000..83eec006 --- /dev/null +++ b/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/UnitTest1.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.MemoryConfigSource.TestProject; + +public enum Gender +{ + Male, + Female +} + +public class Member +{ + public string Id { get; set; } + + public Profile Profile { get; set; } +} + +public class Profile +{ + public Gender Gender { get; set; } + + public int Age { get; set; } + + public string Address { get; set; } +} + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void 綁定複雜型別() + { + var source = new Dictionary + { + ["id"] = "9527", + ["profile:gender"] = "Male", + ["profile:age"] = "18", + ["profile:address"] = "Taipei", + }; + var builder = new ConfigurationBuilder(); + var configRoot = builder.AddInMemoryCollection(source).Build(); + var member = configRoot.Get(); + + Assert.AreEqual("9527", member.Id); + Assert.AreEqual(18, member.Profile.Age); + Assert.AreEqual("Taipei", member.Profile.Address); + Assert.AreEqual(Gender.Male, member.Profile.Gender); + } +} \ No newline at end of file diff --git a/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.sln b/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.sln new file mode 100644 index 00000000..b6a84778 --- /dev/null +++ b/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.MemoryConfigSource.TestProject", "Lab.MemoryConfigSource.TestProject\Lab.MemoryConfigSource.TestProject.csproj", "{531A25A6-0836-4950-85B3-3882B19FD18F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {531A25A6-0836-4950-85B3-3882B19FD18F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {531A25A6-0836-4950-85B3-3882B19FD18F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {531A25A6-0836-4950-85B3-3882B19FD18F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {531A25A6-0836-4950-85B3-3882B19FD18F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From a497b26f319ce2313c74f6809c72bc1c1460ca7e Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 19 Jan 2022 08:46:39 +0800 Subject: [PATCH 141/424] rename --- .../Lab.ConfigBind.TestProject.csproj} | 6 +- .../Lab.ConfigBind.TestProject/UnitTest1.cs | 102 ++++++++++++++++++ .../Lab.ConfigBind/Lab.ConfigBind.sln | 16 +++ .../UnitTest1.cs | 51 --------- .../Lab.MemoryConfigSource.sln | 16 --- 5 files changed, 122 insertions(+), 69 deletions(-) rename Configuration/{Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/Lab.MemoryConfigSource.TestProject.csproj => Lab.ConfigBind/Lab.ConfigBind.TestProject/Lab.ConfigBind.TestProject.csproj} (86%) create mode 100644 Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/UnitTest1.cs create mode 100644 Configuration/Lab.ConfigBind/Lab.ConfigBind.sln delete mode 100644 Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/UnitTest1.cs delete mode 100644 Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.sln diff --git a/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/Lab.MemoryConfigSource.TestProject.csproj b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/Lab.ConfigBind.TestProject.csproj similarity index 86% rename from Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/Lab.MemoryConfigSource.TestProject.csproj rename to Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/Lab.ConfigBind.TestProject.csproj index c752da40..67c4441e 100644 --- a/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/Lab.MemoryConfigSource.TestProject.csproj +++ b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/Lab.ConfigBind.TestProject.csproj @@ -8,12 +8,14 @@ - - + + + + diff --git a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/UnitTest1.cs b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/UnitTest1.cs new file mode 100644 index 00000000..4507739c --- /dev/null +++ b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/UnitTest1.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.ConfigBind.TestProject; + + + +[TestClass] +public class UnitTest1 +{ + public enum Gender + { + Male, + Female + } + + public class Member + { + public string Id { get; set; } + + public Profile Profile { get; set; } + } + + public class Profile + { + public Gender? Gender { get; set; } + + public int? Age { get; set; } + + public string Address { get; set; } + } + + [TestMethod] + public void 綁定複雜型別() + { + var source = new Dictionary + { + ["id"] = "9527", + ["profile:gender"] = "Male", + ["profile:age"] = "18", + ["profile:address"] = "Taipei", + }; + + var builder = new ConfigurationBuilder(); + var configRoot = builder.AddInMemoryCollection(source).Build(); + var member = configRoot.Get(); + + Assert.AreEqual("9527", member.Id); + Assert.AreEqual(18, member.Profile.Age); + Assert.AreEqual("Taipei", member.Profile.Address); + Assert.AreEqual(Gender.Male, member.Profile.Gender); + } + [TestMethod] + public void 綁定集合() + { + var source = new Dictionary + { + ["a:id"] = "9527", + ["a:profile:gender"] = "Male", + ["a:profile:age"] = "18", + ["a:profile:address"] = "Taipei", + ["b:id"] = "9528", + ["b:profile:gender"] = "Male", + ["b:profile:age"] = "19", + ["b:profile:address"] = "Taipei", + + }; + + var builder = new ConfigurationBuilder(); + var configRoot = builder.AddInMemoryCollection(source).Build(); + var member = configRoot.Get>(); + + Assert.AreEqual("9527", member[0].Id); + Assert.AreEqual("9528", member[1].Id); + } + + [TestMethod] + public void 綁定環境變數() + { + var source = new Dictionary + { + ["a:id"] = "9527", + ["a:profile:gender"] = "Male", + ["a:profile:age"] = "18", + ["a:profile:address"] = "Taipei", + ["b:id"] = "9528", + ["b:profile:gender"] = "Male", + ["b:profile:age"] = "19", + ["b:profile:address"] = "Taipei", + + }; + var builder = new ConfigurationBuilder(); + builder.AddEnvironmentVariables(); + var configRoot = builder.AddInMemoryCollection(source).Build(); + var member = configRoot.Get>(); + + Assert.AreEqual("9527", member[0].Id); + Assert.AreEqual("9528", member[1].Id); + } + +} \ No newline at end of file diff --git a/Configuration/Lab.ConfigBind/Lab.ConfigBind.sln b/Configuration/Lab.ConfigBind/Lab.ConfigBind.sln new file mode 100644 index 00000000..10724763 --- /dev/null +++ b/Configuration/Lab.ConfigBind/Lab.ConfigBind.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ConfigBind.TestProject", "Lab.ConfigBind.TestProject\Lab.ConfigBind.TestProject.csproj", "{3733D824-84BE-4993-A321-7DECC340FA64}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3733D824-84BE-4993-A321-7DECC340FA64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3733D824-84BE-4993-A321-7DECC340FA64}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3733D824-84BE-4993-A321-7DECC340FA64}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3733D824-84BE-4993-A321-7DECC340FA64}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/UnitTest1.cs b/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/UnitTest1.cs deleted file mode 100644 index 83eec006..00000000 --- a/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.TestProject/UnitTest1.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Collections.Generic; -using Microsoft.Extensions.Configuration; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Lab.MemoryConfigSource.TestProject; - -public enum Gender -{ - Male, - Female -} - -public class Member -{ - public string Id { get; set; } - - public Profile Profile { get; set; } -} - -public class Profile -{ - public Gender Gender { get; set; } - - public int Age { get; set; } - - public string Address { get; set; } -} - -[TestClass] -public class UnitTest1 -{ - [TestMethod] - public void 綁定複雜型別() - { - var source = new Dictionary - { - ["id"] = "9527", - ["profile:gender"] = "Male", - ["profile:age"] = "18", - ["profile:address"] = "Taipei", - }; - var builder = new ConfigurationBuilder(); - var configRoot = builder.AddInMemoryCollection(source).Build(); - var member = configRoot.Get(); - - Assert.AreEqual("9527", member.Id); - Assert.AreEqual(18, member.Profile.Age); - Assert.AreEqual("Taipei", member.Profile.Address); - Assert.AreEqual(Gender.Male, member.Profile.Gender); - } -} \ No newline at end of file diff --git a/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.sln b/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.sln deleted file mode 100644 index b6a84778..00000000 --- a/Configuration/Lab.MemoryConfigSource/Lab.MemoryConfigSource.sln +++ /dev/null @@ -1,16 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.MemoryConfigSource.TestProject", "Lab.MemoryConfigSource.TestProject\Lab.MemoryConfigSource.TestProject.csproj", "{531A25A6-0836-4950-85B3-3882B19FD18F}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {531A25A6-0836-4950-85B3-3882B19FD18F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {531A25A6-0836-4950-85B3-3882B19FD18F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {531A25A6-0836-4950-85B3-3882B19FD18F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {531A25A6-0836-4950-85B3-3882B19FD18F}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal From f0d8f6dd3484cbd725e10328182fc469cfe4cf47 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 19 Jan 2022 08:48:21 +0800 Subject: [PATCH 142/424] add taskfile to sln --- Configuration/Lab.Environment/Lab.Environment.sln | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Configuration/Lab.Environment/Lab.Environment.sln b/Configuration/Lab.Environment/Lab.Environment.sln index 9623d29e..291bd670 100644 --- a/Configuration/Lab.Environment/Lab.Environment.sln +++ b/Configuration/Lab.Environment/Lab.Environment.sln @@ -4,6 +4,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Environment.ConsoleApp. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Environment.ConsoleApp.NET6", "Lab.Environment.ConsoleApp.NET6\Lab.Environment.ConsoleApp.NET6.csproj", "{5033EB4D-63B9-4F17-9FDD-1A81E174F3C9}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{6ACD7D50-37E9-4003-B8BE-17FC3724B567}" + ProjectSection(SolutionItems) = preProject + Taskfile.yml = Taskfile.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU From df4980f20d1ceb8d9841bdc9097400de288b332e Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 19 Jan 2022 10:14:10 +0800 Subject: [PATCH 143/424] =?UTF-8?q?feat:=E5=AD=97=E5=85=B8=E9=9B=86?= =?UTF-8?q?=E5=90=88=E7=B6=81=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Lab.ConfigBind.TestProject.csproj | 14 +-- ...27\345\205\270\351\233\206\345\220\210.cs" | 92 +++++++++---------- ...60\345\242\203\350\256\212\346\225\270.cs" | 72 +++++++++++++++ 3 files changed, 123 insertions(+), 55 deletions(-) rename Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/UnitTest1.cs => "Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\345\255\227\345\205\270\351\233\206\345\220\210.cs" (67%) create mode 100644 "Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\347\222\260\345\242\203\350\256\212\346\225\270.cs" diff --git a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/Lab.ConfigBind.TestProject.csproj b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/Lab.ConfigBind.TestProject.csproj index 67c4441e..dc5bec91 100644 --- a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/Lab.ConfigBind.TestProject.csproj +++ b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/Lab.ConfigBind.TestProject.csproj @@ -8,13 +8,13 @@ - - - - - - - + + + + + + + diff --git a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/UnitTest1.cs "b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\345\255\227\345\205\270\351\233\206\345\220\210.cs" similarity index 67% rename from Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/UnitTest1.cs rename to "Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\345\255\227\345\205\270\351\233\206\345\220\210.cs" index 4507739c..bbf78098 100644 --- a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/UnitTest1.cs +++ "b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\345\255\227\345\205\270\351\233\206\345\220\210.cs" @@ -4,31 +4,30 @@ namespace Lab.ConfigBind.TestProject; - - [TestClass] -public class UnitTest1 +public class 來源為字典集合 { - public enum Gender - { - Male, - Female - } - - public class Member - { - public string Id { get; set; } - - public Profile Profile { get; set; } - } - - public class Profile + [TestMethod] + public void 綁定集合() { - public Gender? Gender { get; set; } + var source = new Dictionary + { + ["a:id"] = "9527", + ["a:profile:gender"] = "Male", + ["a:profile:age"] = "18", + ["a:profile:address"] = "Taipei", + ["b:id"] = "9528", + ["b:profile:gender"] = "Male", + ["b:profile:age"] = "19", + ["b:profile:address"] = "Taipei", + }; - public int? Age { get; set; } + var builder = new ConfigurationBuilder(); + var configRoot = builder.AddInMemoryCollection(source).Build(); + var member = configRoot.Get>(); - public string Address { get; set; } + Assert.AreEqual("9527", member[0].Id); + Assert.AreEqual("9528", member[1].Id); } [TestMethod] @@ -41,39 +40,16 @@ public void 綁定複雜型別() ["profile:age"] = "18", ["profile:address"] = "Taipei", }; - + var builder = new ConfigurationBuilder(); var configRoot = builder.AddInMemoryCollection(source).Build(); var member = configRoot.Get(); - + Assert.AreEqual("9527", member.Id); Assert.AreEqual(18, member.Profile.Age); Assert.AreEqual("Taipei", member.Profile.Address); Assert.AreEqual(Gender.Male, member.Profile.Gender); } - [TestMethod] - public void 綁定集合() - { - var source = new Dictionary - { - ["a:id"] = "9527", - ["a:profile:gender"] = "Male", - ["a:profile:age"] = "18", - ["a:profile:address"] = "Taipei", - ["b:id"] = "9528", - ["b:profile:gender"] = "Male", - ["b:profile:age"] = "19", - ["b:profile:address"] = "Taipei", - - }; - - var builder = new ConfigurationBuilder(); - var configRoot = builder.AddInMemoryCollection(source).Build(); - var member = configRoot.Get>(); - - Assert.AreEqual("9527", member[0].Id); - Assert.AreEqual("9528", member[1].Id); - } [TestMethod] public void 綁定環境變數() @@ -88,15 +64,35 @@ public void 綁定環境變數() ["b:profile:gender"] = "Male", ["b:profile:age"] = "19", ["b:profile:address"] = "Taipei", - }; var builder = new ConfigurationBuilder(); builder.AddEnvironmentVariables(); var configRoot = builder.AddInMemoryCollection(source).Build(); var member = configRoot.Get>(); - + Assert.AreEqual("9527", member[0].Id); Assert.AreEqual("9528", member[1].Id); } - + + private enum Gender + { + Male, + Female + } + + private class Member + { + public string Id { get; set; } + + public Profile Profile { get; set; } + } + + private class Profile + { + public Gender? Gender { get; set; } + + public int? Age { get; set; } + + public string Address { get; set; } + } } \ No newline at end of file diff --git "a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\347\222\260\345\242\203\350\256\212\346\225\270.cs" "b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\347\222\260\345\242\203\350\256\212\346\225\270.cs" new file mode 100644 index 00000000..c79359a9 --- /dev/null +++ "b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\347\222\260\345\242\203\350\256\212\346\225\270.cs" @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.ConfigBind.TestProject; + +[TestClass] +public class 來源為環境變數 +{ + [TestMethod] + public void 綁定複雜型別() + { + Environment.SetEnvironmentVariable("id", "9527"); + Environment.SetEnvironmentVariable("profile:gender", "Male"); + Environment.SetEnvironmentVariable("profile:age", "18"); + Environment.SetEnvironmentVariable("profile:address", "Taipei"); + + var builder = new ConfigurationBuilder(); + builder.AddEnvironmentVariables(); + var configRoot = builder.AddInMemoryCollection() + .Build(); + var member = configRoot.Get(); + Assert.AreEqual("9527", member.Id); + Assert.AreEqual(18, member.Profile.Age); + Assert.AreEqual("Taipei", member.Profile.Address); + Assert.AreEqual(Gender.Male, member.Profile.Gender); + } + + [TestMethod] + public void 綁定集合() + { + Environment.SetEnvironmentVariable("a:id", "9527"); + Environment.SetEnvironmentVariable("a:profile:gender", "Male"); + Environment.SetEnvironmentVariable("a:profile:age", "18"); + Environment.SetEnvironmentVariable("a:profile:address", "Taipei"); + Environment.SetEnvironmentVariable("b:id", "9528"); + Environment.SetEnvironmentVariable("b:profile:gender", "Male"); + Environment.SetEnvironmentVariable("b:profile:age", "19"); + Environment.SetEnvironmentVariable("b:profile:address", "Taipei"); + var builder = new ConfigurationBuilder(); + builder.AddEnvironmentVariables(); + var configRoot = builder.AddInMemoryCollection() + .Build(); + var member = configRoot.Get>(); + + Assert.AreEqual("9527", member[0].Id); + Assert.AreEqual("9528", member[1].Id); + } + + private enum Gender + { + Male, + Female + } + + private class Member + { + public string Id { get; set; } + + public Profile Profile { get; set; } + } + + private class Profile + { + public Gender? Gender { get; set; } + + public int? Age { get; set; } + + public string Address { get; set; } + } +} \ No newline at end of file From 8630ea161733d2840c3956d3767bea7707718ffa Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 19 Jan 2022 15:13:16 +0800 Subject: [PATCH 144/424] refactor --- .../Lab.ConfigBind.TestProject.csproj | 14 +++--- ...27\345\205\270\351\233\206\345\220\210.cs" | 46 +++++++++---------- ...60\345\242\203\350\256\212\346\225\270.cs" | 43 +++++++++++------ 3 files changed, 60 insertions(+), 43 deletions(-) diff --git a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/Lab.ConfigBind.TestProject.csproj b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/Lab.ConfigBind.TestProject.csproj index dc5bec91..67c4441e 100644 --- a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/Lab.ConfigBind.TestProject.csproj +++ b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/Lab.ConfigBind.TestProject.csproj @@ -8,13 +8,13 @@ - - - - - - - + + + + + + + diff --git "a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\345\255\227\345\205\270\351\233\206\345\220\210.cs" "b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\345\255\227\345\205\270\351\233\206\345\220\210.cs" index bbf78098..80b674d8 100644 --- "a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\345\255\227\345\205\270\351\233\206\345\220\210.cs" +++ "b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\345\255\227\345\205\270\351\233\206\345\220\210.cs" @@ -7,6 +7,29 @@ namespace Lab.ConfigBind.TestProject; [TestClass] public class 來源為字典集合 { + [TestMethod] + public void 綁定字典() + { + var source = new Dictionary + { + ["a:id"] = "9527", + ["a:profile:gender"] = "Male", + ["a:profile:age"] = "18", + ["a:profile:address"] = "Taipei", + ["b:id"] = "9528", + ["b:profile:gender"] = "Male", + ["b:profile:age"] = "19", + ["b:profile:address"] = "Taipei", + }; + + var builder = new ConfigurationBuilder(); + var configRoot = builder.AddInMemoryCollection(source).Build(); + var member = configRoot.Get>(); + + Assert.AreEqual("9527", member["a"].Id); + Assert.AreEqual("9528", member["b"].Id); + } + [TestMethod] public void 綁定集合() { @@ -51,29 +74,6 @@ public void 綁定複雜型別() Assert.AreEqual(Gender.Male, member.Profile.Gender); } - [TestMethod] - public void 綁定環境變數() - { - var source = new Dictionary - { - ["a:id"] = "9527", - ["a:profile:gender"] = "Male", - ["a:profile:age"] = "18", - ["a:profile:address"] = "Taipei", - ["b:id"] = "9528", - ["b:profile:gender"] = "Male", - ["b:profile:age"] = "19", - ["b:profile:address"] = "Taipei", - }; - var builder = new ConfigurationBuilder(); - builder.AddEnvironmentVariables(); - var configRoot = builder.AddInMemoryCollection(source).Build(); - var member = configRoot.Get>(); - - Assert.AreEqual("9527", member[0].Id); - Assert.AreEqual("9528", member[1].Id); - } - private enum Gender { Male, diff --git "a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\347\222\260\345\242\203\350\256\212\346\225\270.cs" "b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\347\222\260\345\242\203\350\256\212\346\225\270.cs" index c79359a9..4020acb1 100644 --- "a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\347\222\260\345\242\203\350\256\212\346\225\270.cs" +++ "b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\347\222\260\345\242\203\350\256\212\346\225\270.cs" @@ -9,22 +9,20 @@ namespace Lab.ConfigBind.TestProject; public class 來源為環境變數 { [TestMethod] - public void 綁定複雜型別() + public void 綁定字典() { - Environment.SetEnvironmentVariable("id", "9527"); - Environment.SetEnvironmentVariable("profile:gender", "Male"); - Environment.SetEnvironmentVariable("profile:age", "18"); - Environment.SetEnvironmentVariable("profile:address", "Taipei"); - + Environment.SetEnvironmentVariable("a:id", "9527"); + Environment.SetEnvironmentVariable("a:profile:gender", "Male"); + Environment.SetEnvironmentVariable("a:profile:age", "18"); + Environment.SetEnvironmentVariable("a:profile:address", "Taipei"); + Environment.SetEnvironmentVariable("b:id", "9528"); + Environment.SetEnvironmentVariable("b:profile:gender", "Male"); + Environment.SetEnvironmentVariable("b:profile:age", "19"); + Environment.SetEnvironmentVariable("b:profile:address", "Taipei"); var builder = new ConfigurationBuilder(); - builder.AddEnvironmentVariables(); var configRoot = builder.AddInMemoryCollection() .Build(); - var member = configRoot.Get(); - Assert.AreEqual("9527", member.Id); - Assert.AreEqual(18, member.Profile.Age); - Assert.AreEqual("Taipei", member.Profile.Address); - Assert.AreEqual(Gender.Male, member.Profile.Gender); + var member = configRoot.Get>(); } [TestMethod] @@ -39,15 +37,34 @@ public void 綁定集合() Environment.SetEnvironmentVariable("b:profile:age", "19"); Environment.SetEnvironmentVariable("b:profile:address", "Taipei"); var builder = new ConfigurationBuilder(); - builder.AddEnvironmentVariables(); var configRoot = builder.AddInMemoryCollection() .Build(); var member = configRoot.Get>(); + var member2 = configRoot.Get>(); Assert.AreEqual("9527", member[0].Id); Assert.AreEqual("9528", member[1].Id); } + [TestMethod] + public void 綁定複雜型別() + { + Environment.SetEnvironmentVariable("id", "9527"); + Environment.SetEnvironmentVariable("profile:gender", "Male"); + Environment.SetEnvironmentVariable("profile:age", "18"); + Environment.SetEnvironmentVariable("profile:address", "Taipei"); + + var builder = new ConfigurationBuilder(); + builder.AddEnvironmentVariables(); + var configRoot = builder.AddInMemoryCollection() + .Build(); + var member = configRoot.Get(); + Assert.AreEqual("9527", member.Id); + Assert.AreEqual(18, member.Profile.Age); + Assert.AreEqual("Taipei", member.Profile.Address); + Assert.AreEqual(Gender.Male, member.Profile.Gender); + } + private enum Gender { Male, From 965ce4b8c01d161aab26b0a6a5effa763e8df65c Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 19 Jan 2022 22:09:45 +0800 Subject: [PATCH 145/424] fix bug --- ...45\255\227\345\205\270\351\233\206\345\220\210.cs" | 2 +- ...47\222\260\345\242\203\350\256\212\346\225\270.cs" | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git "a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\345\255\227\345\205\270\351\233\206\345\220\210.cs" "b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\345\255\227\345\205\270\351\233\206\345\220\210.cs" index 80b674d8..0565a7fe 100644 --- "a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\345\255\227\345\205\270\351\233\206\345\220\210.cs" +++ "b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\345\255\227\345\205\270\351\233\206\345\220\210.cs" @@ -23,7 +23,7 @@ public void 綁定字典() }; var builder = new ConfigurationBuilder(); - var configRoot = builder.AddInMemoryCollection(source).Build(); + var configRoot = builder.AddEnvironmentVariables().Build(); var member = configRoot.Get>(); Assert.AreEqual("9527", member["a"].Id); diff --git "a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\347\222\260\345\242\203\350\256\212\346\225\270.cs" "b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\347\222\260\345\242\203\350\256\212\346\225\270.cs" index 4020acb1..a9c53ad4 100644 --- "a/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\347\222\260\345\242\203\350\256\212\346\225\270.cs" +++ "b/Configuration/Lab.ConfigBind/Lab.ConfigBind.TestProject/\344\276\206\346\272\220\347\202\272\347\222\260\345\242\203\350\256\212\346\225\270.cs" @@ -20,9 +20,12 @@ public void 綁定字典() Environment.SetEnvironmentVariable("b:profile:age", "19"); Environment.SetEnvironmentVariable("b:profile:address", "Taipei"); var builder = new ConfigurationBuilder(); - var configRoot = builder.AddInMemoryCollection() + var configRoot = builder.AddEnvironmentVariables() .Build(); var member = configRoot.Get>(); + + Assert.AreEqual("9527", member["a"].Id); + Assert.AreEqual("9528", member["b"].Id); } [TestMethod] @@ -37,10 +40,9 @@ public void 綁定集合() Environment.SetEnvironmentVariable("b:profile:age", "19"); Environment.SetEnvironmentVariable("b:profile:address", "Taipei"); var builder = new ConfigurationBuilder(); - var configRoot = builder.AddInMemoryCollection() + var configRoot = builder.AddEnvironmentVariables() .Build(); var member = configRoot.Get>(); - var member2 = configRoot.Get>(); Assert.AreEqual("9527", member[0].Id); Assert.AreEqual("9528", member[1].Id); @@ -55,8 +57,7 @@ public void 綁定複雜型別() Environment.SetEnvironmentVariable("profile:address", "Taipei"); var builder = new ConfigurationBuilder(); - builder.AddEnvironmentVariables(); - var configRoot = builder.AddInMemoryCollection() + var configRoot = builder.AddEnvironmentVariables() .Build(); var member = configRoot.Get(); Assert.AreEqual("9527", member.Id); From d01f344d4c32ba5042e5dcd4801203fe55b66607 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 19 Jan 2022 22:56:35 +0800 Subject: [PATCH 146/424] fix bug --- .../Lab.Config/AspNetCore3/AspNetCore3.csproj | 40 +++--- .../Controllers/DefaultController.cs | 10 +- .../Controllers/WeatherForecastController.cs | 20 ++- .../NetCore/Lab.Config/AspNetCore3/Program.cs | 40 +++--- .../AspNetCore3/ServiceCollectionEx.cs | 3 +- .../NetCore/Lab.Config/AspNetCore3/Startup.cs | 22 ++-- .../AspNetCore3/StartupInjectionAppSetting.cs | 14 +- .../StartupInjectionIOptionsMonitor.cs | 4 +- .../AspNetCore3/StartupInjectionOptions.cs | 2 +- .../StartupInjectionOptionsSnapshot.cs | 6 +- .../Lab.Config/AspNetCore3/appsettings.json | 2 - .../Lab.Config/AspNetCore5/AspNetCore5.csproj | 8 +- .../Controllers/DefaultController.cs | 2 +- .../Controllers/WeatherForecastController.cs | 2 +- .../NetCore/Lab.Config/AspNetCore5/Program.cs | 2 +- .../NetCore/Lab.Config/AspNetCore5/Startup.cs | 6 +- .../Lab.Config/AspNetCore5/appsettings.json | 2 - .../Lab.Config/ConsoleApp1/ConsoleApp1.csproj | 8 -- .../NetCore/Lab.Config/ConsoleApp1/Program.cs | 12 -- .../Properties/launchSettings.json | 8 -- .../Lab.Config/Lab.Infra/ConnectionStrings.cs | 2 +- .../Lab.Config/Lab.Infra/Lab.Infra.csproj | 24 ++-- .../NetCore/Lab.Config/Lab.Infra/Player.cs | 2 +- .../Lab.Config/MsUnitTest/MsUnitTest.csproj | 66 +++++----- .../Lab.Config/MsUnitTest/UnitTest1.cs | 14 +- .../NetFx48/AppWorkFlowWithOption.cs | 2 +- .../NetCore/Lab.Config/NetFx48/NetFx48.csproj | 78 +++++------ ...yEnvironmentVariablesConfigurationTests.cs | 2 +- .../NetFx48/SurveyJsonConfigurationTests.cs | 124 +++++++++++------- .../SurveyKeyPerFileConfigurationTests.cs | 2 +- .../Lab.Config/NetFx48/SurveyOptionTests.cs | 42 +++--- .../NetFx48/SurveyUserSecretTests.cs | 4 +- .../Lab.Config/NetFx48/appsettings.QA.json | 1 - .../Lab.Config/NetFx48/appsettings.ini | 6 +- .../Lab.Config/NetFx48/appsettings.json | 2 +- .../Lab.Config/NetFx48/appsettings.test.json | 1 - .../Lab.Config/NetFx48/appsettings.xml | 4 +- 37 files changed, 290 insertions(+), 299 deletions(-) delete mode 100644 Configuration/NetCore/Lab.Config/ConsoleApp1/ConsoleApp1.csproj delete mode 100644 Configuration/NetCore/Lab.Config/ConsoleApp1/Program.cs delete mode 100644 Configuration/NetCore/Lab.Config/ConsoleApp1/Properties/launchSettings.json diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/AspNetCore3.csproj b/Configuration/NetCore/Lab.Config/AspNetCore3/AspNetCore3.csproj index 3305cc3f..24827e37 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/AspNetCore3.csproj +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/AspNetCore3.csproj @@ -1,29 +1,29 @@ - - netcoreapp3.1 - Debug;Release;QA - AnyCPU - + + netcoreapp3.1 + Debug;Release;QA + AnyCPU + - - true - false - + + true + false + - - - + + + - - - + + + - - - Always - - + + + Always + + diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs index 0ad61e02..170d017e 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/DefaultController.cs @@ -13,7 +13,7 @@ public class DefaultController : ControllerBase public IActionResult Get() { var serviceProvider = this.HttpContext.RequestServices; - var options = serviceProvider.GetService>(); + var options = serviceProvider.GetService>(); return this.Ok(options?.Value); } @@ -21,8 +21,8 @@ public IActionResult Get() public IActionResult GetMonitorPlayer(int id) { var serviceProvider = this.HttpContext.RequestServices; - var playerOption = serviceProvider.GetService>(); - var player = playerOption.Get($"Player{id}"); + var playerOption = serviceProvider.GetService>(); + var player = playerOption.Get($"Player{id}"); return this.Ok(player); } @@ -30,8 +30,8 @@ public IActionResult GetMonitorPlayer(int id) public IActionResult GetSnapshotPlayer(int id) { var serviceProvider = this.HttpContext.RequestServices; - var playerOption = serviceProvider.GetService>(); - var player = playerOption.Get($"Player{id}"); + var playerOption = serviceProvider.GetService>(); + var player = playerOption.Get($"Player{id}"); return this.Ok(player); } } diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs index 5b4654df..55424a39 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Controllers/WeatherForecastController.cs @@ -1,10 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Lab.Infra; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -20,10 +19,10 @@ public class WeatherForecastController : ControllerBase }; private readonly ILogger _logger; - private AppSetting _appSetting; - private IConfiguration _config; - private Player _player1; - private Player _player2; + private AppSetting _appSetting; + private IConfiguration _config; + private Player _player1; + private Player _player2; // TODO:依賴 AppSetting // public WeatherForecastController(AppSetting appSetting) @@ -34,7 +33,6 @@ public class WeatherForecastController : ControllerBase // TODO:依賴 IOptions public WeatherForecastController(IOptions options) { - try { this._appSetting = options.Value; @@ -83,13 +81,13 @@ public WeatherForecastController(IOptions options) [HttpGet] public IEnumerable Get() - { - var rng = new Random(); + { + var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { - Date = DateTime.Now.AddDays(index), + Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), - Summary = Summaries[rng.Next(Summaries.Length)] + Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); } diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs index 83e04777..242d45b9 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Program.cs @@ -1,34 +1,32 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; namespace AspNetCore3 { public class Program { + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.ConfigureAppConfiguration(p => + { + p.AddJsonFile("appsettings.json", false, false); + }); + webBuilder.UseStartup(); + + // webBuilder.UseStartup(); + // webBuilder.UseStartup(); + // webBuilder.UseStartup(); + // webBuilder.UseStartup(); + }); + } + public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.ConfigureAppConfiguration(p => - { - p.AddJsonFile("appsettings.json", false, false); - }); - webBuilder.UseStartup(); - // webBuilder.UseStartup(); - // webBuilder.UseStartup(); - // webBuilder.UseStartup(); - // webBuilder.UseStartup(); - }); } -} +} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/ServiceCollectionEx.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/ServiceCollectionEx.cs index 907ebd43..a1adff51 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/ServiceCollectionEx.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/ServiceCollectionEx.cs @@ -32,4 +32,5 @@ // return config; // } // } -// } \ No newline at end of file +// } + diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/Startup.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/Startup.cs index 5faec907..d4468799 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/Startup.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/Startup.cs @@ -38,23 +38,23 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - // AppSetting + //驗證 AppSetting services.AddOptions() .ValidateDataAnnotations() .Validate(p => - { - if (p.AllowedHosts == null) - { - return false; - } + { + if (p.AllowedHosts == null) + { + return false; + } - return true; - }, "AllowedHosts must be value"); // Failure message. + return true; + }, "AllowedHosts must be value"); // Failure message. - //`J Options M IConfiguration + //注入 Options 和完整 IConfiguration services.Configure(this.Configuration); - - //`J Options M Configuration Section Name + + //注入 Options 和 Configuration Section Name // services.Configure("Player1", this.Configuration.GetSection("Player1")); // services.Configure("Player2", this.Configuration.GetSection("Player2")); // services.Configure("Player3", this.Configuration.GetSection("Player3")); diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionAppSetting.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionAppSetting.cs index 9c6d3481..311eda8e 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionAppSetting.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionAppSetting.cs @@ -38,14 +38,14 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - //`J AppSetting + //注入 AppSetting services.AddSingleton(provider => - { - //lazy load - var appSetting = new AppSetting(); - this.Configuration.Bind(appSetting); - return appSetting; - }); + { + //lazy load + var appSetting = new AppSetting(); + this.Configuration.Bind(appSetting); + return appSetting; + }); } } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs index 97d60096..5a43db3c 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionIOptionsMonitor.cs @@ -38,10 +38,10 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - //`J Options M IConfiguration + //注入 Options 和完整 IConfiguration services.Configure(this.Configuration); - //`J Options M Configuration Section Name + //注入 Options 和 Configuration Section Name services.Configure(this.Configuration); services.Configure("Player1", this.Configuration.GetSection("Player1")); services.Configure("Player2", this.Configuration.GetSection("Player2")); diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs index 178ddab3..f953b4ce 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptions.cs @@ -38,7 +38,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - //`J Options M IConfiguration + //注入 Options 和完整 IConfiguration services.Configure(this.Configuration); } } diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs index 44f45b23..7d983108 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/StartupInjectionOptionsSnapshot.cs @@ -38,7 +38,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - // AppSetting + //驗證 AppSetting services.AddOptions() .ValidateDataAnnotations() .Validate(p => @@ -51,10 +51,10 @@ public void ConfigureServices(IServiceCollection services) return true; }, "AllowedHosts must be value"); // Failure message. - //`J Options M IConfiguration + //注入 Options 和完整 IConfiguration services.Configure(this.Configuration); - //`J Options M Configuration Section Name + //注入 Options 和 Configuration Section Name services.Configure(this.Configuration); services.Configure("Player1", this.Configuration.GetSection("Player1")); services.Configure("Player2", this.Configuration.GetSection("Player2")); diff --git a/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json b/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json index 829fc2f8..c03edfe4 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json +++ b/Configuration/NetCore/Lab.Config/AspNetCore3/appsettings.json @@ -7,7 +7,6 @@ } }, "AllowedHosts": "*", - "ConnectionStrings": { "DefaultConnectionString": "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;", "AuthenticationConnectionString": "" @@ -20,7 +19,6 @@ "AppId": "player1", "Key": "12345678990" }, - "Player2": { "AppId": "player2", "Key": "player2_123456" diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/AspNetCore5.csproj b/Configuration/NetCore/Lab.Config/AspNetCore5/AspNetCore5.csproj index 60048b36..92063f64 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore5/AspNetCore5.csproj +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/AspNetCore5.csproj @@ -7,13 +7,13 @@ - - - + + + - + diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs b/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs index 3f0d4c4b..9e817d64 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/DefaultController.cs @@ -45,7 +45,7 @@ public IActionResult GetMonitorPlayer(int id) }; appSettingOptions.OnChange(p => { - Console.WriteLine("`Iwܧ"); + Console.WriteLine("節點已變更"); }); return this.Ok(content); } diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/WeatherForecastController.cs b/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/WeatherForecastController.cs index 932d39a7..dab91d80 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/WeatherForecastController.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/Controllers/WeatherForecastController.cs @@ -1,4 +1,4 @@ -// using System; +// using System; // using System.Collections.Generic; // using System.Linq; // using System.Threading.Tasks; diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/Program.cs b/Configuration/NetCore/Lab.Config/AspNetCore5/Program.cs index 861aa8ce..1fc668cf 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore5/Program.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/Program.cs @@ -12,7 +12,7 @@ public static IHostBuilder CreateHostBuilder(string[] args) { webBuilder.ConfigureAppConfiguration(p => { - // sJպA + // 不重新載入組態 //p.AddJsonFile("appsettings.json", false, false); }); webBuilder.UseStartup(); diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/Startup.cs b/Configuration/NetCore/Lab.Config/AspNetCore5/Startup.cs index 1a7bd75c..78d69940 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore5/Startup.cs +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/Startup.cs @@ -45,7 +45,7 @@ public void ConfigureServices(IServiceCollection services) c.SwaggerDoc("v1", new OpenApiInfo {Title = "AspNetCore5", Version = "v1"}); }); - // AppSetting + //驗證 AppSetting services.AddOptions() .ValidateDataAnnotations() .Validate(p => @@ -58,10 +58,10 @@ public void ConfigureServices(IServiceCollection services) return true; }, "AllowedHosts must be value"); // Failure message. - //`J Options M IConfiguration + //注入 Options 和完整 IConfiguration services.Configure(this.Configuration); - //`J Options M Configuration Section Name + //注入 Options 和 Configuration Section Name services.Configure("Player1", this.Configuration.GetSection("Player1")); services.Configure("Player2", this.Configuration.GetSection("Player2")); services.Configure("Player3", this.Configuration.GetSection("Player3")); diff --git a/Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.json b/Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.json index ee3bc5a4..25a5a48f 100644 --- a/Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.json +++ b/Configuration/NetCore/Lab.Config/AspNetCore5/appsettings.json @@ -7,7 +7,6 @@ } }, "AllowedHosts": "*", - "ConnectionStrings": { "DefaultConnectionString": "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;", "AuthenticationConnectionString": "" @@ -20,7 +19,6 @@ "AppId": "player1", "Key": "12345678990" }, - "Player2": { "AppId": "player2", "Key": "player2_123456" diff --git a/Configuration/NetCore/Lab.Config/ConsoleApp1/ConsoleApp1.csproj b/Configuration/NetCore/Lab.Config/ConsoleApp1/ConsoleApp1.csproj deleted file mode 100644 index 9590466a..00000000 --- a/Configuration/NetCore/Lab.Config/ConsoleApp1/ConsoleApp1.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - Exe - net5.0 - - - diff --git a/Configuration/NetCore/Lab.Config/ConsoleApp1/Program.cs b/Configuration/NetCore/Lab.Config/ConsoleApp1/Program.cs deleted file mode 100644 index be1b5acd..00000000 --- a/Configuration/NetCore/Lab.Config/ConsoleApp1/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace ConsoleApp1 -{ - class Program - { - static void Main(string[] args) - { - Console.WriteLine("Hello World!"); - } - } -} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/ConsoleApp1/Properties/launchSettings.json b/Configuration/NetCore/Lab.Config/ConsoleApp1/Properties/launchSettings.json deleted file mode 100644 index 053b519b..00000000 --- a/Configuration/NetCore/Lab.Config/ConsoleApp1/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "ConsoleApp1": { - "commandName": "Project", - "commandLineArgs": "-" - } - } -} \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/Lab.Infra/ConnectionStrings.cs b/Configuration/NetCore/Lab.Config/Lab.Infra/ConnectionStrings.cs index 45b7fe3e..bd90c3ca 100644 --- a/Configuration/NetCore/Lab.Config/Lab.Infra/ConnectionStrings.cs +++ b/Configuration/NetCore/Lab.Config/Lab.Infra/ConnectionStrings.cs @@ -1,4 +1,4 @@ -namespace Lab.Infra +namespace Lab.Infra { public class ConnectionStrings { diff --git a/Configuration/NetCore/Lab.Config/Lab.Infra/Lab.Infra.csproj b/Configuration/NetCore/Lab.Config/Lab.Infra/Lab.Infra.csproj index e326d242..fc35969d 100644 --- a/Configuration/NetCore/Lab.Config/Lab.Infra/Lab.Infra.csproj +++ b/Configuration/NetCore/Lab.Config/Lab.Infra/Lab.Infra.csproj @@ -1,18 +1,18 @@ - - netcoreapp3.1 - Debug;Release;QA - AnyCPU - + + netcoreapp3.1 + Debug;Release;QA + AnyCPU + - - true - false - + + true + false + - - - + + + diff --git a/Configuration/NetCore/Lab.Config/Lab.Infra/Player.cs b/Configuration/NetCore/Lab.Config/Lab.Infra/Player.cs index 9c2a26ea..2ef82d7a 100644 --- a/Configuration/NetCore/Lab.Config/Lab.Infra/Player.cs +++ b/Configuration/NetCore/Lab.Config/Lab.Infra/Player.cs @@ -1,4 +1,4 @@ -namespace Lab.Infra +namespace Lab.Infra { public class Player { diff --git a/Configuration/NetCore/Lab.Config/MsUnitTest/MsUnitTest.csproj b/Configuration/NetCore/Lab.Config/MsUnitTest/MsUnitTest.csproj index b37edb3d..e3c2748e 100644 --- a/Configuration/NetCore/Lab.Config/MsUnitTest/MsUnitTest.csproj +++ b/Configuration/NetCore/Lab.Config/MsUnitTest/MsUnitTest.csproj @@ -1,37 +1,37 @@ - - netcoreapp3.1 - - false - - Debug;Release;QA - - AnyCPU - - - - true - false - - - - - - - - - - - - - - - - - - Always - - + + netcoreapp3.1 + + false + + Debug;Release;QA + + AnyCPU + + + + true + false + + + + + + + + + + + + + + + + + + Always + + diff --git a/Configuration/NetCore/Lab.Config/MsUnitTest/UnitTest1.cs b/Configuration/NetCore/Lab.Config/MsUnitTest/UnitTest1.cs index fc3a8923..42df9474 100644 --- a/Configuration/NetCore/Lab.Config/MsUnitTest/UnitTest1.cs +++ b/Configuration/NetCore/Lab.Config/MsUnitTest/UnitTest1.cs @@ -10,7 +10,7 @@ namespace MsUnitTest public class UnitTest1 { [TestMethod] - public void zLAppSettingŪ]w() + public void 透過AppSetting物件讀取設定檔() { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) @@ -24,7 +24,7 @@ public class UnitTest1 } [TestMethod] - public void zLAppSettingŪ]w_ϬqsbߥXҥ~() + public void 透過AppSetting物件讀取設定檔_區段不存在拋出例外() { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) @@ -38,7 +38,7 @@ public class UnitTest1 } [TestMethod] - public void jw]w_XRk_Get() + public void 綁定設定_擴充方法_Get() { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) @@ -50,7 +50,7 @@ public class UnitTest1 } [TestMethod] - public void jw]w_XRk_Bind() + public void 綁定設定_擴充方法_Bind() { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) @@ -64,7 +64,7 @@ public class UnitTest1 } [TestMethod] - public void Ū]w() + public void 讀取設定檔() { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) @@ -78,7 +78,7 @@ public class UnitTest1 } [TestMethod] - public void Ū]w_GetConnectionString() + public void 讀取設定檔_GetConnectionString() { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) @@ -92,7 +92,7 @@ public class UnitTest1 } [TestMethod] - public void Ū]w_TryGet() + public void 讀取設定檔_TryGet() { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) diff --git a/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs index 6dd2db39..8fa36c8b 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/AppWorkFlowWithOption.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.Extensions.Options; namespace NetFx48 diff --git a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj index c347c8c3..c31a487c 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj +++ b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj @@ -10,56 +10,56 @@ Debug;Release;QA AnyCPU - 659be13b-676e-4c9e-a0b9-0df2ffd75cfc - + 659be13b-676e-4c9e-a0b9-0df2ffd75cfc + - true - false + true + false - - - - - - - - - - - - - + + + + + + + + + + + + + - - true - Always - PreserveNewest - - - true - Always - PreserveNewest - + + true + Always + PreserveNewest + + + true + Always + PreserveNewest + - - Always - - - Always - - - Always - - - Always - + + Always + + + Always + + + Always + + + Always + diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs index bf710645..773448a6 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs index 05f034b6..fee31ec7 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs @@ -17,7 +17,7 @@ public void 切換組態() { string environmentName; #if DEBUG - environmentName = "Development"; + environmentName = "Development"; #elif QA environmentName = "QA"; #elif STAGING @@ -27,8 +27,8 @@ public void 切換組態() #endif var configBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", false, true) - .AddJsonFile($"appsettings.{environmentName}.json", true, true) + .AddJsonFile("appsettings.json", false, true) + .AddJsonFile($"appsettings.{environmentName}.json", true, true) ; var configRoot = configBuilder.Build(); @@ -60,31 +60,31 @@ public void 注入Configuration() { var builder = Host.CreateDefaultBuilder(null) .ConfigureAppConfiguration(config => - { - config.Sources.Clear(); - config.AddJsonFile("appsettings.json", true, true); - }) + { + config.Sources.Clear(); + config.AddJsonFile("appsettings.json", true, true); + }) .ConfigureServices(service => - { - //DI - service.AddScoped(typeof(AppWorkFlow)); - }); + { + //DI + service.AddScoped(typeof(AppWorkFlow)); + }); var host = builder.Build(); var appService = host.Services.GetService(); - var playerId = appService.GetPlayerId(); + var playerId = appService.GetPlayerId(); Console.WriteLine($"AppId = {playerId}"); } [TestMethod] - public void 記憶體組態() + public void 讀取記憶體組態() { var configBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddInMemoryCollection(new Dictionary { - {"Player:AppId", "player1"}, - {"Player:Key", "1234567890"}, + { "Player:AppId", "player1" }, + { "Player:Key", "1234567890" }, { "ConnectionStrings:DefaultConnectionString", "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" @@ -102,6 +102,32 @@ public void 記憶體組態() Console.WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); } + [TestMethod] + public void 讀取記憶體組態後綁定() + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddInMemoryCollection(new Dictionary + { + { "Player:AppId", "player1" }, + { "Player:Key", "1234567890" }, + { + "ConnectionStrings:DefaultConnectionString", + "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" + }, + }) + ; + + var configRoot = configBuilder.Build(); + var appSetting = configRoot.Get(); + + //讀取組態 + + Console.WriteLine($"AppId = {appSetting.Player.AppId}"); + Console.WriteLine($"Key = {appSetting.Player.Key}"); + Console.WriteLine($"Connection String = {appSetting.ConnectionStrings.DefaultConnectionString}"); + } + [TestMethod] public void 通過Host() { @@ -113,8 +139,8 @@ public void 實例化JsonConfigurationProvider() { var configProvider = new JsonConfigurationProvider(new JsonConfigurationSource { - Optional = false, - Path = "appsettings.json", + Optional = false, + Path = "appsettings.json", ReloadOnChange = true }); configProvider.Load(); @@ -122,27 +148,13 @@ public void 實例化JsonConfigurationProvider() Console.WriteLine($"AppId = {appId}"); } - [TestMethod] - public void 讀取設定檔_GetSection() - { - var builder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json"); - var configRoot = builder.Build(); - - Console.WriteLine($"AppId = {configRoot.GetSection("AppId")}"); - Console.WriteLine($"AppId = {configRoot.GetSection("Player:AppId")}"); - Console.WriteLine($"Key = {configRoot.GetSection("Player:Key")}"); - Console.WriteLine($"Connection String = {configRoot.GetSection("ConnectionStrings:DefaultConnectionString")}"); - } - [TestMethod] public void 讀取設定檔_GetChild() { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json"); - var configRoot = builder.Build(); + var configRoot = builder.Build(); var firstSections = configRoot.GetChildren(); foreach (var firstSection in firstSections) { @@ -154,6 +166,20 @@ public void 讀取設定檔_GetChild() } } + [TestMethod] + public void 讀取設定檔_GetSection() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + var configRoot = builder.Build(); + + Console.WriteLine($"AppId = {configRoot.GetSection("AppId")}"); + Console.WriteLine($"AppId = {configRoot.GetSection("Player:AppId")}"); + Console.WriteLine($"Key = {configRoot.GetSection("Player:Key")}"); + Console.WriteLine($"Connection String = {configRoot.GetSection("ConnectionStrings:DefaultConnectionString")}"); + } + [TestMethod] public void 讀取設定檔_TryGet() { @@ -191,8 +217,8 @@ public void 讀取設定檔_綁定_Get() var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json"); - var configRoot = builder.Build(); - var player = configRoot.GetSection("Player").Get(); + var configRoot = builder.Build(); + var player = configRoot.GetSection("Player").Get(); var appSetting = configRoot.Get(); Console.WriteLine($"AppId = {player.AppId}"); @@ -204,22 +230,22 @@ private static IHostBuilder CreateHostBuilder(string[] args) { return Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration(config => - { - config.Sources.Clear(); - config.AddJsonFile("appsettings.json", true, true); - var configRoot = config.Build(); - - //讀取組態 - Console.WriteLine($"AppId = {configRoot["AppId"]}"); - Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); - Console.WriteLine($"Key = {configRoot["Player:Key"]}"); - Console - .WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); - }) + { + config.Sources.Clear(); + config.AddJsonFile("appsettings.json", true, true); + var configRoot = config.Build(); + + //讀取組態 + Console.WriteLine($"AppId = {configRoot["AppId"]}"); + Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); + Console.WriteLine($"Key = {configRoot["Player:Key"]}"); + Console + .WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); + }) .ConfigureServices(service => - { - //DI - }); + { + //DI + }); } } } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyKeyPerFileConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyKeyPerFileConfigurationTests.cs index 5205b0f4..573309f1 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyKeyPerFileConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyKeyPerFileConfigurationTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using Microsoft.Extensions.Configuration; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs index 6845a811..96824af4 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyOptionTests.cs @@ -10,12 +10,12 @@ namespace NetFx48 public class SurveyOptionTests { [TestMethod] - public void `JOption() + public void 注入Option() { var builder = Host.CreateDefaultBuilder() .ConfigureAppConfiguration((hosting, configBuilder) => { - // 1.ŪպA + // 1.讀組態檔 var environmentName = hosting.Configuration["ENVIRONMENT2"]; configBuilder.AddJsonFile("appsettings.json", false, true); @@ -25,10 +25,10 @@ public class SurveyOptionTests }) .ConfigureServices((hosting, services) => { - // 2. `J Option M Configuration + // 2. 注入 Option 和 Configuration services.Configure(hosting.Configuration); - //`JLA + //注入其他服務 services.AddSingleton(); }) ; @@ -39,12 +39,12 @@ public class SurveyOptionTests } [TestMethod] - public void `JOptionMonitor() + public void 注入OptionMonitor() { var builder = Host.CreateDefaultBuilder() .ConfigureAppConfiguration((hosting, configBuilder) => { - // 1.ŪպA + // 1.讀組態檔 var environmentName = hosting.Configuration["ENVIRONMENT2"]; configBuilder.AddJsonFile("appsettings.json", false, true); @@ -54,14 +54,14 @@ public class SurveyOptionTests }) .ConfigureServices((hosting, services) => { - // `J Option M Configuration + // 注入 Option 和完整 Configuration services.Configure(hosting.Configuration); - // `J Option MSw Configuration Section Name + // 注入 Option 和特定 Configuration Section Name services.Configure("Player", hosting.Configuration.GetSection("Player")); - //`JLA + //注入其他服務 services.AddScoped(); }) ; @@ -72,7 +72,7 @@ public class SurveyOptionTests } [TestMethod] - public void `JOptionSnapshot() + public void 注入OptionSnapshot() { var builder = Host.CreateDefaultBuilder() .ConfigureAppConfiguration((hosting, configBuilder) => @@ -86,14 +86,14 @@ public class SurveyOptionTests }) .ConfigureServices((hosting, services) => { - // `J Option by պA + // 注入 Option by 完整組態 services.Configure(hosting.Configuration); - // `J Option by SwպA + // 注入 Option by 特定組態 services.Configure(hosting.Configuration .GetSection("Player")); - //`JLA + //注入其他服務 services.AddScoped(); }) ; @@ -104,12 +104,12 @@ public class SurveyOptionTests } [TestMethod] - public void () + public void 驗證() { var builder = Host.CreateDefaultBuilder() .ConfigureAppConfiguration((hosting, configBuilder) => { - // 1.ŪպA + // 1.讀組態檔 var environmentName = hosting.Configuration["ENVIRONMENT2"]; configBuilder.AddJsonFile("appsettings.json", false, true); @@ -119,9 +119,9 @@ public class SurveyOptionTests }) .ConfigureServices((hosting, services) => { - // 2. `J Option M Configuration + // 2. 注入 Option 和 Configuration services.Configure(hosting.Configuration); - // + //驗證 services.AddOptions() .ValidateDataAnnotations() .Validate(p => @@ -137,7 +137,7 @@ public class SurveyOptionTests "DefaultConnectionString must be value"); // Failure message. ; - //`JLA + //注入其他服務 services.AddSingleton(); }) ; @@ -148,12 +148,12 @@ public class SurveyOptionTests } [TestMethod] - public void `JպA() + public void 直接注入組態物件() { var builder = Host.CreateDefaultBuilder() .ConfigureAppConfiguration((hosting, configBuilder) => { - // 1.ŪպA + // 1.讀組態檔 var environmentName = hosting.Configuration["ENVIRONMENT2"]; configBuilder.AddJsonFile("appsettings.json", false, true); @@ -166,7 +166,7 @@ public class SurveyOptionTests var appSetting = hosting.Configuration.Get(); services.AddSingleton(typeof(AppSetting), appSetting); - //`JLA + //注入其他服務 services.AddSingleton(); }) ; diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyUserSecretTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyUserSecretTests.cs index 67e005f8..32043141 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyUserSecretTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyUserSecretTests.cs @@ -11,7 +11,7 @@ namespace NetFx48 public class SurveyUserSecretTests { [TestMethod] - public void HostŪK() + public void Host讀取秘密() { var builder = Host.CreateDefaultBuilder() .ConfigureHostConfiguration(config => @@ -27,7 +27,7 @@ public class SurveyUserSecretTests } [TestMethod] - public void ʹҤƲպAŪK() + public void 手動實例化組態讀取秘密() { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) diff --git a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.QA.json b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.QA.json index 077edc27..b12c223d 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.QA.json +++ b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.QA.json @@ -2,7 +2,6 @@ "ConnectionStrings": { "DefaultConnectionString": "Server=(localdb)\\mssqllocaldb;Database=ConsoleApp.NewDb.QA;Trusted_Connection=True;" }, - "Player": { "AppId": "qa", "Key": "qa1234567890" diff --git a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.ini b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.ini index c7092637..2b9d12b9 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.ini +++ b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.ini @@ -1,6 +1,6 @@ [ConnectionStrings] -DefaultConnectionString="Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" +DefaultConnectionString = "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" [Player] -AppId=testApp -Key=12345678990 +AppId = testApp +Key = 12345678990 diff --git a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.json b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.json index 52fc1bcc..20f51da4 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.json +++ b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.json @@ -7,5 +7,5 @@ "Key": "1234567890" }, "Environment": "Development", - "ApplicationName":"NetFx48" + "ApplicationName": "NetFx48" } \ No newline at end of file diff --git a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.test.json b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.test.json index c339236f..420fcb73 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.test.json +++ b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.test.json @@ -2,7 +2,6 @@ "ConnectionStrings": { "DefaultConnectionString": "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb.Test;Trusted_Connection=True;" }, - "Player": { "AppId": "test", "Key": "test1234567890" diff --git a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.xml b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.xml index adf9ceb9..80f4425b 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/appsettings.xml +++ b/Configuration/NetCore/Lab.Config/NetFx48/appsettings.xml @@ -1,7 +1,9 @@  - Server=(localdb)\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True; + + Server=(localdb)\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True; + testApp From ca4bb98b6601ecb6d78d536c3c73a31dd0d9b34b Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 19 Jan 2022 23:21:17 +0800 Subject: [PATCH 147/424] refactor --- .../NetCore/Lab.Config/NetFx48/NetFx48.csproj | 26 ++-- .../NetFx48/SurveyJsonConfigurationTests.cs | 53 ------- .../NetFx48/SurveyMemoryConfigurationTests.cs | 130 ++++++++++++++++++ 3 files changed, 143 insertions(+), 66 deletions(-) create mode 100644 Configuration/NetCore/Lab.Config/NetFx48/SurveyMemoryConfigurationTests.cs diff --git a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj index c31a487c..92ceb1d4 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj +++ b/Configuration/NetCore/Lab.Config/NetFx48/NetFx48.csproj @@ -19,19 +19,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs index fee31ec7..8118d15a 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyJsonConfigurationTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Json; @@ -76,58 +75,6 @@ public void 注入Configuration() Console.WriteLine($"AppId = {playerId}"); } - [TestMethod] - public void 讀取記憶體組態() - { - var configBuilder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddInMemoryCollection(new Dictionary - { - { "Player:AppId", "player1" }, - { "Player:Key", "1234567890" }, - { - "ConnectionStrings:DefaultConnectionString", - "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" - }, - }) - ; - - var configRoot = configBuilder.Build(); - - //讀取組態 - - Console.WriteLine($"AppId = {configRoot["AppId"]}"); - Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); - Console.WriteLine($"Key = {configRoot["Player:Key"]}"); - Console.WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); - } - - [TestMethod] - public void 讀取記憶體組態後綁定() - { - var configBuilder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddInMemoryCollection(new Dictionary - { - { "Player:AppId", "player1" }, - { "Player:Key", "1234567890" }, - { - "ConnectionStrings:DefaultConnectionString", - "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" - }, - }) - ; - - var configRoot = configBuilder.Build(); - var appSetting = configRoot.Get(); - - //讀取組態 - - Console.WriteLine($"AppId = {appSetting.Player.AppId}"); - Console.WriteLine($"Key = {appSetting.Player.Key}"); - Console.WriteLine($"Connection String = {appSetting.ConnectionStrings.DefaultConnectionString}"); - } - [TestMethod] public void 通過Host() { diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyMemoryConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyMemoryConfigurationTests.cs new file mode 100644 index 00000000..8aba7974 --- /dev/null +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyMemoryConfigurationTests.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NetFx48 +{ + [TestClass] + public class SurveyMemoryConfigurationTests + { + [TestMethod] + public void 讀取記憶體組態() + { + var configBuilder = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { "Player:AppId", "player1" }, + { "Player:Key", "1234567890" }, + { + "ConnectionStrings:DefaultConnectionString", + "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" + }, + }) + ; + + var configRoot = configBuilder.Build(); + + //讀取組態 + + Console.WriteLine($"AppId = {configRoot["AppId"]}"); + Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); + Console.WriteLine($"Key = {configRoot["Player:Key"]}"); + Console.WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); + } + + [TestMethod] + public void 讀取記憶體組態_綁定() + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddInMemoryCollection(new Dictionary + { + { "Player:AppId", "player1" }, + { "Player:Key", "1234567890" }, + { + "ConnectionStrings:DefaultConnectionString", + "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" + }, + }) + ; + + var configRoot = configBuilder.Build(); + var appSetting = configRoot.Get(); + + //讀取組態 + + Console.WriteLine($"AppId = {appSetting.Player.AppId}"); + Console.WriteLine($"Key = {appSetting.Player.Key}"); + Console.WriteLine($"Connection String = {appSetting.ConnectionStrings.DefaultConnectionString}"); + } + + [TestMethod] + public void 讀取記憶體組態_綁定_集合() + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddInMemoryCollection(new Dictionary + { + { "a:Player:AppId", "player1" }, + { "a:Player:Key", "1234567890" }, + { + "a:ConnectionStrings:DefaultConnectionString", + "a:Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" + }, + { "b:Player:AppId", "player2" }, + { "b:Player:Key", "1234567890" }, + { + "b:ConnectionStrings:DefaultConnectionString", + "b:Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" + }, + }) + ; + + var configRoot = configBuilder.Build(); + var appSettings = configRoot.Get>(); + + //讀取組態 + + Console.WriteLine($"AppId = {appSettings[0].Player.AppId}"); + Console.WriteLine($"Key = {appSettings[0].Player.Key}"); + Console.WriteLine($"Connection String = {appSettings[0].ConnectionStrings.DefaultConnectionString}"); + } + + [TestMethod] + public void 讀取記憶體組態_綁定_字典() + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddInMemoryCollection(new Dictionary + { + { "a:Player:AppId", "player1" }, + { "a:Player:Key", "1234567890" }, + { + "a:ConnectionStrings:DefaultConnectionString", + "a:Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" + }, + { "b:Player:AppId", "player2" }, + { "b:Player:Key", "1234567890" }, + { + "b:ConnectionStrings:DefaultConnectionString", + "b:Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;" + }, + }) + ; + + var configRoot = configBuilder.Build(); + var appSettings = configRoot.Get>(); + + //讀取組態 + + Console.WriteLine($"AppId = {appSettings["a"].Player.AppId}"); + Console.WriteLine($"Key = {appSettings["a"].Player.Key}"); + Console.WriteLine($"Connection String = {appSettings["a"].ConnectionStrings.DefaultConnectionString}"); + Console.WriteLine($"AppId = {appSettings["b"].Player.AppId}"); + Console.WriteLine($"Key = {appSettings["b"].Player.Key}"); + Console.WriteLine($"Connection String = {appSettings["b"].ConnectionStrings.DefaultConnectionString}"); + } + } +} \ No newline at end of file From fee8cedd52959a956045bc4ef6873e35e871903c Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 19 Jan 2022 23:43:25 +0800 Subject: [PATCH 148/424] add test case --- ...yEnvironmentVariablesConfigurationTests.cs | 173 ++++++++++++++---- 1 file changed, 134 insertions(+), 39 deletions(-) diff --git a/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs b/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs index 773448a6..aed350b8 100644 --- a/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs +++ b/Configuration/NetCore/Lab.Config/NetFx48/SurveyEnvironmentVariablesConfigurationTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -15,25 +16,25 @@ public void Host實例化ConfigurationBuilder() { var builder = Host.CreateDefaultBuilder() .ConfigureAppConfiguration((hosting, configBuilder) => - { - // config.Sources.Clear(); - var hostingEnvironmentEnvironmentName = - hosting.HostingEnvironment.EnvironmentName; - configBuilder.AddEnvironmentVariables("Custom_"); - var configRoot = configBuilder.Build(); - - //讀取組態 - Console - .WriteLine($"ASPNETCORE_ENVIRONMENT = {configRoot["ASPNETCORE_ENVIRONMENT"]}"); - Console - .WriteLine($"DOTNET_ENVIRONMENT = {configRoot["DOTNET_ENVIRONMENT"]}"); - Console - .WriteLine($"CUSTOM_ENVIRONMENT = {configRoot["CUSTOM_ENVIRONMENT"]}"); - Console - .WriteLine($"ENVIRONMENT1 = {configRoot["ENVIRONMENT1"]}"); - }) + { + // config.Sources.Clear(); + var hostingEnvironmentEnvironmentName = + hosting.HostingEnvironment.EnvironmentName; + configBuilder.AddEnvironmentVariables("Custom_"); + var configRoot = configBuilder.Build(); + + //讀取組態 + Console + .WriteLine($"ASPNETCORE_ENVIRONMENT = {configRoot["ASPNETCORE_ENVIRONMENT"]}"); + Console + .WriteLine($"DOTNET_ENVIRONMENT = {configRoot["DOTNET_ENVIRONMENT"]}"); + Console + .WriteLine($"CUSTOM_ENVIRONMENT = {configRoot["CUSTOM_ENVIRONMENT"]}"); + Console + .WriteLine($"ENVIRONMENT1 = {configRoot["ENVIRONMENT1"]}"); + }) ; - var host = builder.Build(); + var host = builder.Build(); var environment = host.Services.GetRequiredService(); Console.WriteLine($"EnvironmentName={environment.EnvironmentName}"); } @@ -43,23 +44,23 @@ public void 切換組態設定() { var builder = Host.CreateDefaultBuilder() .ConfigureAppConfiguration((hosting, configBuilder) => - { - // config.Sources.Clear(); - var environmentName = - hosting.Configuration["ENVIRONMENT2"]; - configBuilder.AddJsonFile("appsettings.json", false, true); - configBuilder - .AddJsonFile($"appsettings.{environmentName}.json", - true, true); - - var configRoot = configBuilder.Build(); - - //讀取組態 - Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); - Console.WriteLine($"Key = {configRoot["Player:Key"]}"); - Console - .WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); - }) + { + // config.Sources.Clear(); + var environmentName = + hosting.Configuration["ENVIRONMENT2"]; + configBuilder.AddJsonFile("appsettings.json", false, true); + configBuilder + .AddJsonFile($"appsettings.{environmentName}.json", + true, true); + + var configRoot = configBuilder.Build(); + + //讀取組態 + Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); + Console.WriteLine($"Key = {configRoot["Player:Key"]}"); + Console + .WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); + }) ; builder.Build(); } @@ -83,14 +84,108 @@ public void 設定主機組態() { var builder = Host.CreateDefaultBuilder() .ConfigureHostConfiguration(config => - { - config.AddJsonFile("appsettings.json", false, true); - }) + { + config.AddJsonFile("appsettings.json", false, true); + }) ; - var host = builder.Build(); + var host = builder.Build(); var environment = host.Services.GetRequiredService(); Console.WriteLine($"EnvironmentName={environment.EnvironmentName}"); } + + [TestMethod] + public void 讀取環境變數() + { + Environment.SetEnvironmentVariable("Player:AppId", "player1"); + Environment.SetEnvironmentVariable("Player:Key", "1234567890"); + Environment.SetEnvironmentVariable("ConnectionStrings:DefaultConnectionString", + "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;"); + var configBuilder = new ConfigurationBuilder().AddEnvironmentVariables(); + + var configRoot = configBuilder.Build(); + + //讀取組態 + + Console.WriteLine($"AppId = {configRoot["AppId"]}"); + Console.WriteLine($"AppId = {configRoot["Player:AppId"]}"); + Console.WriteLine($"Key = {configRoot["Player:Key"]}"); + Console.WriteLine($"Connection String = {configRoot["ConnectionStrings:DefaultConnectionString"]}"); + } + + + [TestMethod] + public void 讀取環境變數_綁定() + { + Environment.SetEnvironmentVariable("Player:AppId", "player1"); + Environment.SetEnvironmentVariable("Player:Key", "1234567890"); + Environment.SetEnvironmentVariable("ConnectionStrings:DefaultConnectionString", + "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;"); + var configBuilder = new ConfigurationBuilder().AddEnvironmentVariables(); + + var configRoot = configBuilder.Build(); + var appSetting = configRoot.Get(); + + //讀取組態 + + Console.WriteLine($"AppId = {appSetting.Player.AppId}"); + Console.WriteLine($"Key = {appSetting.Player.Key}"); + Console.WriteLine($"Connection String = {appSetting.ConnectionStrings.DefaultConnectionString}"); + } + + [TestMethod] + public void 讀取環境變數_綁定_集合() + { + Environment.SetEnvironmentVariable("a:Player:AppId", "player1"); + Environment.SetEnvironmentVariable("a:Player:Key", "1234567890"); + Environment.SetEnvironmentVariable("a:ConnectionStrings:DefaultConnectionString", + "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;"); + Environment.SetEnvironmentVariable("b:Player:AppId", "player2"); + Environment.SetEnvironmentVariable("b:Player:Key", "1234567890"); + Environment.SetEnvironmentVariable("b:ConnectionStrings:DefaultConnectionString", + "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;"); + + var configBuilder = new ConfigurationBuilder().AddEnvironmentVariables(); + + var configRoot = configBuilder.Build(); + var appSettings = configRoot.Get>(); + + //讀取組態 + + Console.WriteLine($"AppId = {appSettings[0].Player.AppId}"); + Console.WriteLine($"Key = {appSettings[0].Player.Key}"); + Console.WriteLine($"Connection String = {appSettings[0].ConnectionStrings.DefaultConnectionString}"); + Console.WriteLine($"AppId = {appSettings[1].Player.AppId}"); + Console.WriteLine($"Key = {appSettings[1].Player.Key}"); + Console.WriteLine($"Connection String = {appSettings[1].ConnectionStrings.DefaultConnectionString}"); + } + + [TestMethod] + public void 讀取環境變數_綁定_字典() + { + Environment.SetEnvironmentVariable("a:Player:AppId", "player1"); + Environment.SetEnvironmentVariable("a:Player:Key", "1234567890"); + Environment.SetEnvironmentVariable("a:ConnectionStrings:DefaultConnectionString", + "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;"); + Environment.SetEnvironmentVariable("b:Player:AppId", "player2"); + Environment.SetEnvironmentVariable("b:Player:Key", "1234567890"); + Environment.SetEnvironmentVariable("b:ConnectionStrings:DefaultConnectionString", + "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;"); + + var configBuilder = new ConfigurationBuilder().AddEnvironmentVariables(); + + var configRoot = configBuilder.Build(); + var appSettings = configRoot.Get>(); + + //讀取組態 + + Console.WriteLine($"AppId = {appSettings["a"].Player.AppId}"); + Console.WriteLine($"Key = {appSettings["a"].Player.Key}"); + Console.WriteLine($"Connection String = {appSettings["a"].ConnectionStrings.DefaultConnectionString}"); + Console.WriteLine($"AppId = {appSettings["b"].Player.AppId}"); + Console.WriteLine($"Key = {appSettings["b"].Player.Key}"); + Console.WriteLine($"Connection String = {appSettings["b"].ConnectionStrings.DefaultConnectionString}"); + } + } } \ No newline at end of file From 23bd21a9c729910bc8f7d39ea89799a7d83cc521 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 20 Jan 2022 00:46:40 +0800 Subject: [PATCH 149/424] feat: Env File Config --- .../Lab.EnvFileConfig.TestProject.csproj | 28 +++++++++++++++ .../UnitTest1.cs | 22 ++++++++++++ .../Lab.EnvFileConfig.TestProject/secret.env | 2 ++ .../Lab.EnvFileConfig/Lab.EnvFileConfig.sln | 22 ++++++++++++ .../EnvFileConfigurationExtensions.cs | 15 ++++++++ .../EnvFileConfigurationProvider.cs | 36 +++++++++++++++++++ .../EnvFileConfigurationSource.cs | 19 ++++++++++ .../Lab.EnvFileConfig.csproj | 15 ++++++++ .../Lab.EnvFileConfig/Program.cs | 32 +++++++++++++++++ 9 files changed, 191 insertions(+) create mode 100644 Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/Lab.EnvFileConfig.TestProject.csproj create mode 100644 Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/UnitTest1.cs create mode 100644 Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/secret.env create mode 100644 Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.sln create mode 100644 Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/EnvFileConfigurationExtensions.cs create mode 100644 Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/EnvFileConfigurationProvider.cs create mode 100644 Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/EnvFileConfigurationSource.cs create mode 100644 Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Lab.EnvFileConfig.csproj create mode 100644 Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Program.cs diff --git a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/Lab.EnvFileConfig.TestProject.csproj b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/Lab.EnvFileConfig.TestProject.csproj new file mode 100644 index 00000000..03a75833 --- /dev/null +++ b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/Lab.EnvFileConfig.TestProject.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + + + + Always + + + + diff --git a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/UnitTest1.cs b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/UnitTest1.cs new file mode 100644 index 00000000..79283a3b --- /dev/null +++ b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/UnitTest1.cs @@ -0,0 +1,22 @@ +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.EnvFileConfig.TestProject; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestMethod1() + { + var configRoot = new ConfigurationBuilder() + + // .AddJsonFile("appSettings.json") + .AddEnvFile("secret.env") + .Build() + ; + var section = configRoot.GetSection("SQL_SERVER_CS"); + Console.WriteLine($"Value = {section.Value}"); + } +} \ No newline at end of file diff --git a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/secret.env b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/secret.env new file mode 100644 index 00000000..fc2d45ae --- /dev/null +++ b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/secret.env @@ -0,0 +1,2 @@ +SQL_SERVER_CS=foo-bar +REDIS_ENDPOINT=localhost:6379 \ No newline at end of file diff --git a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.sln b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.sln new file mode 100644 index 00000000..7a85e196 --- /dev/null +++ b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.EnvFileConfig", "Lab.EnvFileConfig\Lab.EnvFileConfig.csproj", "{2982F90A-3127-4E7E-8DC3-44512C0CE1E2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.EnvFileConfig.TestProject", "Lab.EnvFileConfig.TestProject\Lab.EnvFileConfig.TestProject.csproj", "{6C8009BE-E88B-4A97-9D44-AE753BEBE2E4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2982F90A-3127-4E7E-8DC3-44512C0CE1E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2982F90A-3127-4E7E-8DC3-44512C0CE1E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2982F90A-3127-4E7E-8DC3-44512C0CE1E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2982F90A-3127-4E7E-8DC3-44512C0CE1E2}.Release|Any CPU.Build.0 = Release|Any CPU + {6C8009BE-E88B-4A97-9D44-AE753BEBE2E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C8009BE-E88B-4A97-9D44-AE753BEBE2E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C8009BE-E88B-4A97-9D44-AE753BEBE2E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C8009BE-E88B-4A97-9D44-AE753BEBE2E4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/EnvFileConfigurationExtensions.cs b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/EnvFileConfigurationExtensions.cs new file mode 100644 index 00000000..24143051 --- /dev/null +++ b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/EnvFileConfigurationExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.Configuration; + +namespace Lab.EnvFileConfig +{ + public static class EnvFileConfigurationExtensions + { + public static IConfigurationBuilder AddEnvFile(this IConfigurationBuilder builder, string envFile) + { + var source = new EnvFileConfigurationSource(envFile); + builder.Add(source); + builder.AddEnvironmentVariables(); + return builder; + } + } +} \ No newline at end of file diff --git a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/EnvFileConfigurationProvider.cs b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/EnvFileConfigurationProvider.cs new file mode 100644 index 00000000..4be70094 --- /dev/null +++ b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/EnvFileConfigurationProvider.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Configuration; + +namespace Lab.EnvFileConfig +{ + public class EnvFileConfigurationProvider : ConfigurationProvider + { + private readonly string _envFile; + + public EnvFileConfigurationProvider(string envFile) + { + this._envFile = envFile; + } + + public override void Load() + { + if (!File.Exists(this._envFile)) + { + return; + } + + foreach (var line in File.ReadAllLines(this._envFile)) + { + var parts = line.Split( + '=', + StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length != 2) + { + continue; + } + + Environment.SetEnvironmentVariable(parts[0], parts[1]); + } + } + } +} \ No newline at end of file diff --git a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/EnvFileConfigurationSource.cs b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/EnvFileConfigurationSource.cs new file mode 100644 index 00000000..cc0c8e40 --- /dev/null +++ b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/EnvFileConfigurationSource.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Configuration; + +namespace Lab.EnvFileConfig +{ + public class EnvFileConfigurationSource : IConfigurationSource + { + private readonly string _envFile; + + public EnvFileConfigurationSource(string envFile) + { + this._envFile = envFile; + } + + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + return new EnvFileConfigurationProvider(this._envFile); + } + } +} \ No newline at end of file diff --git a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Lab.EnvFileConfig.csproj b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Lab.EnvFileConfig.csproj new file mode 100644 index 00000000..f3baa1a4 --- /dev/null +++ b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Lab.EnvFileConfig.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + + + + + + + + + diff --git a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Program.cs b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Program.cs new file mode 100644 index 00000000..c4d3d476 --- /dev/null +++ b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Program.cs @@ -0,0 +1,32 @@ +// using System.Diagnostics; +// using Lab.EnvFileConfig; +// using Microsoft.Extensions.Configuration; +// +// namespace App +// { +// public class Program +// { +// private static void Main() +// { +// var initialSettings = new Dictionary +// { +// ["Gender"] = "Male", +// ["Age"] = "18", +// ["ContactInfo:EmailAddress"] = "foobar@outlook.com", +// ["ContactInfo:PhoneNo"] = "123456789" +// }; +// +// var profile = new ConfigurationBuilder() +// .AddJsonFile("appSettings.json") +// .AddDatabase("DefaultDb", initialSettings) +// .Build() +// .Get(); +// +// Debug.Assert(profile.Gender == Gender.Male); +// Debug.Assert(profile.Age == 18); +// Debug.Assert(profile.ContactInfo.EmailAddress == "foobar@outlook.com"); +// Debug.Assert(profile.ContactInfo.PhoneNo == "123456789"); +// } +// } +// } + From 9a0ea0cc19b189309d50bb856a34634810bfc7f3 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 20 Jan 2022 17:50:50 +0800 Subject: [PATCH 150/424] refactor --- .../Lab.EnvFileConfig.TestProject/UnitTest1.cs | 15 ++++++++++++++- .../Lab.EnvFileConfig/AppSetting.cs | 8 ++++++++ .../Lab.EnvFileConfig/Lab.EnvFileConfig.csproj | 1 + 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/AppSetting.cs diff --git a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/UnitTest1.cs b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/UnitTest1.cs index 79283a3b..27513906 100644 --- a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/UnitTest1.cs +++ b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig.TestProject/UnitTest1.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Microsoft.Extensions.Configuration; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -8,7 +9,7 @@ namespace Lab.EnvFileConfig.TestProject; public class UnitTest1 { [TestMethod] - public void TestMethod1() + public void 讀取ENV檔案() { var configRoot = new ConfigurationBuilder() @@ -19,4 +20,16 @@ public void TestMethod1() var section = configRoot.GetSection("SQL_SERVER_CS"); Console.WriteLine($"Value = {section.Value}"); } + + [TestMethod] + public void 讀取ENV檔案後綁定() + { + var configRoot = new ConfigurationBuilder() + .AddEnvFile("secret.env") + .Build() + ; + var appSetting = configRoot.Get(); + Assert.AreEqual("foo-bar", appSetting.SQL_SERVER_CS); + Assert.AreEqual("localhost:6379", appSetting.REDIS_ENDPOINT); + } } \ No newline at end of file diff --git a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/AppSetting.cs b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/AppSetting.cs new file mode 100644 index 00000000..78d54413 --- /dev/null +++ b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/AppSetting.cs @@ -0,0 +1,8 @@ +namespace Lab.EnvFileConfig; + +public class AppSetting +{ + public string SQL_SERVER_CS { get; set; } + + public string REDIS_ENDPOINT { get; set; } +} \ No newline at end of file diff --git a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Lab.EnvFileConfig.csproj b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Lab.EnvFileConfig.csproj index f3baa1a4..a7cfe4a7 100644 --- a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Lab.EnvFileConfig.csproj +++ b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Lab.EnvFileConfig.csproj @@ -9,6 +9,7 @@ + From c1dd221ac41b123e46f4c6f9b5ff022774b9f6cd Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 20 Jan 2022 19:12:23 +0800 Subject: [PATCH 151/424] refactor --- .../Lab.EnvFileConfig.csproj | 8 ++--- .../Lab.EnvFileConfig/Program.cs | 32 ------------------- 2 files changed, 4 insertions(+), 36 deletions(-) delete mode 100644 Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Program.cs diff --git a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Lab.EnvFileConfig.csproj b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Lab.EnvFileConfig.csproj index a7cfe4a7..48c93535 100644 --- a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Lab.EnvFileConfig.csproj +++ b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Lab.EnvFileConfig.csproj @@ -7,10 +7,10 @@ - - - - + + + + diff --git a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Program.cs b/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Program.cs deleted file mode 100644 index c4d3d476..00000000 --- a/Configuration/Lab.EnvFileConfig/Lab.EnvFileConfig/Program.cs +++ /dev/null @@ -1,32 +0,0 @@ -// using System.Diagnostics; -// using Lab.EnvFileConfig; -// using Microsoft.Extensions.Configuration; -// -// namespace App -// { -// public class Program -// { -// private static void Main() -// { -// var initialSettings = new Dictionary -// { -// ["Gender"] = "Male", -// ["Age"] = "18", -// ["ContactInfo:EmailAddress"] = "foobar@outlook.com", -// ["ContactInfo:PhoneNo"] = "123456789" -// }; -// -// var profile = new ConfigurationBuilder() -// .AddJsonFile("appSettings.json") -// .AddDatabase("DefaultDb", initialSettings) -// .Build() -// .Get(); -// -// Debug.Assert(profile.Gender == Gender.Male); -// Debug.Assert(profile.Age == 18); -// Debug.Assert(profile.ContactInfo.EmailAddress == "foobar@outlook.com"); -// Debug.Assert(profile.ContactInfo.PhoneNo == "123456789"); -// } -// } -// } - From da6e29d6a817243d9697008738e0261b0d5f3e8f Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 13 Feb 2022 20:01:04 +0800 Subject: [PATCH 152/424] add project --- Test/MultiTestCase/Makefile | 4 + Test/MultiTestCase/MultiTestCase.sln | 48 ++++++++++++ Test/MultiTestCase/docker-compose.yml | 10 +++ .../EmployeeAggregate/Entity/Employee.cs | 29 +++++++ .../EmployeeAggregate/IEmployeeAggregate.cs | 8 ++ .../Repository/IEmployeeRepository.cs | 16 ++++ .../src/Lab.Domain/Lab.Domain.csproj | 9 +++ .../AppDependencyInjectionExtensions.cs | 52 +++++++++++++ .../AppEnvironmentOption.cs | 31 ++++++++ .../EntityModel/Employee.cs | 30 ++++++++ .../EntityModel/EmployeeDbContext.cs | 75 +++++++++++++++++++ .../EntityModel/Identity.cs | 33 ++++++++ .../EntityModel/OrderHistory.cs | 30 ++++++++ .../EnvironmentAssistant.cs | 15 ++++ .../Lab.Infrastructure.DB.csproj | 15 ++++ .../Lab.MultiTestCase.UnitTest.csproj | 28 +++++++ .../Lab.MultiTestCase.UnitTest/MsTestHook.cs | 31 ++++++++ .../TestInstanceManager.cs | 34 +++++++++ .../Lab.MultiTestCase.UnitTest/UnitTest1.cs | 24 ++++++ .../Lab.MultiTestCase.csproj | 14 ++++ 20 files changed, 536 insertions(+) create mode 100644 Test/MultiTestCase/Makefile create mode 100644 Test/MultiTestCase/MultiTestCase.sln create mode 100644 Test/MultiTestCase/docker-compose.yml create mode 100644 Test/MultiTestCase/src/Lab.Domain/EmployeeAggregate/Entity/Employee.cs create mode 100644 Test/MultiTestCase/src/Lab.Domain/EmployeeAggregate/IEmployeeAggregate.cs create mode 100644 Test/MultiTestCase/src/Lab.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs create mode 100644 Test/MultiTestCase/src/Lab.Domain/Lab.Domain.csproj create mode 100644 Test/MultiTestCase/src/Lab.Infrastructure.DB/AppDependencyInjectionExtensions.cs create mode 100644 Test/MultiTestCase/src/Lab.Infrastructure.DB/AppEnvironmentOption.cs create mode 100644 Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/Employee.cs create mode 100644 Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/EmployeeDbContext.cs create mode 100644 Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/Identity.cs create mode 100644 Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/OrderHistory.cs create mode 100644 Test/MultiTestCase/src/Lab.Infrastructure.DB/EnvironmentAssistant.cs create mode 100644 Test/MultiTestCase/src/Lab.Infrastructure.DB/Lab.Infrastructure.DB.csproj create mode 100644 Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/Lab.MultiTestCase.UnitTest.csproj create mode 100644 Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/MsTestHook.cs create mode 100644 Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/TestInstanceManager.cs create mode 100644 Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/UnitTest1.cs create mode 100644 Test/MultiTestCase/src/Lab.MultiTestCase/Lab.MultiTestCase.csproj diff --git a/Test/MultiTestCase/Makefile b/Test/MultiTestCase/Makefile new file mode 100644 index 00000000..cf17f07f --- /dev/null +++ b/Test/MultiTestCase/Makefile @@ -0,0 +1,4 @@ +G1: + echo 'Hello World' +G2: + cmd \ No newline at end of file diff --git a/Test/MultiTestCase/MultiTestCase.sln b/Test/MultiTestCase/MultiTestCase.sln new file mode 100644 index 00000000..fe689a03 --- /dev/null +++ b/Test/MultiTestCase/MultiTestCase.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{1F776B7D-C182-4DC3-BA57-844657BD9AC0}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + Makefile = Makefile + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{483A9EB7-C4AF-4D68-80ED-49277985C760}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.MultiTestCase", "src\Lab.MultiTestCase\Lab.MultiTestCase.csproj", "{8F6CEAB7-4486-4AD6-8AAE-5DBEDA449E57}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.MultiTestCase.UnitTest", "src\Lab.MultiTestCase.UnitTest\Lab.MultiTestCase.UnitTest.csproj", "{8F1C53C4-CE7C-447A-B627-3D428DA818A2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Domain", "src\Lab.Domain\Lab.Domain.csproj", "{A3EFE099-E9FB-41F4-9718-087AFA4E0A7B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Infrastructure.DB", "src\Lab.Infrastructure.DB\Lab.Infrastructure.DB.csproj", "{CD1E06DF-79FC-4189-88D8-05F4E5C8DC7B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8F6CEAB7-4486-4AD6-8AAE-5DBEDA449E57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F6CEAB7-4486-4AD6-8AAE-5DBEDA449E57}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F6CEAB7-4486-4AD6-8AAE-5DBEDA449E57}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F6CEAB7-4486-4AD6-8AAE-5DBEDA449E57}.Release|Any CPU.Build.0 = Release|Any CPU + {8F1C53C4-CE7C-447A-B627-3D428DA818A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F1C53C4-CE7C-447A-B627-3D428DA818A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F1C53C4-CE7C-447A-B627-3D428DA818A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F1C53C4-CE7C-447A-B627-3D428DA818A2}.Release|Any CPU.Build.0 = Release|Any CPU + {A3EFE099-E9FB-41F4-9718-087AFA4E0A7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3EFE099-E9FB-41F4-9718-087AFA4E0A7B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3EFE099-E9FB-41F4-9718-087AFA4E0A7B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3EFE099-E9FB-41F4-9718-087AFA4E0A7B}.Release|Any CPU.Build.0 = Release|Any CPU + {CD1E06DF-79FC-4189-88D8-05F4E5C8DC7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD1E06DF-79FC-4189-88D8-05F4E5C8DC7B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD1E06DF-79FC-4189-88D8-05F4E5C8DC7B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD1E06DF-79FC-4189-88D8-05F4E5C8DC7B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {8F6CEAB7-4486-4AD6-8AAE-5DBEDA449E57} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {8F1C53C4-CE7C-447A-B627-3D428DA818A2} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {A3EFE099-E9FB-41F4-9718-087AFA4E0A7B} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {CD1E06DF-79FC-4189-88D8-05F4E5C8DC7B} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + EndGlobalSection +EndGlobal diff --git a/Test/MultiTestCase/docker-compose.yml b/Test/MultiTestCase/docker-compose.yml new file mode 100644 index 00000000..8ecb1567 --- /dev/null +++ b/Test/MultiTestCase/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.8" + +services: + db-sql: + image: mcr.microsoft.com/mssql/server:2019-latest + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=pass@w0rd1~ + ports: + - 1433:1433 \ No newline at end of file diff --git a/Test/MultiTestCase/src/Lab.Domain/EmployeeAggregate/Entity/Employee.cs b/Test/MultiTestCase/src/Lab.Domain/EmployeeAggregate/Entity/Employee.cs new file mode 100644 index 00000000..85d92ad1 --- /dev/null +++ b/Test/MultiTestCase/src/Lab.Domain/EmployeeAggregate/Entity/Employee.cs @@ -0,0 +1,29 @@ +namespace Lab.Domain.Entity; + +public record Employee +{ + public Guid Id { get; set; } + + public string Name { get; set; } + + public int? Age { get; set; } + + public string Remark { get; set; } + + public DateTimeOffset CreateAt { get; set; } + + public string CreateBy { get; set; } + + public Employee SetName(string name) + { + this.Name = name; + return this; + } + + public Employee SetAge(int age) + { + this.Age = age; + return this; + } + +} \ No newline at end of file diff --git a/Test/MultiTestCase/src/Lab.Domain/EmployeeAggregate/IEmployeeAggregate.cs b/Test/MultiTestCase/src/Lab.Domain/EmployeeAggregate/IEmployeeAggregate.cs new file mode 100644 index 00000000..4d72246e --- /dev/null +++ b/Test/MultiTestCase/src/Lab.Domain/EmployeeAggregate/IEmployeeAggregate.cs @@ -0,0 +1,8 @@ +using Lab.Domain.Entity; + +namespace Lab.Domain; + +public interface IEmployeeAggregate +{ + Employee InsertAsync(Employee employee, CancellationToken cancel = default); +} \ No newline at end of file diff --git a/Test/MultiTestCase/src/Lab.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs b/Test/MultiTestCase/src/Lab.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs new file mode 100644 index 00000000..898a9e1d --- /dev/null +++ b/Test/MultiTestCase/src/Lab.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs @@ -0,0 +1,16 @@ +using Lab.Domain.Entity; + +namespace Lab.Domain.Repository; + +public interface IEmployeeRepository +{ + Task InsertAsync(Employee employee, CancellationToken cancel = default); +} + +class EmployeeRepository : IEmployeeRepository +{ + public Task InsertAsync(Employee employee, CancellationToken cancel = default) + { + throw new NotImplementedException(); + } +} diff --git a/Test/MultiTestCase/src/Lab.Domain/Lab.Domain.csproj b/Test/MultiTestCase/src/Lab.Domain/Lab.Domain.csproj new file mode 100644 index 00000000..eb2460e9 --- /dev/null +++ b/Test/MultiTestCase/src/Lab.Domain/Lab.Domain.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/Test/MultiTestCase/src/Lab.Infrastructure.DB/AppDependencyInjectionExtensions.cs b/Test/MultiTestCase/src/Lab.Infrastructure.DB/AppDependencyInjectionExtensions.cs new file mode 100644 index 00000000..c0960243 --- /dev/null +++ b/Test/MultiTestCase/src/Lab.Infrastructure.DB/AppDependencyInjectionExtensions.cs @@ -0,0 +1,52 @@ +using Lab.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.Infrastructure.DB; + +public static class AppDependencyInjectionExtensions +{ + public static void AddAppEnvironment(this IServiceCollection services) + { + services.AddLogging(builder => + { + builder.AddConsole(); + }); + services.AddSingleton(); + } + + public static void AddEntityFramework(this IServiceCollection services) + { + services.AddPooledDbContextFactory((provider, optionsBuilder) => + { + var option = provider.GetService(); + var connectionString = option.EmployeeDbConnectionString; + var loggerFactory = provider.GetService(); + optionsBuilder.UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + ; + }); + + ; + + // services.AddPooledDbContextFactory((provider, options) => + // { + // var option = provider.GetService(); + // var loggerFactory = provider.GetService(); + // options.UseNpgsql( + // option.MemberDbConnectionString, //只會呼叫一次 + // builder => + // builder.EnableRetryOnFailure( + // 10, + // TimeSpan.FromSeconds(30), + // new List { "57P01" })) + // + // // .UseLazyLoadingProxies() + // .UseSnakeCaseNamingConvention() + // .UseLoggerFactory(loggerFactory) + // ; + // }); + } + +} \ No newline at end of file diff --git a/Test/MultiTestCase/src/Lab.Infrastructure.DB/AppEnvironmentOption.cs b/Test/MultiTestCase/src/Lab.Infrastructure.DB/AppEnvironmentOption.cs new file mode 100644 index 00000000..eb657155 --- /dev/null +++ b/Test/MultiTestCase/src/Lab.Infrastructure.DB/AppEnvironmentOption.cs @@ -0,0 +1,31 @@ +namespace Lab.Infrastructure.DB; + +public class AppEnvironmentOption +{ + public string EmployeeDbConnectionString + { + get + { + if (string.IsNullOrWhiteSpace(this._employeeDbConnectionString)) + { + this._employeeDbConnectionString = + EnvironmentAssistant.GetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR); + } + + return this._employeeDbConnectionString; + } + set + { + this._employeeDbConnectionString = value; + Environment.SetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR, value); + } + } + + private string _employeeDbConnectionString; + private readonly string EMPLOYEE_DB_CONN_STR = "EMPLOYEE_DB_CONNECTION_STR"; + + public void Initial() + { + var memberDbConnectionString = this.EmployeeDbConnectionString; + } +} \ No newline at end of file diff --git a/Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/Employee.cs b/Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/Employee.cs new file mode 100644 index 00000000..f90ea3ad --- /dev/null +++ b/Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/Employee.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.Infrastructure.DB.EntityModel +{ + [Table("Employee")] + public class Employee + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Id { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + public string Remark { get; set; } + + [Required] + public DateTimeOffset CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + public virtual Identity Identity { get; set; } + } +} \ No newline at end of file diff --git a/Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/EmployeeDbContext.cs b/Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/EmployeeDbContext.cs new file mode 100644 index 00000000..e657ab04 --- /dev/null +++ b/Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/EmployeeDbContext.cs @@ -0,0 +1,75 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; + +namespace Lab.Infrastructure.DB.EntityModel +{ + public class EmployeeDbContext : DbContext + { + private static readonly bool[] s_migrated = { false }; + + public virtual DbSet Employees { get; set; } + + public virtual DbSet Identities { get; set; } + + public virtual DbSet OrderHistories { get; set; } + + public EmployeeDbContext(DbContextOptions options) + : base(options) + { + if (s_migrated[0]) + { + return; + } + + lock (s_migrated) + { + if (s_migrated[0] == false) + { + var memoryOptions = options.FindExtension(); + + if (memoryOptions == null) + { + var sqlOptions = options.FindExtension(); + if (sqlOptions != null) + { + Console.WriteLine( + $"EmployeeDbContext of connection string be '{sqlOptions.ConnectionString}'"); + this.Database.Migrate(); + } + } + + s_migrated[0] = true; + } + } + } + + //管理索引 + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(p => + { + p.HasKey(e => e.Id) + .IsClustered(false); + + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + + p.Property(p => p.Remark) + .IsRequired(false) + ; + }); + + modelBuilder.Entity(p => + { + p.HasKey(e => e.Employee_Id) + .IsClustered(false); + + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + }); + } + } +} \ No newline at end of file diff --git a/Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/Identity.cs b/Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/Identity.cs new file mode 100644 index 00000000..fe8b8e3b --- /dev/null +++ b/Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/Identity.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.Infrastructure.DB.EntityModel +{ + [Table("Identity")] + public class Identity + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Employee_Id { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + [Required] + public DateTimeOffset CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + [ForeignKey("Employee_Id")] + public virtual Employee Employee { get; set; } + } +} \ No newline at end of file diff --git a/Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/OrderHistory.cs b/Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/OrderHistory.cs new file mode 100644 index 00000000..0eb33d75 --- /dev/null +++ b/Test/MultiTestCase/src/Lab.Infrastructure.DB/EntityModel/OrderHistory.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.Infrastructure.DB.EntityModel +{ + [Table("OrderHistory")] + public class OrderHistory + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + public Guid? Employee_Id { get; set; } + + public string Remark { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Product_Id { get; set; } + + public string Product_Name { get; set; } + + [Required] + public DateTime CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + } +} \ No newline at end of file diff --git a/Test/MultiTestCase/src/Lab.Infrastructure.DB/EnvironmentAssistant.cs b/Test/MultiTestCase/src/Lab.Infrastructure.DB/EnvironmentAssistant.cs new file mode 100644 index 00000000..72a3c6ee --- /dev/null +++ b/Test/MultiTestCase/src/Lab.Infrastructure.DB/EnvironmentAssistant.cs @@ -0,0 +1,15 @@ +namespace Lab.Infrastructure.DB; + +public class EnvironmentAssistant +{ + public static string GetEnvironmentVariable(string key) + { + var result = Environment.GetEnvironmentVariable(key); + if (string.IsNullOrWhiteSpace(result)) + { + throw new Exception($"the key '{key}' not exist in environment variable"); + } + + return result; + } +} \ No newline at end of file diff --git a/Test/MultiTestCase/src/Lab.Infrastructure.DB/Lab.Infrastructure.DB.csproj b/Test/MultiTestCase/src/Lab.Infrastructure.DB/Lab.Infrastructure.DB.csproj new file mode 100644 index 00000000..9fcdb0d8 --- /dev/null +++ b/Test/MultiTestCase/src/Lab.Infrastructure.DB/Lab.Infrastructure.DB.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + + + + + + + + + diff --git a/Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/Lab.MultiTestCase.UnitTest.csproj b/Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/Lab.MultiTestCase.UnitTest.csproj new file mode 100644 index 00000000..ecb158eb --- /dev/null +++ b/Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/Lab.MultiTestCase.UnitTest.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + + + + + + + + diff --git a/Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/MsTestHook.cs b/Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/MsTestHook.cs new file mode 100644 index 00000000..c48c99fe --- /dev/null +++ b/Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/MsTestHook.cs @@ -0,0 +1,31 @@ +// using Microsoft.VisualStudio.TestTools.UnitTesting; +// +// namespace Lab.MultiTestCase.UnitTest; +// +// [TestClass] +// public class MsTestHook +// { +// [AssemblyCleanup] +// public static void Cleanup() +// { +// TestInstanceManager.SetTestEnvironmentVariable(); +// var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); +// if (db.Database.CanConnect()) +// { +// db.Database.EnsureDeleted(); +// } +// } +// +// [AssemblyInitialize] +// public static void Setup(TestContext context) +// { +// TestInstanceManager.SetTestEnvironmentVariable(); +// var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); +// if (db.Database.CanConnect()) +// { +// db.Database.EnsureDeleted(); +// } +// +// db.Database.EnsureCreated(); +// } +// } \ No newline at end of file diff --git a/Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/TestInstanceManager.cs b/Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/TestInstanceManager.cs new file mode 100644 index 00000000..16ca6112 --- /dev/null +++ b/Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/TestInstanceManager.cs @@ -0,0 +1,34 @@ +// using System; +// using Lab.MultiTestCase.EntityModel; +// using Microsoft.EntityFrameworkCore; +// using Microsoft.Extensions.DependencyInjection; +// +// namespace Lab.MultiTestCase.UnitTest; +// +// internal class TestInstanceManager +// { +// private static IServiceProvider _serviceProvider; +// +// public static IDbContextFactory EmployeeDbContextFactory => +// _serviceProvider.GetService>(); +// +// static TestInstanceManager() +// { +// var services = new ServiceCollection(); +// ConfigureTestServices(services); +// } +// +// public static void ConfigureTestServices(IServiceCollection services) +// { +// services.AddAppEnvironment(); +// services.AddEntityFramework(); +// _serviceProvider = services.BuildServiceProvider(); +// } +// +// public static void SetTestEnvironmentVariable() +// { +// var option = _serviceProvider.GetService(); +// option.EmployeeDbConnectionString = +// "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True"; +// } +// } \ No newline at end of file diff --git a/Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/UnitTest1.cs b/Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/UnitTest1.cs new file mode 100644 index 00000000..79d1a00c --- /dev/null +++ b/Test/MultiTestCase/src/Lab.MultiTestCase.UnitTest/UnitTest1.cs @@ -0,0 +1,24 @@ +using System; +using Lab.Domain.Entity; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.MultiTestCase.UnitTest; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void AddRanges() + { + var source = new Employee() + { + Id = Guid.NewGuid(), + Age = 18, + Name = "yao" + }; + + source.Age = 20; + Assert.AreEqual(18,source.Age); + } + +} \ No newline at end of file diff --git a/Test/MultiTestCase/src/Lab.MultiTestCase/Lab.MultiTestCase.csproj b/Test/MultiTestCase/src/Lab.MultiTestCase/Lab.MultiTestCase.csproj new file mode 100644 index 00000000..4ed8d60e --- /dev/null +++ b/Test/MultiTestCase/src/Lab.MultiTestCase/Lab.MultiTestCase.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + + + + + + + + From 0b0745fc8789f6a426749367312f339376d16490 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 13 Feb 2022 20:14:21 +0800 Subject: [PATCH 153/424] feat: add project --- .../ChangeTracking/ChangeTracking.sln | 41 ++++++++++ .../ChangeTracking/Makefile | 4 + .../ChangeTracking/docker-compose.yml | 10 +++ .../Lab.ChangeTracking.Domain.UnitTest.csproj | 27 +++++++ .../MsTestHook.cs | 31 ++++++++ .../TestInstanceManager.cs | 34 +++++++++ .../UnitTest1.cs | 24 ++++++ .../EmployeeAggregate/Entity/Employee.cs | 29 +++++++ .../EmployeeAggregate/IEmployeeAggregate.cs | 8 ++ .../Repository/IEmployeeRepository.cs | 16 ++++ .../Lab.ChangeTracking.Domain.csproj | 9 +++ .../AppDependencyInjectionExtensions.cs | 52 +++++++++++++ .../AppEnvironmentOption.cs | 31 ++++++++ .../EntityModel/Employee.cs | 30 ++++++++ .../EntityModel/EmployeeDbContext.cs | 75 +++++++++++++++++++ .../EntityModel/Identity.cs | 33 ++++++++ .../EntityModel/OrderHistory.cs | 30 ++++++++ .../EnvironmentAssistant.cs | 15 ++++ ...ab.ChangeTracking.Infrastructure.DB.csproj | 15 ++++ 19 files changed, 514 insertions(+) create mode 100644 Property Change Tracking/ChangeTracking/ChangeTracking.sln create mode 100644 Property Change Tracking/ChangeTracking/Makefile create mode 100644 Property Change Tracking/ChangeTracking/docker-compose.yml create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/TestInstanceManager.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/UnitTest1.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/Employee.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/OrderHistory.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj diff --git a/Property Change Tracking/ChangeTracking/ChangeTracking.sln b/Property Change Tracking/ChangeTracking/ChangeTracking.sln new file mode 100644 index 00000000..1c868fe3 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/ChangeTracking.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{1F776B7D-C182-4DC3-BA57-844657BD9AC0}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + Makefile = Makefile + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{483A9EB7-C4AF-4D68-80ED-49277985C760}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Infrastructure.DB", "src\Lab.ChangeTracking.Infrastructure.DB\Lab.ChangeTracking.Infrastructure.DB.csproj", "{A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Domain", "src\Lab.ChangeTracking.Domain\Lab.ChangeTracking.Domain.csproj", "{A1C827F2-683E-470C-A3DE-BD6DD2BE5198}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Domain.UnitTest", "src\Lab.ChangeTracking.Domain.UnitTest\Lab.ChangeTracking.Domain.UnitTest.csproj", "{7066EE2C-28A8-4408-B212-E582608AB967}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Release|Any CPU.Build.0 = Release|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Release|Any CPU.Build.0 = Release|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {7066EE2C-28A8-4408-B212-E582608AB967} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + EndGlobalSection +EndGlobal diff --git a/Property Change Tracking/ChangeTracking/Makefile b/Property Change Tracking/ChangeTracking/Makefile new file mode 100644 index 00000000..cf17f07f --- /dev/null +++ b/Property Change Tracking/ChangeTracking/Makefile @@ -0,0 +1,4 @@ +G1: + echo 'Hello World' +G2: + cmd \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/docker-compose.yml b/Property Change Tracking/ChangeTracking/docker-compose.yml new file mode 100644 index 00000000..8ecb1567 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.8" + +services: + db-sql: + image: mcr.microsoft.com/mssql/server:2019-latest + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=pass@w0rd1~ + ports: + - 1433:1433 \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj new file mode 100644 index 00000000..1fb69aa7 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj @@ -0,0 +1,27 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + + + + + + + diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs new file mode 100644 index 00000000..c48c99fe --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs @@ -0,0 +1,31 @@ +// using Microsoft.VisualStudio.TestTools.UnitTesting; +// +// namespace Lab.MultiTestCase.UnitTest; +// +// [TestClass] +// public class MsTestHook +// { +// [AssemblyCleanup] +// public static void Cleanup() +// { +// TestInstanceManager.SetTestEnvironmentVariable(); +// var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); +// if (db.Database.CanConnect()) +// { +// db.Database.EnsureDeleted(); +// } +// } +// +// [AssemblyInitialize] +// public static void Setup(TestContext context) +// { +// TestInstanceManager.SetTestEnvironmentVariable(); +// var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); +// if (db.Database.CanConnect()) +// { +// db.Database.EnsureDeleted(); +// } +// +// db.Database.EnsureCreated(); +// } +// } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/TestInstanceManager.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/TestInstanceManager.cs new file mode 100644 index 00000000..16ca6112 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/TestInstanceManager.cs @@ -0,0 +1,34 @@ +// using System; +// using Lab.MultiTestCase.EntityModel; +// using Microsoft.EntityFrameworkCore; +// using Microsoft.Extensions.DependencyInjection; +// +// namespace Lab.MultiTestCase.UnitTest; +// +// internal class TestInstanceManager +// { +// private static IServiceProvider _serviceProvider; +// +// public static IDbContextFactory EmployeeDbContextFactory => +// _serviceProvider.GetService>(); +// +// static TestInstanceManager() +// { +// var services = new ServiceCollection(); +// ConfigureTestServices(services); +// } +// +// public static void ConfigureTestServices(IServiceCollection services) +// { +// services.AddAppEnvironment(); +// services.AddEntityFramework(); +// _serviceProvider = services.BuildServiceProvider(); +// } +// +// public static void SetTestEnvironmentVariable() +// { +// var option = _serviceProvider.GetService(); +// option.EmployeeDbConnectionString = +// "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True"; +// } +// } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/UnitTest1.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/UnitTest1.cs new file mode 100644 index 00000000..2f0fe014 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/UnitTest1.cs @@ -0,0 +1,24 @@ +using System; +using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.ChangeTracking.Domain.UnitTest; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void AddRanges() + { + var source = new Employee() + { + Id = Guid.NewGuid(), + Age = 18, + Name = "yao" + }; + + source.Age = 20; + Assert.AreEqual(18,source.Age); + } + +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/Employee.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/Employee.cs new file mode 100644 index 00000000..b1271581 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/Employee.cs @@ -0,0 +1,29 @@ +namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; + +public record Employee +{ + public Guid Id { get; set; } + + public string Name { get; set; } + + public int? Age { get; set; } + + public string Remark { get; set; } + + public DateTimeOffset CreateAt { get; set; } + + public string CreateBy { get; set; } + + public Employee SetName(string name) + { + this.Name = name; + return this; + } + + public Employee SetAge(int age) + { + this.Age = age; + return this; + } + +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs new file mode 100644 index 00000000..04ae8d69 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs @@ -0,0 +1,8 @@ +using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; + +namespace Lab.ChangeTracking.Domain.EmployeeAggregate; + +public interface IEmployeeAggregate +{ + Employee InsertAsync(Employee employee, CancellationToken cancel = default); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs new file mode 100644 index 00000000..7688ac6c --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs @@ -0,0 +1,16 @@ +using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; + +namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Repository; + +public interface IEmployeeRepository +{ + Task InsertAsync(Employee employee, CancellationToken cancel = default); +} + +class EmployeeRepository : IEmployeeRepository +{ + public Task InsertAsync(Employee employee, CancellationToken cancel = default) + { + throw new NotImplementedException(); + } +} diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj new file mode 100644 index 00000000..eb2460e9 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs new file mode 100644 index 00000000..b5cdcb8b --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs @@ -0,0 +1,52 @@ +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.ChangeTracking.Infrastructure.DB; + +public static class AppDependencyInjectionExtensions +{ + public static void AddAppEnvironment(this IServiceCollection services) + { + services.AddLogging(builder => + { + builder.AddConsole(); + }); + services.AddSingleton(); + } + + public static void AddEntityFramework(this IServiceCollection services) + { + services.AddPooledDbContextFactory((provider, optionsBuilder) => + { + var option = provider.GetService(); + var connectionString = option.EmployeeDbConnectionString; + var loggerFactory = provider.GetService(); + optionsBuilder.UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + ; + }); + + ; + + // services.AddPooledDbContextFactory((provider, options) => + // { + // var option = provider.GetService(); + // var loggerFactory = provider.GetService(); + // options.UseNpgsql( + // option.MemberDbConnectionString, //只會呼叫一次 + // builder => + // builder.EnableRetryOnFailure( + // 10, + // TimeSpan.FromSeconds(30), + // new List { "57P01" })) + // + // // .UseLazyLoadingProxies() + // .UseSnakeCaseNamingConvention() + // .UseLoggerFactory(loggerFactory) + // ; + // }); + } + +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs new file mode 100644 index 00000000..e29f53f3 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs @@ -0,0 +1,31 @@ +namespace Lab.ChangeTracking.Infrastructure.DB; + +public class AppEnvironmentOption +{ + public string EmployeeDbConnectionString + { + get + { + if (string.IsNullOrWhiteSpace(this._employeeDbConnectionString)) + { + this._employeeDbConnectionString = + EnvironmentAssistant.GetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR); + } + + return this._employeeDbConnectionString; + } + set + { + this._employeeDbConnectionString = value; + Environment.SetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR, value); + } + } + + private string _employeeDbConnectionString; + private readonly string EMPLOYEE_DB_CONN_STR = "EMPLOYEE_DB_CONNECTION_STR"; + + public void Initial() + { + var memberDbConnectionString = this.EmployeeDbConnectionString; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs new file mode 100644 index 00000000..6bc11f3c --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel +{ + [Table("Employee")] + public class Employee + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Id { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + public string Remark { get; set; } + + [Required] + public DateTimeOffset CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + public virtual Identity Identity { get; set; } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs new file mode 100644 index 00000000..1f651e6d --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs @@ -0,0 +1,75 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel +{ + public class EmployeeDbContext : DbContext + { + private static readonly bool[] s_migrated = { false }; + + public virtual DbSet Employees { get; set; } + + public virtual DbSet Identities { get; set; } + + public virtual DbSet OrderHistories { get; set; } + + public EmployeeDbContext(DbContextOptions options) + : base(options) + { + if (s_migrated[0]) + { + return; + } + + lock (s_migrated) + { + if (s_migrated[0] == false) + { + var memoryOptions = options.FindExtension(); + + if (memoryOptions == null) + { + var sqlOptions = options.FindExtension(); + if (sqlOptions != null) + { + Console.WriteLine( + $"EmployeeDbContext of connection string be '{sqlOptions.ConnectionString}'"); + this.Database.Migrate(); + } + } + + s_migrated[0] = true; + } + } + } + + //管理索引 + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(p => + { + p.HasKey(e => e.Id) + .IsClustered(false); + + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + + p.Property(p => p.Remark) + .IsRequired(false) + ; + }); + + modelBuilder.Entity(p => + { + p.HasKey(e => e.Employee_Id) + .IsClustered(false); + + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + }); + } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs new file mode 100644 index 00000000..fc557c2b --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel +{ + [Table("Identity")] + public class Identity + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Employee_Id { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + [Required] + public DateTimeOffset CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + [ForeignKey("Employee_Id")] + public virtual Employee Employee { get; set; } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/OrderHistory.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/OrderHistory.cs new file mode 100644 index 00000000..914d8af6 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/OrderHistory.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel +{ + [Table("OrderHistory")] + public class OrderHistory + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + public Guid? Employee_Id { get; set; } + + public string Remark { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Product_Id { get; set; } + + public string Product_Name { get; set; } + + [Required] + public DateTime CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs new file mode 100644 index 00000000..b115d8a2 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs @@ -0,0 +1,15 @@ +namespace Lab.ChangeTracking.Infrastructure.DB; + +public class EnvironmentAssistant +{ + public static string GetEnvironmentVariable(string key) + { + var result = Environment.GetEnvironmentVariable(key); + if (string.IsNullOrWhiteSpace(result)) + { + throw new Exception($"the key '{key}' not exist in environment variable"); + } + + return result; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj new file mode 100644 index 00000000..9fcdb0d8 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + + + + + + + + + From 78fbb9e4c9bb4a3dbb9d719eb462b41c0edd5640 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 16 Feb 2022 23:33:47 +0800 Subject: [PATCH 154/424] =?UTF-8?q?Repo=20=E5=AF=A6=E4=BD=9C=20track=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChangeTrackingUnitTest.cs | 65 + .../Lab.ChangeTracking.Domain.UnitTest.csproj | 1 + .../TestAssistants.cs | 48 + .../TestInstanceManager.cs | 34 - .../UnitTest1.cs | 24 - .../Lab.ChangeTracking.Domain/Annotations.cs | 1603 +++++++++++++++++ .../Lab.ChangeTracking.Domain/BaseEntity.cs | 30 + .../EmployeeAggregate/EmployeeAggregate.cs | 24 + .../EmployeeAggregate/Entity/Employee.cs | 29 - .../Entity/EmployeeEntity.cs | 21 + .../Entity/EmployeeEntity2.cs | 49 + .../EmployeeAggregate/IEmployeeAggregate.cs | 2 +- .../Repository/EmployeeRepository.cs | 30 + .../Repository/IEmployeeRepository.cs | 12 +- .../Lab.ChangeTracking.Domain/IChangeable.cs | 14 + .../Lab.ChangeTracking.Domain.csproj | 9 + .../PropertyChangeTracker.cs | 41 + .../src/Lab.ChangeTracking.Domain/Survey.cs | 39 + 18 files changed, 1977 insertions(+), 98 deletions(-) create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs delete mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/TestInstanceManager.cs delete mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/UnitTest1.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Annotations.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/BaseEntity.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs delete mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/Employee.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity2.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/IChangeable.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/PropertyChangeTracker.cs create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Survey.cs diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs new file mode 100644 index 00000000..788d76a2 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -0,0 +1,65 @@ +using System; +using System.ComponentModel; +using ChangeTracking; +using Lab.ChangeTracking.Domain.EmployeeAggregate; +using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; +using Lab.ChangeTracking.Domain.EmployeeAggregate.Repository; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Lab.MultiTestCase.UnitTest; +using Microsoft.EntityFrameworkCore; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.ChangeTracking.Domain.UnitTest; + +[TestClass] +public class ChangeTrackingUnitTest +{ + private IEmployeeAggregate _employeeAggregate = TestAssistants.EmployeeAggregate; + + public ChangeTrackingUnitTest() + { + } + + [TestMethod] + public void 原本用法() + { + var source = new EmployeeEntity() + { + Id = Guid.NewGuid(), + Name = "yao", + Age = 12, + }; + source.Age = 18; + Assert.AreEqual(18, source.Age); + } + + [TestMethod] + public void 追蹤() + { + var source = new EmployeeEntity() + { + Id = Guid.NewGuid(), + Name = "yao", + Age = 12, + }; + var tracked = source.AsTrackable(); + tracked.Age = 18; + + // source.Age = 18; + var trackable = tracked.CastToIChangeTrackable(); + + Assert.AreEqual(18, source.Age); + } + + [TestMethod] + public void Repository_追蹤() + { + var source = new EmployeeEntity() + { + Id = Guid.NewGuid(), + Name = "yao", + Age = 12, + }; + var employeeEntity = this._employeeAggregate.ModifyAsync(source).Result; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj index 1fb69aa7..72a505c5 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj @@ -8,6 +8,7 @@ + diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs new file mode 100644 index 00000000..68fe6717 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs @@ -0,0 +1,48 @@ +using System; +using Lab.ChangeTracking.Domain.EmployeeAggregate; +using Lab.ChangeTracking.Domain.EmployeeAggregate.Repository; +using Lab.ChangeTracking.Infrastructure.DB; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Lab.MultiTestCase.UnitTest; + +// assistant +internal class TestAssistants +{ + private static IServiceProvider _serviceProvider; + + public static IDbContextFactory EmployeeDbContextFactory => + _serviceProvider.GetService>(); + + public static IEmployeeRepository EmployeeRepository => + _serviceProvider.GetService(); + + public static IEmployeeAggregate EmployeeAggregate => + _serviceProvider.GetService(); + + static TestAssistants() + { + var services = new ServiceCollection(); + ConfigureTestServices(services); + SetTestEnvironmentVariable(); + } + + public static void ConfigureTestServices(IServiceCollection services) + { + services.AddAppEnvironment(); + services.AddEntityFramework(); + + services.AddSingleton(); + services.AddSingleton(); + _serviceProvider = services.BuildServiceProvider(); + } + + public static void SetTestEnvironmentVariable() + { + var option = _serviceProvider.GetService(); + option.EmployeeDbConnectionString = + "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True"; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/TestInstanceManager.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/TestInstanceManager.cs deleted file mode 100644 index 16ca6112..00000000 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/TestInstanceManager.cs +++ /dev/null @@ -1,34 +0,0 @@ -// using System; -// using Lab.MultiTestCase.EntityModel; -// using Microsoft.EntityFrameworkCore; -// using Microsoft.Extensions.DependencyInjection; -// -// namespace Lab.MultiTestCase.UnitTest; -// -// internal class TestInstanceManager -// { -// private static IServiceProvider _serviceProvider; -// -// public static IDbContextFactory EmployeeDbContextFactory => -// _serviceProvider.GetService>(); -// -// static TestInstanceManager() -// { -// var services = new ServiceCollection(); -// ConfigureTestServices(services); -// } -// -// public static void ConfigureTestServices(IServiceCollection services) -// { -// services.AddAppEnvironment(); -// services.AddEntityFramework(); -// _serviceProvider = services.BuildServiceProvider(); -// } -// -// public static void SetTestEnvironmentVariable() -// { -// var option = _serviceProvider.GetService(); -// option.EmployeeDbConnectionString = -// "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True"; -// } -// } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/UnitTest1.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/UnitTest1.cs deleted file mode 100644 index 2f0fe014..00000000 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/UnitTest1.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Lab.ChangeTracking.Domain.UnitTest; - -[TestClass] -public class UnitTest1 -{ - [TestMethod] - public void AddRanges() - { - var source = new Employee() - { - Id = Guid.NewGuid(), - Age = 18, - Name = "yao" - }; - - source.Age = 20; - Assert.AreEqual(18,source.Age); - } - -} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Annotations.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Annotations.cs new file mode 100644 index 00000000..c93bbecf --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Annotations.cs @@ -0,0 +1,1603 @@ +/* MIT License + +Copyright (c) 2016 JetBrains http://www.jetbrains.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ + +using System; +// ReSharper disable UnusedType.Global + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming + +namespace Lab.ChangeTracking.Domain.Annotations +{ + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so checking for null is required before its usage. + /// + /// + /// [CanBeNull] object Test() => null; + /// + /// void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] + public sealed class CanBeNullAttribute : Attribute { } + + /// + /// Indicates that the value of the marked element can never be null. + /// + /// + /// [NotNull] object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] + public sealed class NotNullAttribute : Attribute { } + + /// + /// Can be applied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can never be null. + /// + /// + /// public void Foo([ItemNotNull]List<string> books) + /// { + /// foreach (var book in books) { + /// if (book != null) // Warning: Expression is always true + /// Console.WriteLine(book.ToUpper()); + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemNotNullAttribute : Attribute { } + + /// + /// Can be applied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can be null. + /// + /// + /// public void Foo([ItemCanBeNull]List<string> books) + /// { + /// foreach (var book in books) + /// { + /// // Warning: Possible 'System.NullReferenceException' + /// Console.WriteLine(book.ToUpper()); + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + public sealed class ItemCanBeNullAttribute : Attribute { } + + /// + /// Indicates that the marked method builds string by the format pattern and (optional) arguments. + /// The parameter, which contains the format string, should be given in the constructor. The format string + /// should be in -like form. + /// + /// + /// [StringFormatMethod("message")] + /// void ShowError(string message, params object[] args) { /* do something */ } + /// + /// void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Method | + AttributeTargets.Property | AttributeTargets.Delegate)] + public sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as the format string + /// + public StringFormatMethodAttribute([NotNull] string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + [NotNull] public string FormatParameterName { get; } + } + + /// + /// Indicates that the marked parameter is a message template where placeholders are to be replaced by the following arguments + /// in the order in which they appear + /// + /// + /// void LogInfo([StructuredMessageTemplate]string message, params object[] args) { /* do something */ } + /// + /// void Foo() { + /// LogInfo("User created: {username}"); // Warning: Non-existing argument in format string + /// } + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class StructuredMessageTemplateAttribute : Attribute {} + + /// + /// Use this annotation to specify a type that contains static or const fields + /// with values for the annotated property/field/parameter. + /// The specified type will be used to improve completion suggestions. + /// + /// + /// namespace TestNamespace + /// { + /// public class Constants + /// { + /// public static int INT_CONST = 1; + /// public const string STRING_CONST = "1"; + /// } + /// + /// public class Class1 + /// { + /// [ValueProvider("TestNamespace.Constants")] public int myField; + /// public void Foo([ValueProvider("TestNamespace.Constants")] string str) { } + /// + /// public void Test() + /// { + /// Foo(/*try completion here*/);// + /// myField = /*try completion here*/ + /// } + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, + AllowMultiple = true)] + public sealed class ValueProviderAttribute : Attribute + { + public ValueProviderAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; } + } + + /// + /// Indicates that the integral value falls into the specified interval. + /// It's allowed to specify multiple non-intersecting intervals. + /// Values of interval boundaries are inclusive. + /// + /// + /// void Foo([ValueRange(0, 100)] int value) { + /// if (value == -1) { // Warning: Expression is always 'false' + /// ... + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | + AttributeTargets.Method | AttributeTargets.Delegate, + AllowMultiple = true)] + public sealed class ValueRangeAttribute : Attribute + { + public object From { get; } + public object To { get; } + + public ValueRangeAttribute(long from, long to) + { + From = from; + To = to; + } + + public ValueRangeAttribute(ulong from, ulong to) + { + From = from; + To = to; + } + + public ValueRangeAttribute(long value) + { + From = To = value; + } + + public ValueRangeAttribute(ulong value) + { + From = To = value; + } + } + + /// + /// Indicates that the integral value never falls below zero. + /// + /// + /// void Foo([NonNegativeValue] int value) { + /// if (value == -1) { // Warning: Expression is always 'false' + /// ... + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | + AttributeTargets.Method | AttributeTargets.Delegate)] + public sealed class NonNegativeValueAttribute : Attribute { } + + /// + /// Indicates that the function argument should be a string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of . + /// + /// + /// void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InvokerParameterNameAttribute : Attribute { } + + /// + /// Indicates that the method is contained in a type that implements + /// System.ComponentModel.INotifyPropertyChanged interface and this method + /// is used to notify that some property value changed. + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// NotifyChanged(string) + /// NotifyChanged(params string[]) + /// NotifyChanged{T}(Expression{Func{T}}) + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// string _name; + /// + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// NotifyChanged("Property") + /// NotifyChanged(() => Property) + /// NotifyChanged((VM x) => x.Property) + /// SetProperty(ref myField, value, "Property") + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() { } + public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) + { + ParameterName = parameterName; + } + + [CanBeNull] public string ParameterName { get; } + } + + /// + /// Describes dependency between method input and output. + /// + /// + ///

Function Definition Table syntax:

+ /// + /// FDT ::= FDTRow [;FDTRow]* + /// FDTRow ::= Input => Output | Output <= Input + /// Input ::= ParameterName: Value [, Input]* + /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + /// Value ::= true | false | null | notnull | canbenull + /// + /// If the method has a single input parameter, its name could be omitted.
+ /// Using halt (or void/nothing, which is the same) for the method output + /// means that the method doesn't return normally (throws or terminates the process).
+ /// Value canbenull is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, or use single attribute + /// with rows separated by the semicolon. There is no notion of order rows, all rows are checked + /// for applicability and applied per each program state tracked by the analysis engine.
+ ///
+ /// + /// + /// [ContractAnnotation("=> halt")] + /// public void TerminationMethod() + /// + /// + /// [ContractAnnotation("null <= param:null")] // reverse condition syntax + /// public string GetName(string surname) + /// + /// + /// [ContractAnnotation("s:null => true")] + /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + /// + /// + /// // A method that returns null if the parameter is null, + /// // and not null if the parameter is not null + /// [ContractAnnotation("null => null; notnull => notnull")] + /// public object Transform(object data) + /// + /// + /// [ContractAnnotation("=> true, result: notnull; => false, result: null")] + /// public bool TryParse(string s, out Person result) + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class ContractAnnotationAttribute : Attribute + { + public ContractAnnotationAttribute([NotNull] string contract) + : this(contract, false) { } + + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) + { + Contract = contract; + ForceFullStates = forceFullStates; + } + + [NotNull] public string Contract { get; } + + public bool ForceFullStates { get; } + } + + /// + /// Indicates whether the marked element should be localized. + /// + /// + /// [LocalizationRequiredAttribute(true)] + /// class Foo { + /// string str = "my string"; // Warning: Localizable string + /// } + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class LocalizationRequiredAttribute : Attribute + { + public LocalizationRequiredAttribute() : this(true) { } + + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + public bool Required { get; } + } + + /// + /// Indicates that the value of the marked type (or its derivatives) + /// cannot be compared using '==' or '!=' operators and Equals() + /// should be used instead. However, using '==' or '!=' for comparison + /// with null is always permitted. + /// + /// + /// [CannotApplyEqualityOperator] + /// class NoEquality { } + /// + /// class UsesNoEquality { + /// void Test() { + /// var ca1 = new NoEquality(); + /// var ca2 = new NoEquality(); + /// if (ca1 != null) { // OK + /// bool condition = ca1 == ca2; // Warning + /// } + /// } + /// } + /// + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] + public sealed class CannotApplyEqualityOperatorAttribute : Attribute { } + + /// + /// When applied to a target attribute, specifies a requirement for any type marked + /// with the target attribute to implement or inherit specific type or types. + /// + /// + /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement + /// class ComponentAttribute : Attribute { } + /// + /// [Component] // ComponentAttribute requires implementing IComponent interface + /// class MyComponent : IComponent { } + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + [BaseTypeRequired(typeof(Attribute))] + public sealed class BaseTypeRequiredAttribute : Attribute + { + public BaseTypeRequiredAttribute([NotNull] Type baseType) + { + BaseType = baseType; + } + + [NotNull] public Type BaseType { get; } + } + + /// + /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), + /// so this symbol will be ignored by usage-checking inspections.
+ /// You can use and + /// to configure how this attribute is applied. + ///
+ /// + /// [UsedImplicitly] + /// public class TypeConverter {} + /// + /// public class SummaryData + /// { + /// [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] + /// public SummaryData() {} + /// } + /// + /// [UsedImplicitly(ImplicitUseTargetFlags.WithInheritors | ImplicitUseTargetFlags.Default)] + /// public interface IService {} + /// + [AttributeUsage(AttributeTargets.All)] + public sealed class UsedImplicitlyAttribute : Attribute + { + public UsedImplicitlyAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseKindFlags UseKindFlags { get; } + + public ImplicitUseTargetFlags TargetFlags { get; } + } + + /// + /// Can be applied to attributes, type parameters, and parameters of a type assignable from . + /// When applied to an attribute, the decorated attribute behaves the same as . + /// When applied to a type parameter or to a parameter of type , + /// indicates that the corresponding type is used implicitly. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter | AttributeTargets.Parameter)] + public sealed class MeansImplicitUseAttribute : Attribute + { + public MeansImplicitUseAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; } + + [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; } + } + + /// + /// Specifies the details of implicitly used symbol when it is marked + /// with or . + /// + [Flags] + public enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + /// Only entity marked with attribute considered used. + Access = 1, + /// Indicates implicit assignment to a member. + Assign = 2, + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + /// Indicates implicit instantiation of a type. + InstantiatedNoFixedConstructorSignature = 8, + } + + /// + /// Specifies what is considered to be used implicitly when marked + /// with or . + /// + [Flags] + public enum ImplicitUseTargetFlags + { + Default = Itself, + Itself = 1, + /// Members of the type marked with the attribute are considered used. + Members = 2, + /// Inherited entities are considered used. + WithInheritors = 4, + /// Entity marked with the attribute and all its members considered used. + WithMembers = Itself | Members + } + + /// + /// This attribute is intended to mark publicly available API, + /// which should not be removed and so is treated as used. + /// + [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] + [AttributeUsage(AttributeTargets.All, Inherited = false)] + public sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() { } + + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + [CanBeNull] public string Comment { get; } + } + + /// + /// Tells the code analysis engine if the parameter is completely handled when the invoked method is on stack. + /// If the parameter is a delegate, indicates that delegate can only be invoked during method execution + /// (the delegate can be invoked zero or multiple times, but not stored to some field and invoked later, + /// when the containing method is no longer on the execution stack). + /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. + /// If is true, the attribute will only takes effect if the method invocation is located under the 'await' expression. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class InstantHandleAttribute : Attribute + { + /// + /// Require the method invocation to be used under the 'await' expression for this attribute to take effect on code analysis engine. + /// Can be used for delegate/enumerable parameters of 'async' methods. + /// + public bool RequireAwait { get; set; } + } + + /// + /// Indicates that a method does not make any observable state changes. + /// The same as System.Diagnostics.Contracts.PureAttribute. + /// + /// + /// [Pure] int Multiply(int x, int y) => x * y; + /// + /// void M() { + /// Multiply(123, 42); // Warning: Return value of pure method is not used + /// } + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class PureAttribute : Attribute { } + + /// + /// Indicates that the return value of the method invocation must be used. + /// + /// + /// Methods decorated with this attribute (in contrast to pure methods) might change state, + /// but make no sense without using their return value.
+ /// Similarly to , this attribute + /// will help to detect usages of the method when the return value is not used. + /// Optionally, you can specify a message to use when showing warnings, e.g. + /// [MustUseReturnValue("Use the return value to...")]. + ///
+ [AttributeUsage(AttributeTargets.Method)] + public sealed class MustUseReturnValueAttribute : Attribute + { + public MustUseReturnValueAttribute() { } + + public MustUseReturnValueAttribute([NotNull] string justification) + { + Justification = justification; + } + + [CanBeNull] public string Justification { get; } + } + + /// + /// This annotation allows to enforce allocation-less usage patterns of delegates for performance-critical APIs. + /// When this annotation is applied to the parameter of delegate type, IDE checks the input argument of this parameter: + /// * When lambda expression or anonymous method is passed as an argument, IDE verifies that the passed closure + /// has no captures of the containing local variables and the compiler is able to cache the delegate instance + /// to avoid heap allocations. Otherwise the warning is produced. + /// * IDE warns when method name or local function name is passed as an argument as this always results + /// in heap allocation of the delegate instance. + /// + /// + /// In C# 9.0 code IDE would also suggest to annotate the anonymous function with 'static' modifier + /// to make use of the similar analysis provided by the language/compiler. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public class RequireStaticDelegateAttribute : Attribute + { + public bool IsError { get; set; } + } + + /// + /// Indicates the type member or parameter of some type, that should be used instead of all other ways + /// to get the value of that type. This annotation is useful when you have some "context" value evaluated + /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. + /// + /// + /// class Foo { + /// [ProvidesContext] IBarService _barService = ...; + /// + /// void ProcessNode(INode node) { + /// DoSomething(node, node.GetGlobalServices().Bar); + /// // ^ Warning: use value of '_barService' field + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)] + public sealed class ProvidesContextAttribute : Attribute { } + + /// + /// Indicates that a parameter is a path to a file or a folder within a web project. + /// Path can be relative or absolute, starting from web root (~). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class PathReferenceAttribute : Attribute + { + public PathReferenceAttribute() { } + + public PathReferenceAttribute([NotNull, PathReference] string basePath) + { + BasePath = basePath; + } + + [CanBeNull] public string BasePath { get; } + } + + /// + /// An extension method marked with this attribute is processed by code completion + /// as a 'Source Template'. When the extension method is completed over some expression, its source code + /// is automatically expanded like a template at call site. + /// + /// + /// Template method body can contain valid source code and/or special comments starting with '$'. + /// Text inside these comments is added as source code when the template is applied. Template parameters + /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. + /// Use the attribute to specify macros for parameters. + /// + /// + /// In this example, the 'forEach' method is a source template available over all values + /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: + /// + /// [SourceTemplate] + /// public static void forEach<T>(this IEnumerable<T> xs) { + /// foreach (var x in xs) { + /// //$ $END$ + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class SourceTemplateAttribute : Attribute { } + + /// + /// Allows specifying a macro for a parameter of a source template. + /// + /// + /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression + /// is defined in the property. When applied on a method, the target + /// template parameter is defined in the property. To apply the macro silently + /// for the parameter, set the property value = -1. + /// + /// + /// Applying the attribute on a source template method: + /// + /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] + /// public static void forEach<T>(this IEnumerable<T> collection) { + /// foreach (var item in collection) { + /// //$ $END$ + /// } + /// } + /// + /// Applying the attribute on a template method parameter: + /// + /// [SourceTemplate] + /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { + /// /*$ var $x$Id = "$newguid$" + x.ToString(); + /// x.DoSomething($x$Id); */ + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] + public sealed class MacroAttribute : Attribute + { + /// + /// Allows specifying a macro that will be executed for a source template + /// parameter when the template is expanded. + /// + [CanBeNull] public string Expression { get; set; } + + /// + /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. + /// + /// + /// If the target parameter is used several times in the template, only one occurrence becomes editable; + /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, + /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. + /// + public int Editable { get; set; } + + /// + /// Identifies the target parameter of a source template if the + /// is applied on a template method. + /// + [CanBeNull] public string Target { get; set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute + { + public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute + { + public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute + { + public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcMasterLocationFormatAttribute : Attribute + { + public AspMvcMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute + { + public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + public sealed class AspMvcViewLocationFormatAttribute : Attribute + { + public AspMvcViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC action. If applied to a method, the MVC action name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcActionAttribute : Attribute + { + public AspMvcActionAttribute() { } + + public AspMvcActionAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] public string AnonymousProperty { get; } + } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC area. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcAreaAttribute : Attribute + { + public AspMvcAreaAttribute() { } + + public AspMvcAreaAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] public string AnonymousProperty { get; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is + /// an MVC controller. If applied to a method, the MVC controller name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcControllerAttribute : Attribute + { + public AspMvcControllerAttribute() { } + + public AspMvcControllerAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] public string AnonymousProperty { get; } + } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC Master. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcMasterAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC model type. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object). + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcModelTypeAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC + /// partial view. If applied to a method, the MVC partial view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcPartialViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class AspMvcSuppressViewErrorAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcDisplayTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC editor template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcEditorTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC template. + /// Use this attribute for custom wrappers similar to + /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(Object). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component name. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcViewComponentAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component view. If applied to a method, the MVC view component view name is default. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class AspMvcViewComponentViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. When applied to a parameter of an attribute, + /// indicates that this parameter is an MVC action name. + /// + /// + /// [ActionName("Foo")] + /// public ActionResult Login(string returnUrl) { + /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK + /// return RedirectToAction("Bar"); // Error: Cannot resolve action + /// } + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] + public sealed class AspMvcActionSelectorAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] + public sealed class HtmlElementAttributesAttribute : Attribute + { + public HtmlElementAttributesAttribute() { } + + public HtmlElementAttributesAttribute([NotNull] string name) + { + Name = name; + } + + [CanBeNull] public string Name { get; } + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class HtmlAttributeValueAttribute : Attribute + { + public HtmlAttributeValueAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; } + } + + /// + /// Razor attribute. Indicates that the marked parameter or method is a Razor section. + /// Use this attribute for custom wrappers similar to + /// System.Web.WebPages.WebPageBase.RenderSection(String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class RazorSectionAttribute : Attribute { } + + /// + /// Indicates how method, constructor invocation, or property access + /// over collection type affects the contents of the collection. + /// When applied to a return value of a method indicates if the returned collection + /// is created exclusively for the caller (CollectionAccessType.UpdatedContent) or + /// can be read/updated from outside (CollectionAccessType.Read | CollectionAccessType.UpdatedContent) + /// Use to specify the access type. + /// + /// + /// Using this attribute only makes sense if all collection methods are marked with this attribute. + /// + /// + /// public class MyStringCollection : List<string> + /// { + /// [CollectionAccess(CollectionAccessType.Read)] + /// public string GetFirstString() + /// { + /// return this.ElementAt(0); + /// } + /// } + /// class Test + /// { + /// public void Foo() + /// { + /// // Warning: Contents of the collection is never updated + /// var col = new MyStringCollection(); + /// string x = col.GetFirstString(); + /// } + /// } + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property | AttributeTargets.ReturnValue)] + public sealed class CollectionAccessAttribute : Attribute + { + public CollectionAccessAttribute(CollectionAccessType collectionAccessType) + { + CollectionAccessType = collectionAccessType; + } + + public CollectionAccessType CollectionAccessType { get; } + } + + /// + /// Provides a value for the to define + /// how the collection method invocation affects the contents of the collection. + /// + [Flags] + public enum CollectionAccessType + { + /// Method does not use or modify content of the collection. + None = 0, + /// Method only reads content of the collection but does not modify it. + Read = 1, + /// Method can change content of the collection but does not add new elements. + ModifyExistingContent = 2, + /// Method can add new elements to the collection. + UpdatedContent = ModifyExistingContent | 4 + } + + /// + /// Indicates that the marked method is assertion method, i.e. it halts the control flow if + /// one of the conditions is satisfied. To set the condition, mark one of the parameters with + /// attribute. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class AssertionMethodAttribute : Attribute { } + + /// + /// Indicates the condition parameter of the assertion method. The method itself should be + /// marked by attribute. The mandatory argument of + /// the attribute is the assertion type. + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AssertionConditionAttribute : Attribute + { + public AssertionConditionAttribute(AssertionConditionType conditionType) + { + ConditionType = conditionType; + } + + public AssertionConditionType ConditionType { get; } + } + + /// + /// Specifies assertion type. If the assertion method argument satisfies the condition, + /// then the execution continues. Otherwise, execution is assumed to be halted. + /// + public enum AssertionConditionType + { + /// Marked parameter should be evaluated to true. + IS_TRUE = 0, + /// Marked parameter should be evaluated to false. + IS_FALSE = 1, + /// Marked parameter should be evaluated to null value. + IS_NULL = 2, + /// Marked parameter should be evaluated to not null value. + IS_NOT_NULL = 3, + } + + /// + /// Indicates that the marked method unconditionally terminates control flow execution. + /// For example, it could unconditionally throw exception. + /// + [Obsolete("Use [ContractAnnotation('=> halt')] instead")] + [AttributeUsage(AttributeTargets.Method)] + public sealed class TerminatesProgramAttribute : Attribute { } + + /// + /// Indicates that the method is a pure LINQ method, with postponed enumeration (like Enumerable.Select, + /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters + /// of delegate type by analyzing LINQ method chains. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class LinqTunnelAttribute : Attribute { } + + /// + /// Indicates that IEnumerable passed as a parameter is not enumerated. + /// Use this annotation to suppress the 'Possible multiple enumeration of IEnumerable' inspection. + /// + /// + /// static void ThrowIfNull<T>([NoEnumeration] T v, string n) where T : class + /// { + /// // custom check for null but no enumeration + /// } + /// + /// void Foo(IEnumerable<string> values) + /// { + /// ThrowIfNull(values, nameof(values)); + /// var x = values.ToList(); // No warnings about multiple enumeration + /// } + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class NoEnumerationAttribute : Attribute { } + + /// + /// Indicates that the marked parameter, field, or property is a regular expression pattern. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class RegexPatternAttribute : Attribute { } + + /// + /// Language of injected code fragment inside marked by string literal. + /// + public enum InjectedLanguage + { + CSS, + HTML, + JAVASCRIPT, + JSON, + XML + } + + /// + /// Indicates that the marked parameter, field, or property is accepting a string literal + /// containing code fragment in a language specified by the . + /// + /// + /// void Foo([LanguageInjection(InjectedLanguage.CSS, Prefix = "body{", Suffix = "}")] string cssProps) + /// { + /// // cssProps should only contains a list of CSS properties + /// } + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class LanguageInjectionAttribute : Attribute + { + public LanguageInjectionAttribute(InjectedLanguage injectedLanguage) + { + InjectedLanguage = injectedLanguage; + } + + /// Specify a language of injected code fragment. + public InjectedLanguage InjectedLanguage { get; } + /// Specify a string that "precedes" injected string literal. + [CanBeNull] public string Prefix { get; set; } + /// Specify a string that "follows" injected string literal. + [CanBeNull] public string Suffix { get; set; } + } + + /// + /// Prevents the Member Reordering feature from tossing members of the marked class. + /// + /// + /// The attribute must be mentioned in your member reordering patterns. + /// + [AttributeUsage( + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] + public sealed class NoReorderAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the type that has ItemsSource property and should be treated + /// as ItemsControl-derived type, to enable inner items DataContext type resolve. + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class XamlItemsControlAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the property of some BindingBase-derived type, that + /// is used to bind some item of ItemsControl-derived type. This annotation will + /// enable the DataContext type resolve for XAML bindings for such properties. + /// + /// + /// Property should have the tree ancestor of the ItemsControl type or + /// marked with the attribute. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class XamlItemBindingOfItemsControlAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the property of some Style-derived type, that + /// is used to style items of ItemsControl-derived type. This annotation will + /// enable the DataContext type resolve for XAML bindings for such properties. + /// + /// + /// Property should have the tree ancestor of the ItemsControl type or + /// marked with the attribute. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class XamlItemStyleOfItemsControlAttribute : Attribute { } + + /// + /// XAML attribute. Indicates that DependencyProperty has OneWay binding mode by default. + /// + /// + /// This attribute must be applied to DependencyProperty's CLR accessor property if it is present, to DependencyProperty descriptor field otherwise. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class XamlOneWayBindingModeByDefaultAttribute : Attribute { } + + /// + /// XAML attribute. Indicates that DependencyProperty has TwoWay binding mode by default. + /// + /// + /// This attribute must be applied to DependencyProperty's CLR accessor property if it is present, to DependencyProperty descriptor field otherwise. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class XamlTwoWayBindingModeByDefaultAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class AspChildControlTypeAttribute : Attribute + { + public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) + { + TagName = tagName; + ControlType = controlType; + } + + [NotNull] public string TagName { get; } + + [NotNull] public Type ControlType { get; } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class AspDataFieldAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class AspDataFieldsAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class AspMethodPropertyAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class AspRequiredAttributeAttribute : Attribute + { + public AspRequiredAttributeAttribute([NotNull] string attribute) + { + Attribute = attribute; + } + + [NotNull] public string Attribute { get; } + } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class AspTypePropertyAttribute : Attribute + { + public bool CreateConstructorReferences { get; } + + public AspTypePropertyAttribute(bool createConstructorReferences) + { + CreateConstructorReferences = createConstructorReferences; + } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorImportNamespaceAttribute : Attribute + { + public RazorImportNamespaceAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorInjectionAttribute : Attribute + { + public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) + { + Type = type; + FieldName = fieldName; + } + + [NotNull] public string Type { get; } + + [NotNull] public string FieldName { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorDirectiveAttribute : Attribute + { + public RazorDirectiveAttribute([NotNull] string directive) + { + Directive = directive; + } + + [NotNull] public string Directive { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RazorPageBaseTypeAttribute : Attribute + { + public RazorPageBaseTypeAttribute([NotNull] string baseType) + { + BaseType = baseType; + } + public RazorPageBaseTypeAttribute([NotNull] string baseType, string pageName) + { + BaseType = baseType; + PageName = pageName; + } + + [NotNull] public string BaseType { get; } + [CanBeNull] public string PageName { get; } + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorHelperCommonAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class RazorLayoutAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorWriteLiteralMethodAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class RazorWriteMethodAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class RazorWriteMethodParameterAttribute : Attribute { } + + /// + /// Indicates that the marked parameter, field, or property is a route template. + /// + /// + /// This attribute allows IDE to recognize the use of web frameworks' route templates + /// to enable syntax highlighting, code completion, navigation, rename and other features in string literals. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class RouteTemplateAttribute : Attribute { } + + /// + /// Indicates that the marked type is custom route parameter constraint, + /// which is registered in application's Startup with name ConstraintName + /// + /// + /// You can specify ProposedType if target constraint matches only route parameters of specific type, + /// it will allow IDE to create method's parameter from usage in route template + /// with specified type instead of default System.String + /// and check if constraint's proposed type conflicts with matched parameter's type + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class RouteParameterConstraintAttribute : Attribute + { + [NotNull] public string ConstraintName { get; } + [CanBeNull] public Type ProposedType { get; set; } + + public RouteParameterConstraintAttribute([NotNull] string constraintName) + { + ConstraintName = constraintName; + } + } + + /// + /// Indicates that the marked parameter, field, or property is an URI string. + /// + /// + /// This attribute enables code completion, navigation, rename and other features + /// in URI string literals assigned to annotated parameter, field or property. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class UriStringAttribute : Attribute + { + public UriStringAttribute() { } + + public UriStringAttribute(string httpVerb) + { + HttpVerb = httpVerb; + } + + [CanBeNull] public string HttpVerb { get; } + } + + /// + /// + /// Defines the code search template using the Structural Search and Replace syntax. + /// It allows you to find and, if necessary, replace blocks of code that match a specific pattern. + /// Search and replace patterns consist of a textual part and placeholders. + /// Textural part must contain only identifiers allowed in the target language and will be matched exactly (white spaces, tabulation characters, and line breaks are ignored). + /// Placeholders allow matching variable parts of the target code blocks. + /// A placeholder has the following format: $placeholder_name$- where placeholder_name is an arbitrary identifier. + /// + /// + /// Available placeholders: + /// + /// $this$ - expression of containing type + /// $thisType$ - containing type + /// $member$ - current member placeholder + /// $qualifier$ - this placeholder is available in the replace pattern and can be used to insert qualifier expression matched by the $member$ placeholder. + /// (Note that if $qualifier$ placeholder is used, then $member$ placeholder will match only qualified references) + /// $expression$ - expression of any type + /// $identifier$ - identifier placeholder + /// $args$ - any number of arguments + /// $arg$ - single argument + /// $arg1$ ... $arg10$ - single argument + /// $stmts$ - any number of statements + /// $stmt$ - single statement + /// $stmt1$ ... $stmt10$ - single statement + /// $name{Expression, 'Namespace.FooType'}$ - expression with 'Namespace.FooType' type + /// $expression{'Namespace.FooType'}$ - expression with 'Namespace.FooType' type + /// $name{Type, 'Namespace.FooType'}$ - 'Namespace.FooType' type + /// $type{'Namespace.FooType'}$ - 'Namespace.FooType' type + /// $statement{1,2}$ - 1 or 2 statements + /// + /// + /// + /// Note that you can also define your own placeholders of the supported types and specify arguments for each placeholder type. + /// This can be done using the following format: $name{type, arguments}$. Where 'name' - is the name of your placeholder, + /// 'type' - is the type of your placeholder (one of the following: Expression, Type, Identifier, Statement, Argument, Member), + /// 'arguments' - arguments list for your placeholder. Each placeholder type supports it's own arguments, check examples below for mode details. + /// Placeholder type may be omitted and determined from the placeholder name, if name has one of the following prefixes: + /// + /// expr, expression - expression placeholder, e.g. $exprPlaceholder{}$, $expressionFoo{}$ + /// arg, argument - argument placeholder, e.g. $argPlaceholder{}$, $argumentFoo{}$ + /// ident, identifier - identifier placeholder, e.g. $identPlaceholder{}$, $identifierFoo{}$ + /// stmt, statement - statement placeholder, e.g. $stmtPlaceholder{}$, $statementFoo{}$ + /// type - type placeholder, e.g. $typePlaceholder{}$, $typeFoo{}$ + /// member - member placeholder, e.g. $memberPlaceholder{}$, $memberFoo{}$ + /// + /// + /// + /// Expression placeholder arguments: + /// + /// expressionType - string value in single quotes, specifies full type name to match (empty string by default) + /// exactType - boolean value, specifies if expression should have exact type match (false by default) + /// + /// Examples: + /// + /// $myExpr{Expression, 'Namespace.FooType', true}$ - defines expression placeholder, matching expressions of the 'Namespace.FooType' type with exact matching. + /// $myExpr{Expression, 'Namespace.FooType'}$ - defines expression placeholder, matching expressions of the 'Namespace.FooType' type or expressions which can be implicitly converted to 'Namespace.FooType'. + /// $myExpr{Expression}$ - defines expression placeholder, matching expressions of any type. + /// $exprFoo{'Namespace.FooType', true}$ - defines expression placeholder, matching expressions of the 'Namespace.FooType' type with exact matching. + /// + /// + /// + /// Type placeholder arguments: + /// + /// type - string value in single quotes, specifies full type name to match (empty string by default) + /// exactType - boolean value, specifies if expression should have exact type match (false by default) + /// + /// Examples: + /// + /// $myType{Type, 'Namespace.FooType', true}$ - defines type placeholder, matching 'Namespace.FooType' types with exact matching. + /// $myType{Type, 'Namespace.FooType'}$ - defines type placeholder, matching 'Namespace.FooType' types or types, which can be implicitly converted to 'Namespace.FooType'. + /// $myType{Type}$ - defines type placeholder, matching any type. + /// $typeFoo{'Namespace.FooType', true}$ - defines types placeholder, matching 'Namespace.FooType' types with exact matching. + /// + /// + /// + /// Identifier placeholder arguments: + /// + /// nameRegex - string value in single quotes, specifies regex to use for matching (empty string by default) + /// nameRegexCaseSensitive - boolean value, specifies if name regex is case sensitive (true by default) + /// type - string value in single quotes, specifies full type name to match (empty string by default) + /// exactType - boolean value, specifies if expression should have exact type match (false by default) + /// + /// Examples: + /// + /// $myIdentifier{Identifier, 'my.*', false, 'Namespace.FooType', true}$ - defines identifier placeholder, matching identifiers (ignoring case) starting with 'my' prefix with 'Namespace.FooType' type. + /// $myIdentifier{Identifier, 'my.*', true, 'Namespace.FooType', true}$ - defines identifier placeholder, matching identifiers (case sensitively) starting with 'my' prefix with 'Namespace.FooType' type. + /// $identFoo{'my.*'}$ - defines identifier placeholder, matching identifiers (case sensitively) starting with 'my' prefix. + /// + /// + /// + /// Statement placeholder arguments: + /// + /// minimalOccurrences - minimal number of statements to match (-1 by default) + /// maximalOccurrences - maximal number of statements to match (-1 by default) + /// + /// Examples: + /// + /// $myStmt{Statement, 1, 2}$ - defines statement placeholder, matching 1 or 2 statements. + /// $myStmt{Statement}$ - defines statement placeholder, matching any number of statements. + /// $stmtFoo{1, 2}$ - defines statement placeholder, matching 1 or 2 statements. + /// + /// + /// + /// Argument placeholder arguments: + /// + /// minimalOccurrences - minimal number of arguments to match (-1 by default) + /// maximalOccurrences - maximal number of arguments to match (-1 by default) + /// + /// Examples: + /// + /// $myArg{Argument, 1, 2}$ - defines argument placeholder, matching 1 or 2 arguments. + /// $myArg{Argument}$ - defines argument placeholder, matching any number of arguments. + /// $argFoo{1, 2}$ - defines argument placeholder, matching 1 or 2 arguments. + /// + /// + /// + /// Member placeholder arguments: + /// + /// docId - string value in single quotes, specifies XML documentation id of the member to match (empty by default) + /// + /// Examples: + /// + /// $myMember{Member, 'M:System.String.IsNullOrEmpty(System.String)'}$ - defines member placeholder, matching 'IsNullOrEmpty' member of the 'System.String' type. + /// $memberFoo{'M:System.String.IsNullOrEmpty(System.String)'}$ - defines member placeholder, matching 'IsNullOrEmpty' member of the 'System.String' type. + /// + /// + /// + /// For more information please refer to the Structural Search and Replace article. + /// + /// + [AttributeUsage( + AttributeTargets.Method + | AttributeTargets.Constructor + | AttributeTargets.Property + | AttributeTargets.Field + | AttributeTargets.Event + | AttributeTargets.Interface + | AttributeTargets.Class + | AttributeTargets.Struct + | AttributeTargets.Enum, + AllowMultiple = true, + Inherited = false)] + public sealed class CodeTemplateAttribute : Attribute + { + public CodeTemplateAttribute(string searchTemplate) + { + SearchTemplate = searchTemplate; + } + + /// + /// Structural search pattern to use in the code template. + /// Pattern includes textual part, which must contain only identifiers allowed in the target language, + /// and placeholders, which allow matching variable parts of the target code blocks. + /// + public string SearchTemplate { get; } + + /// + /// Message to show when the search pattern was found. + /// You can also prepend the message text with "Error:", "Warning:", "Suggestion:" or "Hint:" prefix to specify the pattern severity. + /// Code patterns with replace template produce suggestions by default. + /// However, if replace template is not provided, then warning severity will be used. + /// + public string Message { get; set; } + + /// + /// Structural search replace pattern to use in code template replacement. + /// + public string ReplaceTemplate { get; set; } + + /// + /// Replace message to show in the light bulb. + /// + public string ReplaceMessage { get; set; } + + /// + /// Apply code formatting after code replacement. + /// + public bool FormatAfterReplace { get; set; } = true; + + /// + /// Whether similar code blocks should be matched. + /// + public bool MatchSimilarConstructs { get; set; } + + /// + /// Automatically insert namespace import directives or remove qualifiers that become redundant after the template is applied. + /// + public bool ShortenReferences { get; set; } + + /// + /// String to use as a suppression key. + /// By default the following suppression key is used 'CodeTemplate_SomeType_SomeMember', + /// where 'SomeType' and 'SomeMember' are names of the associated containing type and member to which this attribute is applied. + /// + public string SuppressionKey { get; set; } + } +} diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/BaseEntity.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/BaseEntity.cs new file mode 100644 index 00000000..91929a85 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/BaseEntity.cs @@ -0,0 +1,30 @@ +using System.Reflection; + +namespace Lab.ChangeTracking.Domain.Annotations; + +public record BaseEntity : IChangeable +{ + private PropertyChangeTracker _tracker = new(); + + public void Initial() + { + this._tracker.Initial(); + } + + public bool HasChanged { get; private set; } + + public Dictionary GetChangedProperties() + { + return this._tracker.GetChangedProperties(); + } + + public Dictionary GetOriginalValues() + { + throw new NotImplementedException(); + } + + public void Track(string propertyName, object value) + { + this._tracker.Track(propertyName, value); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs new file mode 100644 index 00000000..29a86465 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs @@ -0,0 +1,24 @@ +using ChangeTracking; +using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; +using Lab.ChangeTracking.Domain.EmployeeAggregate.Repository; + +namespace Lab.ChangeTracking.Domain.EmployeeAggregate; + +public class EmployeeAggregate : IEmployeeAggregate +{ + private IEmployeeRepository _repository; + + public EmployeeAggregate(IEmployeeRepository repository) + { + this._repository = repository; + } + + public async Task ModifyAsync(EmployeeEntity employee, CancellationToken cancel = default) + { + var trackable = employee.AsTrackable(); + trackable.Age = 20; + trackable.Name = "小章"; + var changeCount = await this._repository.ChangeAsync(trackable, cancel); + return trackable; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/Employee.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/Employee.cs deleted file mode 100644 index b1271581..00000000 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/Employee.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; - -public record Employee -{ - public Guid Id { get; set; } - - public string Name { get; set; } - - public int? Age { get; set; } - - public string Remark { get; set; } - - public DateTimeOffset CreateAt { get; set; } - - public string CreateBy { get; set; } - - public Employee SetName(string name) - { - this.Name = name; - return this; - } - - public Employee SetAge(int age) - { - this.Age = age; - return this; - } - -} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs new file mode 100644 index 00000000..08b63ed2 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs @@ -0,0 +1,21 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using ChangeTracking; +using Lab.ChangeTracking.Domain.Annotations; + +namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; + +public class EmployeeEntity +{ + public virtual Guid Id { get; init; } + + public virtual string Name { get; set; } + + public virtual int? Age { get; set; } + + public virtual string Remark { get; set; } + + public virtual DateTimeOffset CreateAt { get; set; } + + public virtual string CreateBy { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity2.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity2.cs new file mode 100644 index 00000000..7171d53c --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity2.cs @@ -0,0 +1,49 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using ChangeTracking; +using Lab.ChangeTracking.Domain.Annotations; + +namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; + +public record EmployeeEntity2 : BaseEntity +{ + private string _name; + private int? _age; + + public virtual Guid Id { get; init; } + + public virtual string Name + { + get => this._name; + init => this._name = value; + } + + public virtual int? Age + { + get => this._age; + init => this._age = value; + } + + public virtual string Remark { get; set; } + + public virtual DateTimeOffset CreateAt { get; set; } + + public virtual string CreateBy { get; set; } + + public EmployeeEntity2 SetName(string name) + { + this._name = name; + return this; + } + + public EmployeeEntity2 SetAge(int age) + { + if (this._age != age) + { + this._age = age; + this.Track(nameof(this.Age), age); + } + + return this; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs index 04ae8d69..9ec26110 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs @@ -4,5 +4,5 @@ namespace Lab.ChangeTracking.Domain.EmployeeAggregate; public interface IEmployeeAggregate { - Employee InsertAsync(Employee employee, CancellationToken cancel = default); + Task ModifyAsync(EmployeeEntity employee, CancellationToken cancel = default); } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs new file mode 100644 index 00000000..88ef6875 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs @@ -0,0 +1,30 @@ +using ChangeTracking; +using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; + +namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Repository; + +public class EmployeeRepository : IEmployeeRepository +{ + private readonly IDbContextFactory _memberContextFactory; + + public EmployeeRepository(IDbContextFactory memberContextFactory) + { + this._memberContextFactory = memberContextFactory; + } + + public async Task ChangeAsync(EmployeeEntity srcEmployee, + CancellationToken cancel = default) + { + var tracked = srcEmployee.CastToIChangeTrackable(); + + await using var dbContext = await this._memberContextFactory.CreateDbContextAsync(cancel); + foreach (var changedProperty in tracked.ChangedProperties) + { + + } + + return await dbContext.SaveChangesAsync(cancel); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs index 7688ac6c..9b49e6a4 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs @@ -4,13 +4,5 @@ namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Repository; public interface IEmployeeRepository { - Task InsertAsync(Employee employee, CancellationToken cancel = default); -} - -class EmployeeRepository : IEmployeeRepository -{ - public Task InsertAsync(Employee employee, CancellationToken cancel = default) - { - throw new NotImplementedException(); - } -} + Task ChangeAsync(EmployeeEntity employee, CancellationToken cancel = default); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/IChangeable.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/IChangeable.cs new file mode 100644 index 00000000..65415d74 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/IChangeable.cs @@ -0,0 +1,14 @@ +namespace Lab.ChangeTracking.Domain.Annotations; + +public interface IChangeable +{ + Dictionary GetChangedProperties(); + + Dictionary GetOriginalValues(); + + void Track(string propertyName, object value); + + void Initial(); + + bool HasChanged { get; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj index eb2460e9..55d86c31 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj @@ -6,4 +6,13 @@ enable + + + + + + + + + diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/PropertyChangeTracker.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/PropertyChangeTracker.cs new file mode 100644 index 00000000..12577d8b --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/PropertyChangeTracker.cs @@ -0,0 +1,41 @@ +using System.Reflection; + +namespace Lab.ChangeTracking.Domain.Annotations; + +public class PropertyChangeTracker +{ + private Dictionary _changedProperties = new(); + private Dictionary _originalValues = new(); + + public void Initial() + { + var properties = this.GetType().GetProperties(); + foreach (var property in properties) + { + this._originalValues.Add(property.Name, property.GetValue(this)); + } + } + + public Dictionary GetChangedProperties() + { + return this._changedProperties; + } + + public Dictionary GetOriginalValues() + { + throw new NotImplementedException(); + } + + public void Track(string propertyName, object value) + { + var changes = this._changedProperties; + if (changes.ContainsKey(propertyName) == false) + { + changes.Add(propertyName, value); + } + else + { + changes[propertyName] = value; + } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Survey.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Survey.cs new file mode 100644 index 00000000..dfa0a859 --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Survey.cs @@ -0,0 +1,39 @@ +using System.ComponentModel; +using System.Reflection; + +namespace Lab.ChangeTracking.Domain.Annotations; + +public class Base : IRevertibleChangeTracking +{ + protected readonly Dictionary ChangedProperties = new(); + protected readonly Dictionary OriginalValues = new(); + + public void Initialize() + { + var properties = this.GetType().GetProperties(); + + // Save the current value of the properties to our dictionary. + foreach (var property in properties) + { + this.OriginalValues.Add(property.Name, property.GetValue(this)); + } + } + + public bool IsChanged { get; private set; } + + public void RejectChanges() + { + foreach (var property in this.ChangedProperties) + { + this.GetType().GetRuntimeProperty(property.Key).SetValue(this, property.Value); + } + + this.AcceptChanges(); + } + + public void AcceptChanges() + { + this.ChangedProperties.Clear(); + this.IsChanged = false; + } +} \ No newline at end of file From 15aea806db76def14108a11d8157ef8823f4f6d2 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 17 Feb 2022 13:11:24 +0800 Subject: [PATCH 155/424] =?UTF-8?q?feat:=20=E8=BF=BD=E8=B9=A4=E7=95=B0?= =?UTF-8?q?=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChangeTrackingUnitTest.cs | 30 ++++++++- .../MsTestHook.cs | 62 +++++++++---------- .../EmployeeAggregate/EmployeeAggregate.cs | 2 + .../Repository/EmployeeRepository.cs | 30 +++++++-- .../Lab.ChangeTracking.Domain.csproj | 1 + .../EntityModel/EmployeeDbContext.cs | 4 ++ 6 files changed, 90 insertions(+), 39 deletions(-) diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index 788d76a2..864181a6 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -52,14 +52,40 @@ public void 追蹤() } [TestMethod] - public void Repository_追蹤() + public void 追蹤後異動() { + var toDB = Insert(); var source = new EmployeeEntity() { - Id = Guid.NewGuid(), + Id = toDB.Id, Name = "yao", Age = 12, }; var employeeEntity = this._employeeAggregate.ModifyAsync(source).Result; } + + private static Employee Insert() + { + using var dbContext = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + var toDB = new Employee() + { + Id = Guid.NewGuid(), + Age = 18, + Name = "yao", + CreateAt = DateTimeOffset.Now, + CreateBy = "TEST", + Identity = new Identity() + { + Account = "yao", + Password = "123456", + CreateAt = DateTimeOffset.Now, + CreateBy = "TEST", + } + }; + dbContext.Employees.Add(toDB); + dbContext.SaveChanges(); + return toDB; + } + + } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs index c48c99fe..ca75b47b 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs @@ -1,31 +1,31 @@ -// using Microsoft.VisualStudio.TestTools.UnitTesting; -// -// namespace Lab.MultiTestCase.UnitTest; -// -// [TestClass] -// public class MsTestHook -// { -// [AssemblyCleanup] -// public static void Cleanup() -// { -// TestInstanceManager.SetTestEnvironmentVariable(); -// var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); -// if (db.Database.CanConnect()) -// { -// db.Database.EnsureDeleted(); -// } -// } -// -// [AssemblyInitialize] -// public static void Setup(TestContext context) -// { -// TestInstanceManager.SetTestEnvironmentVariable(); -// var db = TestInstanceManager.EmployeeDbContextFactory.CreateDbContext(); -// if (db.Database.CanConnect()) -// { -// db.Database.EnsureDeleted(); -// } -// -// db.Database.EnsureCreated(); -// } -// } \ No newline at end of file +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.MultiTestCase.UnitTest; + +[TestClass] +public class MsTestHook +{ + [AssemblyCleanup] + public static void Cleanup() + { + TestAssistants.SetTestEnvironmentVariable(); + var db = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + if (db.Database.CanConnect()) + { + db.Database.EnsureDeleted(); + } + } + + [AssemblyInitialize] + public static void Setup(TestContext context) + { + TestAssistants.SetTestEnvironmentVariable(); + var db = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + if (db.Database.CanConnect()) + { + db.Database.EnsureDeleted(); + } + + db.Database.EnsureCreated(); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs index 29a86465..731d16dc 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs @@ -21,4 +21,6 @@ public async Task ModifyAsync(EmployeeEntity employee, Cancellat var changeCount = await this._repository.ChangeAsync(trackable, cancel); return trackable; } + + } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs index 88ef6875..4f21f5ac 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs @@ -1,4 +1,6 @@ -using ChangeTracking; +using System.Linq; +using ChangeTracking; +using EFCore.BulkExtensions; using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; using Lab.ChangeTracking.Infrastructure.DB.EntityModel; using Microsoft.EntityFrameworkCore; @@ -20,11 +22,27 @@ public async Task ChangeAsync(EmployeeEntity srcEmployee, var tracked = srcEmployee.CastToIChangeTrackable(); await using var dbContext = await this._memberContextFactory.CreateDbContextAsync(cancel); - foreach (var changedProperty in tracked.ChangedProperties) - { - - } + var employee = + await dbContext.Employees.FirstOrDefaultAsync(a => a.Id == srcEmployee.Id, cancellationToken: cancel); + var destEmployee = this.To(srcEmployee); + var updateColumns = tracked.ChangedProperties.ToList(); + var changeCount = await dbContext.Employees + .Where(a => a.Id == srcEmployee.Id) + .BatchUpdateAsync(destEmployee, updateColumns, cancel); + + return changeCount; + } - return await dbContext.SaveChangesAsync(cancel); + public Employee To(EmployeeEntity srcEmployee) + { + return new Employee + { + Id = srcEmployee.Id, + Name = srcEmployee.Name, + Age = srcEmployee.Age, + Remark = srcEmployee.Remark, + CreateAt = srcEmployee.CreateAt, + CreateBy = srcEmployee.CreateBy, + }; } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj index 55d86c31..31373e41 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj @@ -8,6 +8,7 @@ + diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs index 1f651e6d..db4d2c80 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs @@ -69,6 +69,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) p.HasIndex(e => e.SequenceId) .IsUnique() .IsClustered(); + + p.Property(p => p.Remark) + .IsRequired(false) + ; }); } } From e54e01e62f336f8b2792aab06beeabc3a91696c6 Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 18 Feb 2022 01:58:34 +0800 Subject: [PATCH 156/424] =?UTF-8?q?=E7=95=B0=E5=8B=95=E8=A4=87=E9=9B=9C?= =?UTF-8?q?=E5=9E=8B=E5=88=A5-=E5=A4=B1=E6=95=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChangeTrackingUnitTest.cs | 3 ++- .../EmployeeAggregate/EmployeeAggregate.cs | 7 +++-- .../Entity/EmployeeEntity.cs | 2 ++ .../Entity/IdentityEntity.cs | 14 ++++++++++ .../EmployeeAggregate/IEmployeeAggregate.cs | 2 +- .../Repository/EmployeeRepository.cs | 27 ++++++++++++++++++- 6 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index 864181a6..b1682639 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -60,8 +60,9 @@ public void 追蹤後異動() Id = toDB.Id, Name = "yao", Age = 12, + Identity = new IdentityEntity(){} }; - var employeeEntity = this._employeeAggregate.ModifyAsync(source).Result; + var employeeEntity = this._employeeAggregate.ModifyFlowAsync(source).Result; } private static Employee Insert() diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs index 731d16dc..57d7c8d9 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs @@ -13,14 +13,13 @@ public EmployeeAggregate(IEmployeeRepository repository) this._repository = repository; } - public async Task ModifyAsync(EmployeeEntity employee, CancellationToken cancel = default) + public async Task ModifyFlowAsync(EmployeeEntity srcEmployee, CancellationToken cancel = default) { - var trackable = employee.AsTrackable(); + var trackable = srcEmployee.AsTrackable(); trackable.Age = 20; trackable.Name = "小章"; + trackable.Identity.Password = "9527"; var changeCount = await this._repository.ChangeAsync(trackable, cancel); return trackable; } - - } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs index 08b63ed2..eb669e1c 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs @@ -18,4 +18,6 @@ public class EmployeeEntity public virtual DateTimeOffset CreateAt { get; set; } public virtual string CreateBy { get; set; } + + public virtual IdentityEntity Identity { get; set; } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs new file mode 100644 index 00000000..48e24fed --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs @@ -0,0 +1,14 @@ +namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; + +public class IdentityEntity +{ + public virtual string Account { get; set; } + + public virtual string Password { get; set; } + + public virtual string Remark { get; set; } + + public virtual DateTimeOffset CreateAt { get; set; } + + public virtual string CreateBy { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs index 9ec26110..8af395df 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs @@ -4,5 +4,5 @@ namespace Lab.ChangeTracking.Domain.EmployeeAggregate; public interface IEmployeeAggregate { - Task ModifyAsync(EmployeeEntity employee, CancellationToken cancel = default); + Task ModifyFlowAsync(EmployeeEntity employee, CancellationToken cancel = default); } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs index 4f21f5ac..42d35245 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs @@ -23,12 +23,26 @@ public async Task ChangeAsync(EmployeeEntity srcEmployee, await using var dbContext = await this._memberContextFactory.CreateDbContextAsync(cancel); var employee = - await dbContext.Employees.FirstOrDefaultAsync(a => a.Id == srcEmployee.Id, cancellationToken: cancel); + await dbContext.Employees + .FirstOrDefaultAsync(a => a.Id == srcEmployee.Id, cancel); + var identity = + await dbContext.Identities + .FirstOrDefaultAsync(a => a.Employee_Id == srcEmployee.Id, cancel); + var destEmployee = this.To(srcEmployee); var updateColumns = tracked.ChangedProperties.ToList(); + var updateColumns1 = new List() + { + "Age", + nameof(srcEmployee.Identity) + }; var changeCount = await dbContext.Employees .Where(a => a.Id == srcEmployee.Id) .BatchUpdateAsync(destEmployee, updateColumns, cancel); + var changeCount1 = await dbContext.Identities + .Where(a => a.Employee_Id == srcEmployee.Id) + .BatchUpdateAsync(destEmployee.Identity, updateColumns1, cancel); + return changeCount; } @@ -43,6 +57,17 @@ public Employee To(EmployeeEntity srcEmployee) Remark = srcEmployee.Remark, CreateAt = srcEmployee.CreateAt, CreateBy = srcEmployee.CreateBy, + Identity = To(srcEmployee.Identity) + }; + } + public Identity To(IdentityEntity srcIdentity) + { + return new Identity() + { + Password = srcIdentity.Password, + Remark = srcIdentity.Remark, + CreateAt = srcIdentity.CreateAt, + CreateBy = srcIdentity.CreateBy, }; } } \ No newline at end of file From fc622712a150a36085e0bbb44d5ad41716eae1b7 Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 18 Feb 2022 18:09:33 +0800 Subject: [PATCH 157/424] add complex type --- .../ChangeTrackingUnitTest.cs | 5 +- .../EmployeeAggregate/EmployeeAggregate.cs | 7 +- .../Entity/EmployeeEntity.cs | 1 + .../Repository/EmployeeRepository.cs | 74 ++++++++++--------- .../Repository/IEmployeeRepository.cs | 2 +- 5 files changed, 50 insertions(+), 39 deletions(-) diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index b1682639..cec066bb 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using ChangeTracking; using Lab.ChangeTracking.Domain.EmployeeAggregate; @@ -60,7 +61,9 @@ public void 追蹤後異動() Id = toDB.Id, Name = "yao", Age = 12, - Identity = new IdentityEntity(){} + Identity = new IdentityEntity(){}, + Profiles = new Dictionary() + }; var employeeEntity = this._employeeAggregate.ModifyFlowAsync(source).Result; } diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs index 57d7c8d9..ea2db4f4 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs @@ -16,10 +16,13 @@ public EmployeeAggregate(IEmployeeRepository repository) public async Task ModifyFlowAsync(EmployeeEntity srcEmployee, CancellationToken cancel = default) { var trackable = srcEmployee.AsTrackable(); - trackable.Age = 20; + trackable.Name = "小章"; trackable.Identity.Password = "9527"; - var changeCount = await this._repository.ChangeAsync(trackable, cancel); + // trackable.Profiles.Add("FirstName", "余"); + trackable.Profiles = new Dictionary() { { "Last", "小章" } }; + + var changeCount = await this._repository.SaveChangeAsync(trackable, cancel); return trackable; } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs index eb669e1c..651e77ee 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs @@ -15,6 +15,7 @@ public class EmployeeEntity public virtual string Remark { get; set; } + public virtual Dictionary Profiles { get; set; } public virtual DateTimeOffset CreateAt { get; set; } public virtual string CreateBy { get; set; } diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs index 42d35245..2f424ad5 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs @@ -1,5 +1,4 @@ -using System.Linq; -using ChangeTracking; +using ChangeTracking; using EFCore.BulkExtensions; using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; using Lab.ChangeTracking.Infrastructure.DB.EntityModel; @@ -16,37 +15,6 @@ public EmployeeRepository(IDbContextFactory memberContextFact this._memberContextFactory = memberContextFactory; } - public async Task ChangeAsync(EmployeeEntity srcEmployee, - CancellationToken cancel = default) - { - var tracked = srcEmployee.CastToIChangeTrackable(); - - await using var dbContext = await this._memberContextFactory.CreateDbContextAsync(cancel); - var employee = - await dbContext.Employees - .FirstOrDefaultAsync(a => a.Id == srcEmployee.Id, cancel); - var identity = - await dbContext.Identities - .FirstOrDefaultAsync(a => a.Employee_Id == srcEmployee.Id, cancel); - - var destEmployee = this.To(srcEmployee); - var updateColumns = tracked.ChangedProperties.ToList(); - var updateColumns1 = new List() - { - "Age", - nameof(srcEmployee.Identity) - }; - var changeCount = await dbContext.Employees - .Where(a => a.Id == srcEmployee.Id) - .BatchUpdateAsync(destEmployee, updateColumns, cancel); - var changeCount1 = await dbContext.Identities - .Where(a => a.Employee_Id == srcEmployee.Id) - .BatchUpdateAsync(destEmployee.Identity, updateColumns1, cancel); - - - return changeCount; - } - public Employee To(EmployeeEntity srcEmployee) { return new Employee @@ -57,12 +25,13 @@ public Employee To(EmployeeEntity srcEmployee) Remark = srcEmployee.Remark, CreateAt = srcEmployee.CreateAt, CreateBy = srcEmployee.CreateBy, - Identity = To(srcEmployee.Identity) + Identity = this.To(srcEmployee.Identity) }; } + public Identity To(IdentityEntity srcIdentity) { - return new Identity() + return new Identity { Password = srcIdentity.Password, Remark = srcIdentity.Remark, @@ -70,4 +39,39 @@ public Identity To(IdentityEntity srcIdentity) CreateBy = srcIdentity.CreateBy, }; } + + public async Task SaveChangeAsync(EmployeeEntity srcEmployee, + CancellationToken cancel = default) + { + var employeeTrackable = srcEmployee.CastToIChangeTrackable(); + var identityTrackable = srcEmployee.Identity.CastToIChangeTrackable(); + + var memberChangeProperties = employeeTrackable.ChangedProperties.ToList(); + var identityChangeProperties = identityTrackable.ChangedProperties.ToList(); + + await using var dbContext = await this._memberContextFactory.CreateDbContextAsync(cancel); + await using var transaction = await dbContext.Database.BeginTransactionAsync(cancel); + + try + { + var destEmployee = this.To(srcEmployee); + var memberChangeCount = await dbContext.Employees + .Where(a => a.Id == srcEmployee.Id) + .BatchUpdateAsync(destEmployee, + memberChangeProperties, cancel); + var identityChangeCount = await dbContext.Identities + .Where(a => a.Employee_Id == srcEmployee.Id) + .BatchUpdateAsync(destEmployee.Identity, + identityChangeProperties, + cancel); + + await transaction.CommitAsync(cancel); + return memberChangeCount + identityChangeCount; + } + catch (Exception e) + { + await transaction.RollbackAsync(cancel); + throw new Exception("存檔失敗"); + } + } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs index 9b49e6a4..3fd851d5 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs @@ -4,5 +4,5 @@ namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Repository; public interface IEmployeeRepository { - Task ChangeAsync(EmployeeEntity employee, CancellationToken cancel = default); + Task SaveChangeAsync(EmployeeEntity employee, CancellationToken cancel = default); } \ No newline at end of file From ea069a24f7b3e3827b06a6f21b278b789aa78a27 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 19 Feb 2022 12:54:17 +0800 Subject: [PATCH 158/424] add collection trackable --- .../ChangeTrackingUnitTest.cs | 118 +++++++++++++++--- .../EmployeeAggregate/EmployeeAggregate.cs | 14 +-- .../Entity/EmployeeEntity.cs | 8 +- .../EmployeeAggregate/Entity/ProfileEntity.cs | 8 ++ .../Repository/EmployeeRepository.cs | 43 +++++++ .../AppDependencyInjectionExtensions.cs | 2 - .../EntityModel/EmployeeDbContext.cs | 2 - 7 files changed, 165 insertions(+), 30 deletions(-) create mode 100644 Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index cec066bb..a5f1efe1 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -1,6 +1,11 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; +using System.Linq; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Unicode; using ChangeTracking; using Lab.ChangeTracking.Domain.EmployeeAggregate; using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; @@ -15,11 +20,10 @@ namespace Lab.ChangeTracking.Domain.UnitTest; [TestClass] public class ChangeTrackingUnitTest { - private IEmployeeAggregate _employeeAggregate = TestAssistants.EmployeeAggregate; + private readonly IEmployeeAggregate _employeeAggregate = TestAssistants.EmployeeAggregate; - public ChangeTrackingUnitTest() - { - } + private readonly IDbContextFactory _employeeDbContextFactory = + TestAssistants.EmployeeDbContextFactory; [TestMethod] public void 原本用法() @@ -42,18 +46,92 @@ public void 追蹤() Id = Guid.NewGuid(), Name = "yao", Age = 12, + Identity = new IdentityEntity() { Account = "G1234" }, }; - var tracked = source.AsTrackable(); - tracked.Age = 18; + var trackable = source.AsTrackable(); + trackable.Name = "小章"; + var employTrackable = trackable.CastToIChangeTrackable(); - // source.Age = 18; - var trackable = tracked.CastToIChangeTrackable(); + var employeeChangedProperties = employTrackable.ChangedProperties.ToList(); - Assert.AreEqual(18, source.Age); + Console.WriteLine($"{nameof(this.追蹤)}:追蹤欄位"); + Console.WriteLine(JsonSerializer.Serialize(employeeChangedProperties)); + } + + [TestMethod] + public void 追蹤複雜型別() + { + var source = new EmployeeEntity() + { + Id = Guid.NewGuid(), + Name = "yao", + Age = 12, + Identity = new IdentityEntity() { Account = "G1234" }, + }; + var trackable = source.AsTrackable(); + trackable.Name = "小章"; + trackable.Identity.Account = "yao"; + var employTrackable = trackable.CastToIChangeTrackable(); + var identityTrackable = trackable.Identity.CastToIChangeTrackable(); + + var employeeChangedProperties = employTrackable.ChangedProperties.ToList(); + var identityChangedProperties = identityTrackable.ChangedProperties.ToList(); + + Console.WriteLine($"{nameof(this.追蹤複雜型別)}:追蹤欄位"); + Console.WriteLine(JsonSerializer.Serialize(employeeChangedProperties)); + Console.WriteLine(JsonSerializer.Serialize(identityChangedProperties)); + } + + [TestMethod] + public void 追蹤集合() + { + var source = new EmployeeEntity() + { + Id = Guid.NewGuid(), + Name = "yao", + Age = 12, + Identity = new IdentityEntity() { Account = "G1234" }, + Profiles = new List() + { + new() { FirstName = "第一筆" }, + new() { FirstName = "將被刪掉" }, + } + }; + var trackable = source.AsTrackable(); + trackable.Profiles[0].FirstName = "變更"; + trackable.Profiles.Add(new ProfileEntity() { FirstName = "新增" }); + trackable.Profiles.RemoveAt(1); + + var profileTrackable = trackable.Profiles.CastToIChangeTrackableCollection(); + + var unchangedItems = profileTrackable.UnchangedItems; + var addedItems = profileTrackable.AddedItems; + var changedItems = profileTrackable.ChangedItems; + var deleteItems = profileTrackable.DeletedItems; + + Console.WriteLine($"{nameof(this.追蹤集合)}:追蹤集合"); + Console.WriteLine($"UnchangedItems:{Serialize(unchangedItems)}"); + Console.WriteLine($"AddItem:{Serialize(addedItems)}"); + Console.WriteLine($"ChangedItems:{Serialize(changedItems)}"); + Console.WriteLine($"DeleteItems:{Serialize(deleteItems)}"); + Console.WriteLine($"{nameof(this.追蹤集合)}:追蹤變更屬性"); + var changeTrackable = trackable.Profiles[0].CastToIChangeTrackable(); + Console.WriteLine($"變更欄位:{Serialize(changeTrackable.ChangedProperties)}"); + } + + private static string Serialize(T instance) + { + var serialize = JsonSerializer.Serialize(instance, + new JsonSerializerOptions() + { + Encoder = JavaScriptEncoder.Create( + UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs) + }); + return serialize; } [TestMethod] - public void 追蹤後異動() + public void 異動追蹤後存檔() { var toDB = Insert(); var source = new EmployeeEntity() @@ -61,11 +139,23 @@ public void 追蹤後異動() Id = toDB.Id, Name = "yao", Age = 12, - Identity = new IdentityEntity(){}, - Profiles = new Dictionary() - + Identity = new IdentityEntity(), }; var employeeEntity = this._employeeAggregate.ModifyFlowAsync(source).Result; + this.DataShouldOk(source); + } + + private void DataShouldOk(EmployeeEntity source) + { + var dbContext = this._employeeDbContextFactory.CreateDbContext(); + var actual = dbContext.Employees + .Where(p => p.Id == source.Id) + .Include(p => p.Identity) + .First() + ; + + Assert.AreEqual("小章", actual.Name); + Assert.AreEqual("9528", actual.Identity.Password); } private static Employee Insert() @@ -90,6 +180,4 @@ private static Employee Insert() dbContext.SaveChanges(); return toDB; } - - } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs index ea2db4f4..ed22f194 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs @@ -15,14 +15,12 @@ public EmployeeAggregate(IEmployeeRepository repository) public async Task ModifyFlowAsync(EmployeeEntity srcEmployee, CancellationToken cancel = default) { - var trackable = srcEmployee.AsTrackable(); + var memberTrackable = srcEmployee.AsTrackable(); + + memberTrackable.Name = "小章"; + memberTrackable.Identity.Password = "9527"; - trackable.Name = "小章"; - trackable.Identity.Password = "9527"; - // trackable.Profiles.Add("FirstName", "余"); - trackable.Profiles = new Dictionary() { { "Last", "小章" } }; - - var changeCount = await this._repository.SaveChangeAsync(trackable, cancel); - return trackable; + var changeCount = await this._repository.SaveChangeAsync(memberTrackable, cancel); + return memberTrackable; } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs index 651e77ee..29e79a09 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs @@ -1,4 +1,5 @@ -using System.ComponentModel; +using System.Collections.ObjectModel; +using System.ComponentModel; using System.Runtime.CompilerServices; using ChangeTracking; using Lab.ChangeTracking.Domain.Annotations; @@ -15,10 +16,11 @@ public class EmployeeEntity public virtual string Remark { get; set; } - public virtual Dictionary Profiles { get; set; } + public virtual IList Profiles { get; init; } + public virtual DateTimeOffset CreateAt { get; set; } public virtual string CreateBy { get; set; } - public virtual IdentityEntity Identity { get; set; } + public virtual IdentityEntity Identity { get; init; } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs new file mode 100644 index 00000000..0a73b03d --- /dev/null +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs @@ -0,0 +1,8 @@ +namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; + +public class ProfileEntity +{ + public virtual string FirstName { get; set; } + + public virtual string LastName { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs index 2f424ad5..479d99cd 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs @@ -45,9 +45,50 @@ public async Task SaveChangeAsync(EmployeeEntity srcEmployee, { var employeeTrackable = srcEmployee.CastToIChangeTrackable(); var identityTrackable = srcEmployee.Identity.CastToIChangeTrackable(); + var memberChangeProperties = employeeTrackable.ChangedProperties.ToList(); + var identityChangeProperties = identityTrackable.ChangedProperties.ToList(); + + await using var dbContext = await this._memberContextFactory.CreateDbContextAsync(cancel); + await using var transaction = await dbContext.Database.BeginTransactionAsync(cancel); + + try + { + var destEmployee = this.To(srcEmployee); + var memberChangeCount = await dbContext.Employees + .Where(a => a.Id == srcEmployee.Id) + .BatchUpdateAsync(destEmployee, + memberChangeProperties, cancel); + var identityChangeCount = await dbContext.Identities + .Where(a => a.Employee_Id == srcEmployee.Id) + .BatchUpdateAsync(destEmployee.Identity, + identityChangeProperties, + cancel); + + await transaction.CommitAsync(cancel); + return memberChangeCount + identityChangeCount; + } + catch (Exception e) + { + await transaction.RollbackAsync(cancel); + throw new Exception("存檔失敗"); + } + + return 0; + } + + public async Task SaveChange1Async(EmployeeEntity srcEmployee, + CancellationToken cancel = default) + { + var employeeTrackable = srcEmployee.CastToIChangeTrackable(); + var identityTrackable = srcEmployee.Identity.CastToIChangeTrackable(); + var profileTrackable = srcEmployee.Profiles.CastToIChangeTrackableCollection(); var memberChangeProperties = employeeTrackable.ChangedProperties.ToList(); var identityChangeProperties = identityTrackable.ChangedProperties.ToList(); + var changedItems = profileTrackable.ChangedItems; + var addedItems = profileTrackable.AddedItems; + var unchangedItems = profileTrackable.UnchangedItems; + var deletedItems = profileTrackable.DeletedItems; await using var dbContext = await this._memberContextFactory.CreateDbContextAsync(cancel); await using var transaction = await dbContext.Database.BeginTransactionAsync(cancel); @@ -73,5 +114,7 @@ public async Task SaveChangeAsync(EmployeeEntity srcEmployee, await transaction.RollbackAsync(cancel); throw new Exception("存檔失敗"); } + + return 0; } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs index b5cdcb8b..6fc51c5c 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs @@ -28,8 +28,6 @@ public static void AddEntityFramework(this IServiceCollection services) ; }); - ; - // services.AddPooledDbContextFactory((provider, options) => // { // var option = provider.GetService(); diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs index db4d2c80..15a2302d 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs @@ -33,8 +33,6 @@ public EmployeeDbContext(DbContextOptions options) var sqlOptions = options.FindExtension(); if (sqlOptions != null) { - Console.WriteLine( - $"EmployeeDbContext of connection string be '{sqlOptions.ConnectionString}'"); this.Database.Migrate(); } } From 38cd599b55212e434ef4b3a420f65033817e6a92 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 19 Feb 2022 13:40:35 +0800 Subject: [PATCH 159/424] class=>record --- .../ChangeTrackingUnitTest.cs | 2 +- .../EmployeeAggregate/Entity/EmployeeEntity.cs | 2 +- .../EmployeeAggregate/Entity/IdentityEntity.cs | 2 +- .../EmployeeAggregate/Entity/ProfileEntity.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index a5f1efe1..e4a4d8e5 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -155,7 +155,7 @@ private void DataShouldOk(EmployeeEntity source) ; Assert.AreEqual("小章", actual.Name); - Assert.AreEqual("9528", actual.Identity.Password); + Assert.AreEqual("9527", actual.Identity.Password); } private static Employee Insert() diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs index 29e79a09..ac18ef3c 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs @@ -6,7 +6,7 @@ namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; -public class EmployeeEntity +public record EmployeeEntity { public virtual Guid Id { get; init; } diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs index 48e24fed..09505d0f 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs @@ -1,6 +1,6 @@ namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; -public class IdentityEntity +public record IdentityEntity { public virtual string Account { get; set; } diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs index 0a73b03d..9ae35128 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs @@ -1,6 +1,6 @@ namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; -public class ProfileEntity +public record ProfileEntity { public virtual string FirstName { get; set; } From 0305f3aa1e63d2a09f2dd4d3101459c92f069dd0 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 19 Feb 2022 13:44:06 +0800 Subject: [PATCH 160/424] refactor --- .../ChangeTrackingUnitTest.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index e4a4d8e5..c31d4a2e 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -52,10 +52,10 @@ public void 追蹤() trackable.Name = "小章"; var employTrackable = trackable.CastToIChangeTrackable(); - var employeeChangedProperties = employTrackable.ChangedProperties.ToList(); + var employeeChangedProperties = employTrackable.ChangedProperties; Console.WriteLine($"{nameof(this.追蹤)}:追蹤欄位"); - Console.WriteLine(JsonSerializer.Serialize(employeeChangedProperties)); + Console.WriteLine(ToJson(employeeChangedProperties)); } [TestMethod] @@ -78,8 +78,8 @@ public void 追蹤複雜型別() var identityChangedProperties = identityTrackable.ChangedProperties.ToList(); Console.WriteLine($"{nameof(this.追蹤複雜型別)}:追蹤欄位"); - Console.WriteLine(JsonSerializer.Serialize(employeeChangedProperties)); - Console.WriteLine(JsonSerializer.Serialize(identityChangedProperties)); + Console.WriteLine(ToJson(employeeChangedProperties)); + Console.WriteLine(ToJson(identityChangedProperties)); } [TestMethod] @@ -110,16 +110,16 @@ public void 追蹤集合() var deleteItems = profileTrackable.DeletedItems; Console.WriteLine($"{nameof(this.追蹤集合)}:追蹤集合"); - Console.WriteLine($"UnchangedItems:{Serialize(unchangedItems)}"); - Console.WriteLine($"AddItem:{Serialize(addedItems)}"); - Console.WriteLine($"ChangedItems:{Serialize(changedItems)}"); - Console.WriteLine($"DeleteItems:{Serialize(deleteItems)}"); + Console.WriteLine($"UnchangedItems:{ToJson(unchangedItems)}"); + Console.WriteLine($"AddItem:{ToJson(addedItems)}"); + Console.WriteLine($"ChangedItems:{ToJson(changedItems)}"); + Console.WriteLine($"DeleteItems:{ToJson(deleteItems)}"); Console.WriteLine($"{nameof(this.追蹤集合)}:追蹤變更屬性"); var changeTrackable = trackable.Profiles[0].CastToIChangeTrackable(); - Console.WriteLine($"變更欄位:{Serialize(changeTrackable.ChangedProperties)}"); + Console.WriteLine($"變更欄位:{ToJson(changeTrackable.ChangedProperties)}"); } - private static string Serialize(T instance) + private static string ToJson(T instance) { var serialize = JsonSerializer.Serialize(instance, new JsonSerializerOptions() From 66ca512fa482603fd55ae652f3b121a87cdd30d6 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 19 Feb 2022 14:25:00 +0800 Subject: [PATCH 161/424] refactor --- .../ChangeTrackingUnitTest.cs | 87 +++++++++---------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index c31d4a2e..035d8d03 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.Linq; using System.Text.Encodings.Web; using System.Text.Json; @@ -9,7 +7,6 @@ using ChangeTracking; using Lab.ChangeTracking.Domain.EmployeeAggregate; using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; -using Lab.ChangeTracking.Domain.EmployeeAggregate.Repository; using Lab.ChangeTracking.Infrastructure.DB.EntityModel; using Lab.MultiTestCase.UnitTest; using Microsoft.EntityFrameworkCore; @@ -28,7 +25,7 @@ public class ChangeTrackingUnitTest [TestMethod] public void 原本用法() { - var source = new EmployeeEntity() + var source = new EmployeeEntity { Id = Guid.NewGuid(), Name = "yao", @@ -41,12 +38,12 @@ public void 原本用法() [TestMethod] public void 追蹤() { - var source = new EmployeeEntity() + var source = new EmployeeEntity { Id = Guid.NewGuid(), Name = "yao", Age = 12, - Identity = new IdentityEntity() { Account = "G1234" }, + Identity = new IdentityEntity { Account = "G1234" }, }; var trackable = source.AsTrackable(); trackable.Name = "小章"; @@ -58,40 +55,16 @@ public void 追蹤() Console.WriteLine(ToJson(employeeChangedProperties)); } - [TestMethod] - public void 追蹤複雜型別() - { - var source = new EmployeeEntity() - { - Id = Guid.NewGuid(), - Name = "yao", - Age = 12, - Identity = new IdentityEntity() { Account = "G1234" }, - }; - var trackable = source.AsTrackable(); - trackable.Name = "小章"; - trackable.Identity.Account = "yao"; - var employTrackable = trackable.CastToIChangeTrackable(); - var identityTrackable = trackable.Identity.CastToIChangeTrackable(); - - var employeeChangedProperties = employTrackable.ChangedProperties.ToList(); - var identityChangedProperties = identityTrackable.ChangedProperties.ToList(); - - Console.WriteLine($"{nameof(this.追蹤複雜型別)}:追蹤欄位"); - Console.WriteLine(ToJson(employeeChangedProperties)); - Console.WriteLine(ToJson(identityChangedProperties)); - } - [TestMethod] public void 追蹤集合() { - var source = new EmployeeEntity() + var source = new EmployeeEntity { Id = Guid.NewGuid(), Name = "yao", Age = 12, - Identity = new IdentityEntity() { Account = "G1234" }, - Profiles = new List() + Identity = new IdentityEntity { Account = "G1234" }, + Profiles = new List { new() { FirstName = "第一筆" }, new() { FirstName = "將被刪掉" }, @@ -99,7 +72,7 @@ public void 追蹤集合() }; var trackable = source.AsTrackable(); trackable.Profiles[0].FirstName = "變更"; - trackable.Profiles.Add(new ProfileEntity() { FirstName = "新增" }); + trackable.Profiles.Add(new ProfileEntity { FirstName = "新增" }); trackable.Profiles.RemoveAt(1); var profileTrackable = trackable.Profiles.CastToIChangeTrackableCollection(); @@ -119,22 +92,35 @@ public void 追蹤集合() Console.WriteLine($"變更欄位:{ToJson(changeTrackable.ChangedProperties)}"); } - private static string ToJson(T instance) + [TestMethod] + public void 追蹤複雜型別() { - var serialize = JsonSerializer.Serialize(instance, - new JsonSerializerOptions() - { - Encoder = JavaScriptEncoder.Create( - UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs) - }); - return serialize; + var source = new EmployeeEntity + { + Id = Guid.NewGuid(), + Name = "yao", + Age = 12, + Identity = new IdentityEntity { Account = "G1234" }, + }; + var trackable = source.AsTrackable(); + trackable.Name = "小章"; + trackable.Identity.Account = "yao"; + var employTrackable = trackable.CastToIChangeTrackable(); + var identityTrackable = trackable.Identity.CastToIChangeTrackable(); + + var employeeChangedProperties = employTrackable.ChangedProperties; + var identityChangedProperties = identityTrackable.ChangedProperties; + + Console.WriteLine($"{nameof(this.追蹤複雜型別)}:追蹤欄位"); + Console.WriteLine(ToJson(employeeChangedProperties)); + Console.WriteLine(ToJson(identityChangedProperties)); } [TestMethod] public void 異動追蹤後存檔() { var toDB = Insert(); - var source = new EmployeeEntity() + var source = new EmployeeEntity { Id = toDB.Id, Name = "yao", @@ -161,14 +147,14 @@ private void DataShouldOk(EmployeeEntity source) private static Employee Insert() { using var dbContext = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); - var toDB = new Employee() + var toDB = new Employee { Id = Guid.NewGuid(), Age = 18, Name = "yao", CreateAt = DateTimeOffset.Now, CreateBy = "TEST", - Identity = new Identity() + Identity = new Identity { Account = "yao", Password = "123456", @@ -180,4 +166,15 @@ private static Employee Insert() dbContext.SaveChanges(); return toDB; } + + private static string ToJson(T instance) + { + var serialize = JsonSerializer.Serialize(instance, + new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.Create( + UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs) + }); + return serialize; + } } \ No newline at end of file From 98a592e43d452335ccacd1ebf60f18f51b95940d Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 19 Feb 2022 15:20:13 +0800 Subject: [PATCH 162/424] refactor --- .../ChangeTrackingUnitTest.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index 035d8d03..50807b93 100644 --- a/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTracking/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -51,7 +51,7 @@ public void 追蹤() var employeeChangedProperties = employTrackable.ChangedProperties; - Console.WriteLine($"{nameof(this.追蹤)}:追蹤欄位"); + Console.WriteLine($"{nameof(this.追蹤)}:追蹤 Employee 欄位"); Console.WriteLine(ToJson(employeeChangedProperties)); } @@ -82,14 +82,14 @@ public void 追蹤集合() var changedItems = profileTrackable.ChangedItems; var deleteItems = profileTrackable.DeletedItems; - Console.WriteLine($"{nameof(this.追蹤集合)}:追蹤集合"); + Console.WriteLine($"{nameof(this.追蹤集合)}:追蹤 Profiles 集合"); Console.WriteLine($"UnchangedItems:{ToJson(unchangedItems)}"); Console.WriteLine($"AddItem:{ToJson(addedItems)}"); Console.WriteLine($"ChangedItems:{ToJson(changedItems)}"); Console.WriteLine($"DeleteItems:{ToJson(deleteItems)}"); - Console.WriteLine($"{nameof(this.追蹤集合)}:追蹤變更屬性"); + Console.WriteLine($"{nameof(this.追蹤集合)}:追蹤 Profiles[0] 變更屬性"); var changeTrackable = trackable.Profiles[0].CastToIChangeTrackable(); - Console.WriteLine($"變更欄位:{ToJson(changeTrackable.ChangedProperties)}"); + Console.WriteLine($"Profiles[0] 變更欄位:{ToJson(changeTrackable.ChangedProperties)}"); } [TestMethod] @@ -111,8 +111,9 @@ public void 追蹤複雜型別() var employeeChangedProperties = employTrackable.ChangedProperties; var identityChangedProperties = identityTrackable.ChangedProperties; - Console.WriteLine($"{nameof(this.追蹤複雜型別)}:追蹤欄位"); + Console.WriteLine($"{nameof(this.追蹤複雜型別)}:追蹤 Employee 欄位"); Console.WriteLine(ToJson(employeeChangedProperties)); + Console.WriteLine($"{nameof(this.追蹤複雜型別)}:追蹤 Identity 欄位"); Console.WriteLine(ToJson(identityChangedProperties)); } From f955806c99999e61dd1440ea6c9f2d3a35aa12af Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 19 Feb 2022 22:25:12 +0800 Subject: [PATCH 163/424] add bddpipe project --- .../Lab.BDD.Pipe.TestProject.csproj | 18 +++++++++ .../Lab.BDD.Pipe.TestProject/UnitTest1.cs | 39 +++++++++++++++++++ Test/Lab.BDD.Pipe/Lab.BDD.Pipe.sln | 16 ++++++++ 3 files changed, 73 insertions(+) create mode 100644 Test/Lab.BDD.Pipe/Lab.BDD.Pipe.TestProject/Lab.BDD.Pipe.TestProject.csproj create mode 100644 Test/Lab.BDD.Pipe/Lab.BDD.Pipe.TestProject/UnitTest1.cs create mode 100644 Test/Lab.BDD.Pipe/Lab.BDD.Pipe.sln diff --git a/Test/Lab.BDD.Pipe/Lab.BDD.Pipe.TestProject/Lab.BDD.Pipe.TestProject.csproj b/Test/Lab.BDD.Pipe/Lab.BDD.Pipe.TestProject/Lab.BDD.Pipe.TestProject.csproj new file mode 100644 index 00000000..1fdc25af --- /dev/null +++ b/Test/Lab.BDD.Pipe/Lab.BDD.Pipe.TestProject/Lab.BDD.Pipe.TestProject.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + diff --git a/Test/Lab.BDD.Pipe/Lab.BDD.Pipe.TestProject/UnitTest1.cs b/Test/Lab.BDD.Pipe/Lab.BDD.Pipe.TestProject/UnitTest1.cs new file mode 100644 index 00000000..ab3f3637 --- /dev/null +++ b/Test/Lab.BDD.Pipe/Lab.BDD.Pipe.TestProject/UnitTest1.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using BddPipe; +using BddPipe.Model; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using static BddPipe.Runner; +using BddPipe.Recipe; + +namespace Lab.BDD.Pipe.TestProject; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void 相加兩個數字() + { + Scenario() + .Given("有兩個數字", () => new { firstNumber = (decimal)5, secondNumber = (decimal)10 }) + .When("按下相加", setup => + { + var calculation = new Calculation(); + return calculation.Add(setup.firstNumber, setup.secondNumber); + }) + .Then("預期得到", actual => + { + var expected = 15; + Assert.AreEqual(expected, actual); + }) + .Run(); + } +} + +public class Calculation +{ + public decimal Add(decimal firstNumber, decimal secondNumber) + { + return firstNumber + secondNumber; + } +} \ No newline at end of file diff --git a/Test/Lab.BDD.Pipe/Lab.BDD.Pipe.sln b/Test/Lab.BDD.Pipe/Lab.BDD.Pipe.sln new file mode 100644 index 00000000..4b518bf3 --- /dev/null +++ b/Test/Lab.BDD.Pipe/Lab.BDD.Pipe.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.BDD.Pipe.TestProject", "Lab.BDD.Pipe.TestProject\Lab.BDD.Pipe.TestProject.csproj", "{737A0583-DB1E-4C63-8552-EE6ED19700A8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {737A0583-DB1E-4C63-8552-EE6ED19700A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {737A0583-DB1E-4C63-8552-EE6ED19700A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {737A0583-DB1E-4C63-8552-EE6ED19700A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {737A0583-DB1E-4C63-8552-EE6ED19700A8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From c65d3532e791896b39426d4afb105ed44439d45e Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 28 Feb 2022 02:41:03 +0800 Subject: [PATCH 164/424] add first project --- .../ChangeTrackingForORM.sln | 48 +++++++ .../ChangeTrackingForORM/Makefile | 4 + .../ChangeTrackingForORM/docker-compose.yml | 10 ++ .../Employee/IEmployeeEntity.cs | 17 +++ .../Employee/IIdentityEntity.cs | 10 ++ .../Employee/IProfileEntity.cs | 8 ++ .../IChangeState.cs | 6 + .../IChangeTime.cs | 12 ++ .../IChangeTrackable.cs | 5 + .../Lab.ChangeTracking.Abstract.csproj | 9 ++ .../ChangeTrackingUnitTest.cs | 74 ++++++++++ .../Lab.ChangeTracking.Domain.UnitTest.csproj | 28 ++++ .../MsTestHook.cs | 31 +++++ .../TestAssistants.cs | 48 +++++++ .../AggregationRoot.cs | 130 ++++++++++++++++++ .../EmployeeAggregate/EmployeeAggregate.cs | 21 +++ .../Entity/EmployeeEntity.cs | 28 ++++ .../Entity/IdentityEntity.cs | 14 ++ .../EmployeeAggregate/Entity/ProfileEntity.cs | 8 ++ .../EmployeeAggregate/IEmployeeAggregate.cs | 8 ++ .../Repository/EmployeeRepository.cs | 120 ++++++++++++++++ .../Repository/IEmployeeRepository.cs | 8 ++ .../Lab.ChangeTracking.Domain/EntityState.cs | 9 ++ .../src/Lab.ChangeTracking.Domain/Error.cs | 2 + .../Lab.ChangeTracking.Domain.csproj | 20 +++ .../AppDependencyInjectionExtensions.cs | 50 +++++++ .../AppEnvironmentOption.cs | 31 +++++ .../EntityModel/Employee.cs | 35 +++++ .../EntityModel/EmployeeDbContext.cs | 89 ++++++++++++ .../EntityModel/Identity.cs | 34 +++++ .../EntityModel/Profile.cs | 20 +++ .../EnvironmentAssistant.cs | 15 ++ ...ab.ChangeTracking.Infrastructure.DB.csproj | 18 +++ 33 files changed, 970 insertions(+) create mode 100644 Property Change Tracking/ChangeTrackingForORM/ChangeTrackingForORM.sln create mode 100644 Property Change Tracking/ChangeTrackingForORM/Makefile create mode 100644 Property Change Tracking/ChangeTrackingForORM/docker-compose.yml create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IEmployeeEntity.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IIdentityEntity.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IProfileEntity.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/IChangeState.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/IChangeTime.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/IChangeTrackable.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Lab.ChangeTracking.Abstract.csproj create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AggregationRoot.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EntityState.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/Error.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Profile.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj diff --git a/Property Change Tracking/ChangeTrackingForORM/ChangeTrackingForORM.sln b/Property Change Tracking/ChangeTrackingForORM/ChangeTrackingForORM.sln new file mode 100644 index 00000000..783f6db4 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/ChangeTrackingForORM.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{1F776B7D-C182-4DC3-BA57-844657BD9AC0}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + Makefile = Makefile + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{483A9EB7-C4AF-4D68-80ED-49277985C760}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Infrastructure.DB", "src\Lab.ChangeTracking.Infrastructure.DB\Lab.ChangeTracking.Infrastructure.DB.csproj", "{A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Domain", "src\Lab.ChangeTracking.Domain\Lab.ChangeTracking.Domain.csproj", "{A1C827F2-683E-470C-A3DE-BD6DD2BE5198}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Domain.UnitTest", "src\Lab.ChangeTracking.Domain.UnitTest\Lab.ChangeTracking.Domain.UnitTest.csproj", "{7066EE2C-28A8-4408-B212-E582608AB967}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Abstract", "src\Lab.ChangeTracking.Abstract\Lab.ChangeTracking.Abstract.csproj", "{28D1EF37-8DEA-47BD-83A1-4302CC590791}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Release|Any CPU.Build.0 = Release|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Release|Any CPU.Build.0 = Release|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Release|Any CPU.Build.0 = Release|Any CPU + {28D1EF37-8DEA-47BD-83A1-4302CC590791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28D1EF37-8DEA-47BD-83A1-4302CC590791}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28D1EF37-8DEA-47BD-83A1-4302CC590791}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28D1EF37-8DEA-47BD-83A1-4302CC590791}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {7066EE2C-28A8-4408-B212-E582608AB967} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {28D1EF37-8DEA-47BD-83A1-4302CC590791} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + EndGlobalSection +EndGlobal diff --git a/Property Change Tracking/ChangeTrackingForORM/Makefile b/Property Change Tracking/ChangeTrackingForORM/Makefile new file mode 100644 index 00000000..cf17f07f --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/Makefile @@ -0,0 +1,4 @@ +G1: + echo 'Hello World' +G2: + cmd \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/docker-compose.yml b/Property Change Tracking/ChangeTrackingForORM/docker-compose.yml new file mode 100644 index 00000000..8ecb1567 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.8" + +services: + db-sql: + image: mcr.microsoft.com/mssql/server:2019-latest + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=pass@w0rd1~ + ports: + - 1433:1433 \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IEmployeeEntity.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IEmployeeEntity.cs new file mode 100644 index 00000000..5d91da43 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IEmployeeEntity.cs @@ -0,0 +1,17 @@ +namespace Lab.ChangeTracking.Abstract; + +public interface IEmployeeEntity : IChangeTrackable +{ + public Guid Id { get; set; } + + public string Name { get; set; } + + public int? Age { get; set; } + + public string Remark { get; set; } + + // public IList Profiles { get; set; } + // + // public IIdentityEntity Identity { get; set; } + +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IIdentityEntity.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IIdentityEntity.cs new file mode 100644 index 00000000..01defb52 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IIdentityEntity.cs @@ -0,0 +1,10 @@ +namespace Lab.ChangeTracking.Abstract; + +public interface IIdentityEntity : IChangeTime +{ + public string Account { get; set; } + + public string Password { get; set; } + + public string Remark { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IProfileEntity.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IProfileEntity.cs new file mode 100644 index 00000000..a7dddbc8 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IProfileEntity.cs @@ -0,0 +1,8 @@ +namespace Lab.ChangeTracking.Abstract; + +public interface IProfileEntity : IChangeTime +{ + public string FirstName { get; set; } + + public string LastName { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/IChangeState.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/IChangeState.cs new file mode 100644 index 00000000..c906d4ef --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/IChangeState.cs @@ -0,0 +1,6 @@ +namespace Lab.ChangeTracking.Abstract; + +public interface IChangeState +{ + int Version { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/IChangeTime.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/IChangeTime.cs new file mode 100644 index 00000000..2bb10097 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/IChangeTime.cs @@ -0,0 +1,12 @@ +namespace Lab.ChangeTracking.Abstract; + +public interface IChangeTime +{ + DateTimeOffset CreatedAt { get; set; } + + string CreatedBy { get; set; } + + DateTimeOffset UpdatedAt { get; set; } + + string UpdatedBy { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/IChangeTrackable.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/IChangeTrackable.cs new file mode 100644 index 00000000..24c9736f --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/IChangeTrackable.cs @@ -0,0 +1,5 @@ +namespace Lab.ChangeTracking.Abstract; + +public interface IChangeTrackable : IChangeTime, IChangeState +{ +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Lab.ChangeTracking.Abstract.csproj b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Lab.ChangeTracking.Abstract.csproj new file mode 100644 index 00000000..eb2460e9 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Lab.ChangeTracking.Abstract.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs new file mode 100644 index 00000000..8c63ed3d --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Unicode; +using ChangeTracking; +using Lab.ChangeTracking.Domain.Entity; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Lab.MultiTestCase.UnitTest; +using Microsoft.EntityFrameworkCore; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.ChangeTracking.Domain.UnitTest; + +[TestClass] +public class ChangeTrackingUnitTest +{ + private readonly IEmployeeAggregate _employeeAggregate = TestAssistants.EmployeeAggregate; + + private readonly IDbContextFactory _employeeDbContextFactory = + TestAssistants.EmployeeDbContextFactory; + + [TestMethod] + public void 原本用法() + { + var employeeAggregate = new EmployeeAggregate(new EmployeeEntity() + { + Id = Guid.NewGuid(), + Age = 18, + Name = "Yao", + Remark = "TEST" + }); + employeeAggregate.SetName("小章"); + employeeAggregate.SubmitChange(DateTimeOffset.Now, "Sys1"); + + } + + private static Employee Insert() + { + Console.WriteLine("新增資料"); + using var dbContext = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + var toDB = new Employee + { + Id = Guid.NewGuid(), + Age = 18, + Name = "yao", + CreatedAt = DateTimeOffset.Now, + CreatedBy = "TEST", + Identity = new Identity + { + Account = "yao", + Password = "123456", + CreatedAt = DateTimeOffset.Now, + CreatedBy = "TEST", + } + }; + dbContext.Employees.Add(toDB); + dbContext.SaveChanges(); + + return toDB; + } + + private static string ToJson(T instance) + { + var serialize = JsonSerializer.Serialize(instance, + new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.Create( + UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs) + }); + return serialize; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj new file mode 100644 index 00000000..72a505c5 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + + + + + + + + diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs new file mode 100644 index 00000000..ca75b47b --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs @@ -0,0 +1,31 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.MultiTestCase.UnitTest; + +[TestClass] +public class MsTestHook +{ + [AssemblyCleanup] + public static void Cleanup() + { + TestAssistants.SetTestEnvironmentVariable(); + var db = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + if (db.Database.CanConnect()) + { + db.Database.EnsureDeleted(); + } + } + + [AssemblyInitialize] + public static void Setup(TestContext context) + { + TestAssistants.SetTestEnvironmentVariable(); + var db = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + if (db.Database.CanConnect()) + { + db.Database.EnsureDeleted(); + } + + db.Database.EnsureCreated(); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs new file mode 100644 index 00000000..7a127ed1 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs @@ -0,0 +1,48 @@ +using System; +using Lab.ChangeTracking.Domain; +using Lab.ChangeTracking.Domain.Repository; +using Lab.ChangeTracking.Infrastructure.DB; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Lab.MultiTestCase.UnitTest; + +// assistant +internal class TestAssistants +{ + private static IServiceProvider _serviceProvider; + + public static IDbContextFactory EmployeeDbContextFactory => + _serviceProvider.GetService>(); + + public static IEmployeeRepository EmployeeRepository => + _serviceProvider.GetService(); + + public static IEmployeeAggregate EmployeeAggregate => + _serviceProvider.GetService(); + + static TestAssistants() + { + var services = new ServiceCollection(); + ConfigureTestServices(services); + SetTestEnvironmentVariable(); + } + + public static void ConfigureTestServices(IServiceCollection services) + { + services.AddAppEnvironment(); + services.AddEntityFramework(); + + // services.AddSingleton(); + // services.AddSingleton(); + _serviceProvider = services.BuildServiceProvider(); + } + + public static void SetTestEnvironmentVariable() + { + var option = _serviceProvider.GetService(); + option.EmployeeDbConnectionString = + "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True"; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AggregationRoot.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AggregationRoot.cs new file mode 100644 index 00000000..07fc92db --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AggregationRoot.cs @@ -0,0 +1,130 @@ +using Lab.ChangeTracking.Abstract; + +namespace Lab.ChangeTracking.Domain; + +public abstract class AggregationRoot where T : IChangeTrackable +{ + public EntityState State { get; private set; } + + /// + /// 建立時間 + /// + public DateTimeOffset CreatedAt + { + get => this._instance.CreatedAt; + init => this._instance.CreatedAt = value; + } + + /// + /// 建立者 + /// + public string CreatedBy + { + get => this._instance.CreatedBy; + init => this._instance.CreatedBy = value; + } + + /// + /// 異動時間 + /// + public DateTimeOffset UpdatedAt + { + get => this._instance.UpdatedAt; + init => this._instance.UpdatedAt = value; + } + + /// + /// 異動者 + /// + public string UpdatedBy + { + get => this._instance.UpdatedBy; + init => this._instance.UpdatedBy = value; + } + + /// + /// 異動版號 + /// + public int Version + { + get => this._instance.Version; + init => this._instance.Version = value; + } + + private readonly IList> _changes = new List>(); + protected readonly Dictionary ChangedProperties = new(); + protected readonly Dictionary OriginalValues = new(); + + protected T _instance; + + public AggregationRoot(T instance) + { + this._instance = instance; + } + + public IReadOnlyList> GetInstanceChanges() + { + return this._changes.ToList(); + } + + public T GetInstance() + { + return this._instance; + } + + /// + /// SubmitChange 後則進版號 + /// + /// + /// + /// + public (Error err, bool changed) SubmitChange(DateTimeOffset when, string who) + { + if (this.State == EntityState.Submitted) + { + return ( + new Error("STATE_CONFLICT", + $"Entity({this.State}) was submitted and should not submit again."), false); + } + + if (this.State == EntityState.Unchanged) + { + return (null, false); + } + + if (this.State == EntityState.Added) + { + this.Track(x => x.CreatedAt = when); + this.Track(x => x.CreatedBy = who); + this.Track(x => x.UpdatedAt = when); + this.Track(x => x.UpdatedBy = who); + this.Track(x => x.Version += 1); + } + else + { + this.Track(x => x.UpdatedAt = when); + this.Track(x => x.UpdatedBy = who); + this.Track(x => x.Version += 1); + } + + this.State = EntityState.Submitted; + + return (null, true); + } + + protected void Track(Action change) + { + if (this.State == EntityState.Submitted) + { + throw new Exception("已經 Submitted 的 Doamin,無法再進行修改。"); + } + + change(this._instance); + this._changes.Add(change); + + if (this.State == EntityState.Unchanged) + { + this.State = EntityState.Modified; + } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs new file mode 100644 index 00000000..a526348e --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs @@ -0,0 +1,21 @@ +using Lab.ChangeTracking.Domain.Entity; + +namespace Lab.ChangeTracking.Domain; + +public class EmployeeAggregate : AggregationRoot +{ + public EmployeeAggregate(EmployeeEntity instance) : base(instance) + { + } + + public EmployeeAggregate SetName(string name) + { + var instance = this.GetInstance(); + if (instance.Name != name) + { + this.Track(p => p.Name = name); + } + + return this; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs new file mode 100644 index 00000000..c182ab19 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs @@ -0,0 +1,28 @@ +using Lab.ChangeTracking.Abstract; + +namespace Lab.ChangeTracking.Domain.Entity; + +public class EmployeeEntity :IEmployeeEntity +{ + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset UpdatedAt { get; set; } + + public string UpdatedBy { get; set; } + + public int Version { get; set; } + + public Guid Id { get; set; } + + public string Name { get; set; } + + public int? Age { get; set; } + + public string Remark { get; set; } + + // public IList Profiles { get; set; } + + public IdentityEntity Identity { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs new file mode 100644 index 00000000..457beb06 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs @@ -0,0 +1,14 @@ +namespace Lab.ChangeTracking.Domain.Entity; + +public record IdentityEntity +{ + public virtual string Account { get; set; } + + public virtual string Password { get; set; } + + public virtual string Remark { get; set; } + + public virtual DateTimeOffset CreateAt { get; set; } + + public virtual string CreateBy { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs new file mode 100644 index 00000000..88550e4b --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs @@ -0,0 +1,8 @@ +namespace Lab.ChangeTracking.Domain.Entity; + +public record ProfileEntity +{ + public virtual string FirstName { get; set; } + + public virtual string LastName { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs new file mode 100644 index 00000000..afe51a7e --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs @@ -0,0 +1,8 @@ +using Lab.ChangeTracking.Domain.Entity; + +namespace Lab.ChangeTracking.Domain; + +public interface IEmployeeAggregate +{ + Task ModifyFlowAsync(EmployeeEntity employee, CancellationToken cancel = default); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs new file mode 100644 index 00000000..2594db6d --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs @@ -0,0 +1,120 @@ +// using ChangeTracking; +// using EFCore.BulkExtensions; +// using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; +// using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +// using Microsoft.EntityFrameworkCore; +// +// namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Repository; +// +// public class EmployeeRepository : IEmployeeRepository +// { +// private readonly IDbContextFactory _memberContextFactory; +// +// public EmployeeRepository(IDbContextFactory memberContextFactory) +// { +// this._memberContextFactory = memberContextFactory; +// } +// +// public Employee To(EmployeeEntity srcEmployee) +// { +// return new Employee +// { +// Id = srcEmployee.Id, +// Name = srcEmployee.Name, +// Age = srcEmployee.Age, +// Remark = srcEmployee.Remark, +// CreateAt = srcEmployee.CreateAt, +// CreateBy = srcEmployee.CreateBy, +// Identity = this.To(srcEmployee.Identity) +// }; +// } +// +// public Identity To(IdentityEntity srcIdentity) +// { +// return new Identity +// { +// Password = srcIdentity.Password, +// Remark = srcIdentity.Remark, +// CreateAt = srcIdentity.CreateAt, +// CreateBy = srcIdentity.CreateBy, +// }; +// } +// +// public async Task SaveChangeAsync(EmployeeEntity srcEmployee, +// CancellationToken cancel = default) +// { +// var employeeTrackable = srcEmployee.CastToIChangeTrackable(); +// var identityTrackable = srcEmployee.Identity.CastToIChangeTrackable(); +// var memberChangeProperties = employeeTrackable.ChangedProperties.ToList(); +// var identityChangeProperties = identityTrackable.ChangedProperties.ToList(); +// +// await using var dbContext = await this._memberContextFactory.CreateDbContextAsync(cancel); +// await using var transaction = await dbContext.Database.BeginTransactionAsync(cancel); +// +// try +// { +// var destEmployee = this.To(srcEmployee); +// var memberChangeCount = await dbContext.Employees +// .Where(a => a.Id == srcEmployee.Id) +// .BatchUpdateAsync(destEmployee, +// memberChangeProperties, cancel); +// var identityChangeCount = await dbContext.Identities +// .Where(a => a.Employee_Id == srcEmployee.Id) +// .BatchUpdateAsync(destEmployee.Identity, +// identityChangeProperties, +// cancel); +// +// await transaction.CommitAsync(cancel); +// return memberChangeCount + identityChangeCount; +// } +// catch (Exception e) +// { +// await transaction.RollbackAsync(cancel); +// throw new Exception("存檔失敗"); +// } +// +// return 0; +// } +// +// public async Task SaveChange1Async(EmployeeEntity srcEmployee, +// CancellationToken cancel = default) +// { +// var employeeTrackable = srcEmployee.CastToIChangeTrackable(); +// var identityTrackable = srcEmployee.Identity.CastToIChangeTrackable(); +// var profileTrackable = srcEmployee.Profiles.CastToIChangeTrackableCollection(); +// +// var memberChangeProperties = employeeTrackable.ChangedProperties.ToList(); +// var identityChangeProperties = identityTrackable.ChangedProperties.ToList(); +// var changedItems = profileTrackable.ChangedItems; +// var addedItems = profileTrackable.AddedItems; +// var unchangedItems = profileTrackable.UnchangedItems; +// var deletedItems = profileTrackable.DeletedItems; +// +// await using var dbContext = await this._memberContextFactory.CreateDbContextAsync(cancel); +// await using var transaction = await dbContext.Database.BeginTransactionAsync(cancel); +// +// try +// { +// var destEmployee = this.To(srcEmployee); +// var memberChangeCount = await dbContext.Employees +// .Where(a => a.Id == srcEmployee.Id) +// .BatchUpdateAsync(destEmployee, +// memberChangeProperties, cancel); +// var identityChangeCount = await dbContext.Identities +// .Where(a => a.Employee_Id == srcEmployee.Id) +// .BatchUpdateAsync(destEmployee.Identity, +// identityChangeProperties, +// cancel); +// +// await transaction.CommitAsync(cancel); +// return memberChangeCount + identityChangeCount; +// } +// catch (Exception e) +// { +// await transaction.RollbackAsync(cancel); +// throw new Exception("存檔失敗"); +// } +// +// return 0; +// } +// } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs new file mode 100644 index 00000000..0564926a --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs @@ -0,0 +1,8 @@ +using Lab.ChangeTracking.Domain.Entity; + +namespace Lab.ChangeTracking.Domain.Repository; + +public interface IEmployeeRepository +{ + Task SaveChangeAsync(EmployeeEntity employee, CancellationToken cancel = default); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EntityState.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EntityState.cs new file mode 100644 index 00000000..7b4c0e5c --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EntityState.cs @@ -0,0 +1,9 @@ +namespace Lab.ChangeTracking.Domain; + +public enum EntityState +{ + Added, + Unchanged, + Modified, + Submitted +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/Error.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/Error.cs new file mode 100644 index 00000000..7dbcb965 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/Error.cs @@ -0,0 +1,2 @@ +namespace Lab.ChangeTracking.Domain; +public record Error(T Code, object Message); \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj new file mode 100644 index 00000000..def7c560 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj @@ -0,0 +1,20 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs new file mode 100644 index 00000000..6fc51c5c --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs @@ -0,0 +1,50 @@ +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.ChangeTracking.Infrastructure.DB; + +public static class AppDependencyInjectionExtensions +{ + public static void AddAppEnvironment(this IServiceCollection services) + { + services.AddLogging(builder => + { + builder.AddConsole(); + }); + services.AddSingleton(); + } + + public static void AddEntityFramework(this IServiceCollection services) + { + services.AddPooledDbContextFactory((provider, optionsBuilder) => + { + var option = provider.GetService(); + var connectionString = option.EmployeeDbConnectionString; + var loggerFactory = provider.GetService(); + optionsBuilder.UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + ; + }); + + // services.AddPooledDbContextFactory((provider, options) => + // { + // var option = provider.GetService(); + // var loggerFactory = provider.GetService(); + // options.UseNpgsql( + // option.MemberDbConnectionString, //只會呼叫一次 + // builder => + // builder.EnableRetryOnFailure( + // 10, + // TimeSpan.FromSeconds(30), + // new List { "57P01" })) + // + // // .UseLazyLoadingProxies() + // .UseSnakeCaseNamingConvention() + // .UseLoggerFactory(loggerFactory) + // ; + // }); + } + +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs new file mode 100644 index 00000000..e29f53f3 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs @@ -0,0 +1,31 @@ +namespace Lab.ChangeTracking.Infrastructure.DB; + +public class AppEnvironmentOption +{ + public string EmployeeDbConnectionString + { + get + { + if (string.IsNullOrWhiteSpace(this._employeeDbConnectionString)) + { + this._employeeDbConnectionString = + EnvironmentAssistant.GetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR); + } + + return this._employeeDbConnectionString; + } + set + { + this._employeeDbConnectionString = value; + Environment.SetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR, value); + } + } + + private string _employeeDbConnectionString; + private readonly string EMPLOYEE_DB_CONN_STR = "EMPLOYEE_DB_CONNECTION_STR"; + + public void Initial() + { + var memberDbConnectionString = this.EmployeeDbConnectionString; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs new file mode 100644 index 00000000..42cac8ca --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Lab.ChangeTracking.Abstract; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel +{ + public class Employee : IEmployeeEntity + { + public Guid Id { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + public string Remark { get; set; } + + public IList Profiles { get; set; } + + public Identity Identity { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset UpdatedAt { get; set; } + + public string UpdatedBy { get; set; } + + public int Version { get; set; } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs new file mode 100644 index 00000000..80e699a6 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs @@ -0,0 +1,89 @@ +using System.Dynamic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel +{ + public class EmployeeDbContext : DbContext + { + private static readonly bool[] s_migrated = { false }; + + public virtual DbSet Employees { get; set; } + + public virtual DbSet Identities { get; set; } + + public virtual DbSet Profiles { get; set; } + + public EmployeeDbContext(DbContextOptions options) + : base(options) + { + if (s_migrated[0]) + { + return; + } + + lock (s_migrated) + { + if (s_migrated[0] == false) + { + var sqlOptions = options.FindExtension(); + if (sqlOptions != null) + { + this.Database.Migrate(); + } + + s_migrated[0] = true; + } + } + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new EmployeeConfiguration()); + modelBuilder.ApplyConfiguration(new IdentityConfiguration()); + } + + internal class EmployeeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Employee"); + builder.HasKey(p => p.Id) + .IsClustered(false); + + builder.Property(p => p.Name).IsRequired(true); + builder.Property(p => p.Remark).IsRequired(false); + builder.Property(p => p.UpdatedBy).IsRequired(false); + + builder.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + } + } + + internal class IdentityConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Identity"); + builder.HasKey(p => p.Employee_Id).IsClustered(false); + builder.HasOne(e => e.Employee) + .WithOne(p => p.Identity) + .HasForeignKey(p => p.Employee_Id) + .OnDelete(DeleteBehavior.Cascade) + ; + + builder.Property(p => p.Account).IsRequired(); + builder.Property(p => p.Password).IsRequired(); + builder.Property(p => p.Remark).IsRequired(false); + builder.Property(p => p.UpdatedBy).IsRequired(false); + + builder.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + } + } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs new file mode 100644 index 00000000..62b36ba3 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs @@ -0,0 +1,34 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Security.Principal; +using Lab.ChangeTracking.Abstract; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel +{ + public class Identity : IIdentityEntity + { + public Guid Employee_Id { get; set; } + + // [ForeignKey("Employee_Id")] + public virtual Employee Employee { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset UpdatedAt { get; set; } + + public string UpdatedBy { get; set; } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Profile.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Profile.cs new file mode 100644 index 00000000..20ed9ff4 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Profile.cs @@ -0,0 +1,20 @@ +using Lab.ChangeTracking.Abstract; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel; + +public class Profile : IProfileEntity +{ + public Guid Id { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset UpdatedAt { get; set; } + + public string UpdatedBy { get; set; } + + public string FirstName { get; set; } + + public string LastName { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs new file mode 100644 index 00000000..b115d8a2 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs @@ -0,0 +1,15 @@ +namespace Lab.ChangeTracking.Infrastructure.DB; + +public class EnvironmentAssistant +{ + public static string GetEnvironmentVariable(string key) + { + var result = Environment.GetEnvironmentVariable(key); + if (string.IsNullOrWhiteSpace(result)) + { + throw new Exception($"the key '{key}' not exist in environment variable"); + } + + return result; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj new file mode 100644 index 00000000..d944728b --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + From 066023b6b3f75659860a99ddcbaaeffabd15b9bd Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 2 Mar 2022 01:04:28 +0800 Subject: [PATCH 165/424] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E7=95=B0?= =?UTF-8?q?=E5=8B=95=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Employee/IProfileEntity.cs | 2 + .../ChangeTrackingUnitTest.cs | 74 ++++-- .../Lab.ChangeTracking.Domain.UnitTest.csproj | 1 + .../TestAssistants.cs | 14 +- .../AggregationRoot.cs | 51 ++-- .../EmployeeAggregate/EmployeeAggregate.cs | 71 +++++- .../Entity/EmployeeEntity.cs | 2 +- .../EmployeeAggregate/IEmployeeAggregate.cs | 11 +- .../Repository/EmployeeRepository.cs | 219 ++++++++---------- .../Repository/IEmployeeRepository.cs | 9 +- .../EmployeeRepositoryExtensions.cs | 22 ++ .../Lab.ChangeTracking.Domain/EntityState.cs | 8 +- .../IAggregationRoot.cs | 22 ++ .../Lab.ChangeTracking.Domain/ISystemClock.cs | 11 + .../IUUIDProvider.cs | 14 ++ .../EntityModel/EmployeeDbContext.cs | 62 +++-- .../EntityModel/Profile.cs | 8 + 17 files changed, 409 insertions(+), 192 deletions(-) create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeRepositoryExtensions.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IAggregationRoot.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/ISystemClock.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IUUIDProvider.cs diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IProfileEntity.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IProfileEntity.cs index a7dddbc8..f0acfa1d 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IProfileEntity.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Abstract/Employee/IProfileEntity.cs @@ -5,4 +5,6 @@ public interface IProfileEntity : IChangeTime public string FirstName { get; set; } public string LastName { get; set; } + + public string Remark { get; set; } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index 8c63ed3d..5b9e1835 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -1,48 +1,85 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Unicode; -using ChangeTracking; -using Lab.ChangeTracking.Domain.Entity; +using System.Threading.Tasks; +using Lab.ChangeTracking.Abstract; using Lab.ChangeTracking.Infrastructure.DB.EntityModel; using Lab.MultiTestCase.UnitTest; using Microsoft.EntityFrameworkCore; using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; namespace Lab.ChangeTracking.Domain.UnitTest; [TestClass] public class ChangeTrackingUnitTest { - private readonly IEmployeeAggregate _employeeAggregate = TestAssistants.EmployeeAggregate; + private readonly IEmployeeAggregate _employeeAggregate = TestAssistants.EmployeeAggregate; private readonly IDbContextFactory _employeeDbContextFactory = TestAssistants.EmployeeDbContextFactory; [TestMethod] - public void 原本用法() + public async Task 新增() { - var employeeAggregate = new EmployeeAggregate(new EmployeeEntity() - { - Id = Guid.NewGuid(), - Age = 18, - Name = "Yao", - Remark = "TEST" - }); - employeeAggregate.SetName("小章"); - employeeAggregate.SubmitChange(DateTimeOffset.Now, "Sys1"); - + // arrange + var employeeRepository = TestAssistants.EmployeeRepository; + var systemClock = Substitute.For(); + systemClock.Now.Returns(DateTimeOffset.Parse("2021-01-01")); + var uuIdProvider = Substitute.For(); + uuIdProvider.GenerateId().Returns(TestAssistants.Parse("1")); + + // act + var employeeAggregate = new EmployeeAggregate(employeeRepository, uuIdProvider, systemClock); + employeeAggregate.CreateInstance("yao", 18, "Test User"); + employeeAggregate.SubmitChange(systemClock.Now, "Sys1"); + var result = await employeeAggregate.SaveChangeAsync(); + + // assert + await using var dbContext = await this._employeeDbContextFactory.CreateDbContextAsync(); + var actual = dbContext.Employees.AsTracking().FirstOrDefault(); + Assert.AreEqual("yao", actual.Name); + Assert.AreEqual(18, actual.Age); + Assert.AreEqual(1, actual.Version); + Assert.AreEqual("Test User", actual.Remark); + } + + [TestMethod] + public async Task 編輯() + { + // arrange + InsertTestData(); + var employeeRepository = TestAssistants.EmployeeRepository; + var systemClock = Substitute.For(); + systemClock.Now.Returns(DateTimeOffset.Parse("2021-01-02")); + var uuIdProvider = Substitute.For(); + uuIdProvider.GenerateId().Returns(TestAssistants.Parse("1")); + + // act + var employeeAggregate = new EmployeeAggregate(employeeRepository, uuIdProvider, systemClock); + await employeeAggregate.GetAsync(TestAssistants.Parse("1")); + employeeAggregate.SetName("小章"); + employeeAggregate.SetAge(20); + employeeAggregate.SubmitChange(systemClock.Now, "Sys1"); + var count = await employeeAggregate.SaveChangeAsync(); + + // assert + await using var dbContext = await this._employeeDbContextFactory.CreateDbContextAsync(); + var actual = dbContext.Employees.AsTracking().FirstOrDefault(); + Assert.AreEqual("小章", actual.Name); + Assert.AreEqual(20, actual.Age); + Assert.AreEqual(2, actual.Version); } - private static Employee Insert() + private static Employee InsertTestData() { Console.WriteLine("新增資料"); using var dbContext = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); var toDB = new Employee { - Id = Guid.NewGuid(), + Id = TestAssistants.Parse("1"), Age = 18, Name = "yao", CreatedAt = DateTimeOffset.Now, @@ -53,7 +90,8 @@ private static Employee Insert() Password = "123456", CreatedAt = DateTimeOffset.Now, CreatedBy = "TEST", - } + }, + Version = 1 }; dbContext.Employees.Add(toDB); dbContext.SaveChanges(); diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj index 72a505c5..b29d606e 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj @@ -19,6 +19,7 @@ +
diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs index 7a127ed1..dd355049 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs @@ -1,4 +1,5 @@ using System; +using Lab.ChangeTracking.Abstract; using Lab.ChangeTracking.Domain; using Lab.ChangeTracking.Domain.Repository; using Lab.ChangeTracking.Infrastructure.DB; @@ -19,8 +20,8 @@ internal class TestAssistants public static IEmployeeRepository EmployeeRepository => _serviceProvider.GetService(); - public static IEmployeeAggregate EmployeeAggregate => - _serviceProvider.GetService(); + public static IEmployeeAggregate EmployeeAggregate => + _serviceProvider.GetService>(); static TestAssistants() { @@ -34,7 +35,7 @@ public static void ConfigureTestServices(IServiceCollection services) services.AddAppEnvironment(); services.AddEntityFramework(); - // services.AddSingleton(); + services.AddSingleton(); // services.AddSingleton(); _serviceProvider = services.BuildServiceProvider(); } @@ -45,4 +46,11 @@ public static void SetTestEnvironmentVariable() option.EmployeeDbConnectionString = "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True"; } + public static Guid Parse(string id) + { + var guidFormat = "{0}-0000-0000-0000-000000000000"; + var guidText = string.Format(guidFormat, id.PadRight(8, '0')); + var key = Guid.Parse(guidText); + return key; + } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AggregationRoot.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AggregationRoot.cs index 07fc92db..6dc74137 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AggregationRoot.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AggregationRoot.cs @@ -2,9 +2,9 @@ namespace Lab.ChangeTracking.Domain; -public abstract class AggregationRoot where T : IChangeTrackable +public abstract class AggregationRoot : IAggregationRoot where T : IChangeTrackable { - public EntityState State { get; private set; } + public EntityState State { get; protected set; } /// /// 建立時間 @@ -51,25 +51,35 @@ public int Version init => this._instance.Version = value; } - private readonly IList> _changes = new List>(); + private readonly IList> _changeActions = new List>(); protected readonly Dictionary ChangedProperties = new(); protected readonly Dictionary OriginalValues = new(); protected T _instance; - public AggregationRoot(T instance) + public IReadOnlyList> GetChangeActions() { - this._instance = instance; + return this._changeActions.ToList(); } - public IReadOnlyList> GetInstanceChanges() + public void SetInstance(T instance) { - return this._changes.ToList(); + this.State = EntityState.Unchanged; + this._instance = instance; } public T GetInstance() { return this._instance; + + // return new T + // { + // Version = _instance.Version, + // CreatedAt = _instance.CreatedAt, + // CreatedBy = this._instance.CreatedBy, + // UpdatedAt = this.UpdatedAt, + // UpdatedBy = this.CreatedBy + // }; } /// @@ -94,17 +104,17 @@ public T GetInstance() if (this.State == EntityState.Added) { - this.Track(x => x.CreatedAt = when); - this.Track(x => x.CreatedBy = who); - this.Track(x => x.UpdatedAt = when); - this.Track(x => x.UpdatedBy = who); - this.Track(x => x.Version += 1); + this.ChangeTrack(x => x.CreatedAt = when); + this.ChangeTrack(x => x.CreatedBy = who); + this.ChangeTrack(x => x.UpdatedAt = when); + this.ChangeTrack(x => x.UpdatedBy = who); + this.ChangeTrack(x => x.Version += 1); } else { - this.Track(x => x.UpdatedAt = when); - this.Track(x => x.UpdatedBy = who); - this.Track(x => x.Version += 1); + this.ChangeTrack(x => x.UpdatedAt = when); + this.ChangeTrack(x => x.UpdatedBy = who); + this.ChangeTrack(x => x.Version += 1); } this.State = EntityState.Submitted; @@ -112,19 +122,24 @@ public T GetInstance() return (null, true); } - protected void Track(Action change) + public void ChangeTrack(Action changeAction) { if (this.State == EntityState.Submitted) { throw new Exception("已經 Submitted 的 Doamin,無法再進行修改。"); } - change(this._instance); - this._changes.Add(change); + changeAction(this._instance); + this._changeActions.Add(changeAction); if (this.State == EntityState.Unchanged) { this.State = EntityState.Modified; } } + + private T Clone(T source) + { + return (T)Activator.CreateInstance(typeof(T)); + } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs index a526348e..18a2e0a4 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs @@ -1,19 +1,82 @@ +using Lab.ChangeTracking.Abstract; using Lab.ChangeTracking.Domain.Entity; +using Lab.ChangeTracking.Domain.Repository; namespace Lab.ChangeTracking.Domain; -public class EmployeeAggregate : AggregationRoot +// public class EmployeeAggregate : AggregationRoot, +// IEmployeeAggregate +public class EmployeeAggregate : AggregationRoot { - public EmployeeAggregate(EmployeeEntity instance) : base(instance) + public Guid Id => this._instance.Id; + + public string Name => this._instance.Name; + + public int? Age => this._instance.Age; + + public string Remark => this._instance.Remark; + + private readonly IEmployeeRepository _repository; + private readonly ISystemClock _systemClock; + private readonly IUUIdProvider _uuIdProvider; + + private string _name; + + public EmployeeAggregate(IEmployeeRepository repository, + IUUIdProvider uuIdProvider, + ISystemClock systemClock) + { + this._repository = repository; + this._uuIdProvider = uuIdProvider; + this._systemClock = systemClock; + } + + public void CreateInstance(string name, int age, string remark = null) + { + var now = this._systemClock.Now; + var instance = new EmployeeEntity + { + Id = this._uuIdProvider.GenerateId(), + Name = name, + Age = age, + Version = 1, + Remark = remark, + Identity = null + }; + this.State = EntityState.Added; + this._instance = instance; + } + + public async Task GetAsync(Guid id, CancellationToken cancel = default) + { + this._instance = await this._repository.GetAsync(id, cancel); + + this.State = EntityState.Unchanged; + } + + public async Task SaveChangeAsync(CancellationToken cancel = default) + { + return await this._repository.SaveChangeAsync(this, cancel); + } + + public EmployeeAggregate SetAge(int age) { + var instance = this._instance; + if (instance.Age != age) + { + this.ChangeTrack(p => p.Age = age); + } + + return this; } + // public IEmployeeAggregate SetName(string name) public EmployeeAggregate SetName(string name) { - var instance = this.GetInstance(); + var instance = this._instance; if (instance.Name != name) { - this.Track(p => p.Name = name); + this.ChangeTrack(p => p.Name = name); } return this; diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs index c182ab19..197bb5c8 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs @@ -2,7 +2,7 @@ namespace Lab.ChangeTracking.Domain.Entity; -public class EmployeeEntity :IEmployeeEntity +public record EmployeeEntity : IEmployeeEntity { public DateTimeOffset CreatedAt { get; set; } diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs index afe51a7e..986572dc 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs @@ -1,8 +1,13 @@ -using Lab.ChangeTracking.Domain.Entity; +using Lab.ChangeTracking.Abstract; +using Lab.ChangeTracking.Domain.Entity; namespace Lab.ChangeTracking.Domain; -public interface IEmployeeAggregate +public interface IEmployeeAggregate : IAggregationRoot where T : IChangeTrackable { - Task ModifyFlowAsync(EmployeeEntity employee, CancellationToken cancel = default); + IEmployeeAggregate SetName(string name); + + IEmployeeAggregate SetAge(int age); + + void SaveChangeAsync(CancellationToken cancel = default); } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs index 2594db6d..c42e8103 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs @@ -1,120 +1,99 @@ -// using ChangeTracking; -// using EFCore.BulkExtensions; -// using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; -// using Lab.ChangeTracking.Infrastructure.DB.EntityModel; -// using Microsoft.EntityFrameworkCore; -// -// namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Repository; -// -// public class EmployeeRepository : IEmployeeRepository -// { -// private readonly IDbContextFactory _memberContextFactory; -// -// public EmployeeRepository(IDbContextFactory memberContextFactory) -// { -// this._memberContextFactory = memberContextFactory; -// } -// -// public Employee To(EmployeeEntity srcEmployee) -// { -// return new Employee -// { -// Id = srcEmployee.Id, -// Name = srcEmployee.Name, -// Age = srcEmployee.Age, -// Remark = srcEmployee.Remark, -// CreateAt = srcEmployee.CreateAt, -// CreateBy = srcEmployee.CreateBy, -// Identity = this.To(srcEmployee.Identity) -// }; -// } -// -// public Identity To(IdentityEntity srcIdentity) -// { -// return new Identity -// { -// Password = srcIdentity.Password, -// Remark = srcIdentity.Remark, -// CreateAt = srcIdentity.CreateAt, -// CreateBy = srcIdentity.CreateBy, -// }; -// } -// -// public async Task SaveChangeAsync(EmployeeEntity srcEmployee, -// CancellationToken cancel = default) -// { -// var employeeTrackable = srcEmployee.CastToIChangeTrackable(); -// var identityTrackable = srcEmployee.Identity.CastToIChangeTrackable(); -// var memberChangeProperties = employeeTrackable.ChangedProperties.ToList(); -// var identityChangeProperties = identityTrackable.ChangedProperties.ToList(); -// -// await using var dbContext = await this._memberContextFactory.CreateDbContextAsync(cancel); -// await using var transaction = await dbContext.Database.BeginTransactionAsync(cancel); -// -// try -// { -// var destEmployee = this.To(srcEmployee); -// var memberChangeCount = await dbContext.Employees -// .Where(a => a.Id == srcEmployee.Id) -// .BatchUpdateAsync(destEmployee, -// memberChangeProperties, cancel); -// var identityChangeCount = await dbContext.Identities -// .Where(a => a.Employee_Id == srcEmployee.Id) -// .BatchUpdateAsync(destEmployee.Identity, -// identityChangeProperties, -// cancel); -// -// await transaction.CommitAsync(cancel); -// return memberChangeCount + identityChangeCount; -// } -// catch (Exception e) -// { -// await transaction.RollbackAsync(cancel); -// throw new Exception("存檔失敗"); -// } -// -// return 0; -// } -// -// public async Task SaveChange1Async(EmployeeEntity srcEmployee, -// CancellationToken cancel = default) -// { -// var employeeTrackable = srcEmployee.CastToIChangeTrackable(); -// var identityTrackable = srcEmployee.Identity.CastToIChangeTrackable(); -// var profileTrackable = srcEmployee.Profiles.CastToIChangeTrackableCollection(); -// -// var memberChangeProperties = employeeTrackable.ChangedProperties.ToList(); -// var identityChangeProperties = identityTrackable.ChangedProperties.ToList(); -// var changedItems = profileTrackable.ChangedItems; -// var addedItems = profileTrackable.AddedItems; -// var unchangedItems = profileTrackable.UnchangedItems; -// var deletedItems = profileTrackable.DeletedItems; -// -// await using var dbContext = await this._memberContextFactory.CreateDbContextAsync(cancel); -// await using var transaction = await dbContext.Database.BeginTransactionAsync(cancel); -// -// try -// { -// var destEmployee = this.To(srcEmployee); -// var memberChangeCount = await dbContext.Employees -// .Where(a => a.Id == srcEmployee.Id) -// .BatchUpdateAsync(destEmployee, -// memberChangeProperties, cancel); -// var identityChangeCount = await dbContext.Identities -// .Where(a => a.Employee_Id == srcEmployee.Id) -// .BatchUpdateAsync(destEmployee.Identity, -// identityChangeProperties, -// cancel); -// -// await transaction.CommitAsync(cancel); -// return memberChangeCount + identityChangeCount; -// } -// catch (Exception e) -// { -// await transaction.RollbackAsync(cancel); -// throw new Exception("存檔失敗"); -// } -// -// return 0; -// } -// } \ No newline at end of file +using ChangeTracking; +using EFCore.BulkExtensions; +using Lab.ChangeTracking.Abstract; +using Lab.ChangeTracking.Domain.Entity; +using Lab.ChangeTracking.Domain.Repository; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; + +namespace Lab.ChangeTracking.Domain.Repository; + +public class EmployeeRepository : IEmployeeRepository +{ + private readonly IDbContextFactory _employeeContextFactory; + + public EmployeeRepository(IDbContextFactory employeeContextFactory) + { + this._employeeContextFactory = employeeContextFactory; + } + + public Employee To(EmployeeEntity srcEmployee) + { + return new Employee + { + Id = srcEmployee.Id, + Name = srcEmployee.Name, + Age = srcEmployee.Age, + Remark = srcEmployee.Remark, + CreatedAt = srcEmployee.CreatedAt, + CreatedBy = srcEmployee.CreatedBy, + Identity = this.To(srcEmployee.Identity) + }; + } + + public Identity To(IdentityEntity srcIdentity) + { + return new Identity + { + Password = srcIdentity.Password, + Remark = srcIdentity.Remark, + CreatedAt = srcIdentity.CreateAt, + CreatedBy = srcIdentity.CreateBy, + }; + } + + public async Task SaveChangeAsync(IEmployeeAggregate srcEmployee, + CancellationToken cancel = default) + { + // var employeeEntity = srcEmployee.GetInstance(); + var employee = new Employee(); + foreach (var change in srcEmployee.GetChangeActions()) + { + change(employee); + } + + await using var dbContext = await this._employeeContextFactory.CreateDbContextAsync(cancel); + return 1; + } + + public async Task SaveChangeAsync(EmployeeAggregate srcEmployee, CancellationToken cancel = default) + { + await using var dbContext = await this._employeeContextFactory.CreateDbContextAsync(cancel); + + if (srcEmployee.State != EntityState.Submitted) + { + throw new Exception($"尚未 {nameof(EntityState.Submitted)},不能存檔"); + } + + var employeeFromDb = await dbContext.Employees + .FirstOrDefaultAsync(x => x.Id == srcEmployee.Id, cancel); + if (employeeFromDb == null) + { + var toDb = srcEmployee.GetInstance().ToDataEntity(); + dbContext.Add(toDb); + } + else + { + foreach (var changeAction in srcEmployee.GetChangeActions()) + { + changeAction(employeeFromDb); + } + } + + return await dbContext.SaveChangesAsync(cancel); + } + + public Task SaveChangeAsync(IEmployeeEntity employee, CancellationToken cancel = default) + { + throw new NotImplementedException(); + } + + public async Task GetAsync(Guid id, CancellationToken cancel = default) + { + await using var dbContext = await this._employeeContextFactory.CreateDbContextAsync(cancel); + return await dbContext.Employees + .Where(p => p.Id == id) + .AsNoTracking() + .FirstOrDefaultAsync(cancel); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs index 0564926a..4a2ae1fc 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs @@ -1,8 +1,13 @@ -using Lab.ChangeTracking.Domain.Entity; +using Lab.ChangeTracking.Abstract; +using Lab.ChangeTracking.Domain.Entity; namespace Lab.ChangeTracking.Domain.Repository; public interface IEmployeeRepository { - Task SaveChangeAsync(EmployeeEntity employee, CancellationToken cancel = default); + // Task SaveChangeAsync(IEmployeeAggregate employee, CancellationToken cancel = default); + Task SaveChangeAsync(EmployeeAggregate employee, CancellationToken cancel = default); + Task SaveChangeAsync(IEmployeeEntity employee, CancellationToken cancel = default); + + Task GetAsync(Guid id, CancellationToken cancel); } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeRepositoryExtensions.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeRepositoryExtensions.cs new file mode 100644 index 00000000..9f7162be --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeRepositoryExtensions.cs @@ -0,0 +1,22 @@ +using Lab.ChangeTracking.Abstract; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; + +namespace Lab.ChangeTracking.Domain.Repository; + +static class EmployeeRepositoryExtensions +{ + public static Employee ToDataEntity(this IEmployeeEntity srcEmployee) + { + return new Employee + { + Id = srcEmployee.Id, + Name = srcEmployee.Name, + Age = srcEmployee.Age, + Remark = srcEmployee.Remark, + CreatedAt = srcEmployee.CreatedAt, + CreatedBy = srcEmployee.CreatedBy, + // Identity = this.To(srcEmployee.Identity) + }; + } + +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EntityState.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EntityState.cs index 7b4c0e5c..7b9757ca 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EntityState.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EntityState.cs @@ -2,8 +2,8 @@ namespace Lab.ChangeTracking.Domain; public enum EntityState { - Added, - Unchanged, - Modified, - Submitted + Added = 0, + Modified = 1, + Submitted = 2, + Unchanged = 99, } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IAggregationRoot.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IAggregationRoot.cs new file mode 100644 index 00000000..6f41f399 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IAggregationRoot.cs @@ -0,0 +1,22 @@ +using Lab.ChangeTracking.Abstract; + +namespace Lab.ChangeTracking.Domain; + +public interface IAggregationRoot where T : IChangeTrackable +{ + IReadOnlyList> GetChangeActions(); + + void SetInstance(T instance); + + T GetInstance(); + + /// + /// SubmitChange 後則進版號 + /// + /// + /// + /// + (Error err, bool changed) SubmitChange(DateTimeOffset when, string who); + + void ChangeTrack(Action change); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/ISystemClock.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/ISystemClock.cs new file mode 100644 index 00000000..945faa76 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/ISystemClock.cs @@ -0,0 +1,11 @@ +namespace Lab.ChangeTracking.Domain; + +public interface ISystemClock +{ + DateTimeOffset Now { get; set; } +} + +public class SystemClock : ISystemClock +{ + public DateTimeOffset Now { get; set; }=DateTimeOffset.Now; +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IUUIDProvider.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IUUIDProvider.cs new file mode 100644 index 00000000..a58ce071 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IUUIDProvider.cs @@ -0,0 +1,14 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IUUIdProvider +{ + Guid GenerateId(); +} + +public class UUIdProvider : IUUIdProvider +{ + public Guid GenerateId() + { + return Guid.NewGuid(); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs index 80e699a6..03c02b92 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs @@ -19,24 +19,26 @@ public class EmployeeDbContext : DbContext public EmployeeDbContext(DbContextOptions options) : base(options) { - if (s_migrated[0]) - { - return; - } - - lock (s_migrated) - { - if (s_migrated[0] == false) - { - var sqlOptions = options.FindExtension(); - if (sqlOptions != null) - { - this.Database.Migrate(); - } - - s_migrated[0] = true; - } - } + // 改用 CI 執行 Migrate + + // if (s_migrated[0]) + // { + // return; + // } + // + // lock (s_migrated) + // { + // if (s_migrated[0] == false) + // { + // var sqlOptions = options.FindExtension(); + // if (sqlOptions != null) + // { + // this.Database.Migrate(); + // } + // + // s_migrated[0] = true; + // } + // } } protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -63,7 +65,7 @@ public void Configure(EntityTypeBuilder builder) } } - internal class IdentityConfiguration : IEntityTypeConfiguration + private class IdentityConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { @@ -85,5 +87,27 @@ public void Configure(EntityTypeBuilder builder) .IsClustered(); } } + + private class ProfileConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Profile"); + builder.HasKey(p => p.Id).IsClustered(false); + builder.HasOne(e => e.Employee) + .WithMany(p => p.Profiles) + .HasForeignKey(p => p.Employee_Id) + .OnDelete(DeleteBehavior.Cascade) ; + + builder.Property(p => p.FirstName).IsRequired(); + builder.Property(p => p.LastName).IsRequired(); + builder.Property(p => p.Remark).IsRequired(false); + builder.Property(p => p.UpdatedBy).IsRequired(false); + + builder.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + } + } } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Profile.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Profile.cs index 20ed9ff4..e5c4ebca 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Profile.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Profile.cs @@ -6,6 +6,10 @@ public class Profile : IProfileEntity { public Guid Id { get; set; } + public Guid Employee_Id { get; set; } + + public Employee Employee { get; set; } + public DateTimeOffset CreatedAt { get; set; } public string CreatedBy { get; set; } @@ -17,4 +21,8 @@ public class Profile : IProfileEntity public string FirstName { get; set; } public string LastName { get; set; } + + public string Remark { get; set; } + + public int SequenceId { get; set; } } \ No newline at end of file From 0573c8c944c1e4e6591864e9fb02f054137b5cc7 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 3 Mar 2022 08:20:10 +0800 Subject: [PATCH 166/424] refactor: add accessContext --- .../ChangeTrackingUnitTest.cs | 21 ++++++---- .../TestAssistants.cs | 4 +- .../AccessContext.cs | 8 ++++ .../AggregationRoot.cs | 37 ++++++---------- .../EmployeeAggregate/EmployeeAggregate.cs | 42 ++++++++----------- .../Entity/EmployeeEntity.cs | 2 +- .../Entity/IdentityEntity.cs | 2 +- .../EmployeeAggregate/Entity/ProfileEntity.cs | 2 +- .../EmployeeAggregate/IEmployeeAggregate.cs | 3 +- .../Repository/EmployeeRepository.cs | 16 +++---- .../Repository/IEmployeeRepository.cs | 3 +- .../EmployeeRepositoryExtensions.cs | 2 +- .../IAccessContext.cs | 8 ++++ .../IAggregationRoot.cs | 6 +-- 14 files changed, 79 insertions(+), 77 deletions(-) create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AccessContext.cs create mode 100644 Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IAccessContext.cs diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index 5b9e1835..15eb588f 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -5,6 +5,7 @@ using System.Text.Unicode; using System.Threading.Tasks; using Lab.ChangeTracking.Abstract; +using Lab.ChangeTracking.Domain.EmployeeAggregate; using Lab.ChangeTracking.Infrastructure.DB.EntityModel; using Lab.MultiTestCase.UnitTest; using Microsoft.EntityFrameworkCore; @@ -30,11 +31,15 @@ public async Task 新增() systemClock.Now.Returns(DateTimeOffset.Parse("2021-01-01")); var uuIdProvider = Substitute.For(); uuIdProvider.GenerateId().Returns(TestAssistants.Parse("1")); + var accessContext = Substitute.For(); + accessContext.AccessNow.Returns(DateTimeOffset.Parse("2021-01-02")); + accessContext.AccessUserId.Returns("System User"); // act - var employeeAggregate = new EmployeeAggregate(employeeRepository, uuIdProvider, systemClock); - employeeAggregate.CreateInstance("yao", 18, "Test User"); - employeeAggregate.SubmitChange(systemClock.Now, "Sys1"); + var employeeAggregate = + new EmployeeAggregate.EmployeeAggregate(employeeRepository, uuIdProvider, systemClock, accessContext); + employeeAggregate.Initial("yao", 18, "Test User"); + employeeAggregate.SubmitChange(); var result = await employeeAggregate.SaveChangeAsync(); // assert @@ -56,13 +61,15 @@ public async Task 編輯() systemClock.Now.Returns(DateTimeOffset.Parse("2021-01-02")); var uuIdProvider = Substitute.For(); uuIdProvider.GenerateId().Returns(TestAssistants.Parse("1")); + var accessContext = Substitute.For(); + accessContext.AccessNow.Returns(DateTimeOffset.Parse("2021-01-02")); // act - var employeeAggregate = new EmployeeAggregate(employeeRepository, uuIdProvider, systemClock); + var employeeAggregate = + new EmployeeAggregate.EmployeeAggregate(employeeRepository, uuIdProvider, systemClock, accessContext); await employeeAggregate.GetAsync(TestAssistants.Parse("1")); - employeeAggregate.SetName("小章"); - employeeAggregate.SetAge(20); - employeeAggregate.SubmitChange(systemClock.Now, "Sys1"); + employeeAggregate.SetName("小章").SetAge(20); + employeeAggregate.SubmitChange(); var count = await employeeAggregate.SaveChangeAsync(); // assert diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs index dd355049..1c502ed8 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs @@ -1,7 +1,8 @@ using System; using Lab.ChangeTracking.Abstract; using Lab.ChangeTracking.Domain; -using Lab.ChangeTracking.Domain.Repository; +using Lab.ChangeTracking.Domain.EmployeeAggregate; +using Lab.ChangeTracking.Domain.EmployeeAggregate.Repository; using Lab.ChangeTracking.Infrastructure.DB; using Lab.ChangeTracking.Infrastructure.DB.EntityModel; using Microsoft.EntityFrameworkCore; @@ -36,6 +37,7 @@ public static void ConfigureTestServices(IServiceCollection services) services.AddEntityFramework(); services.AddSingleton(); + services.AddSingleton(); // services.AddSingleton(); _serviceProvider = services.BuildServiceProvider(); } diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AccessContext.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AccessContext.cs new file mode 100644 index 00000000..fb42dfc6 --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AccessContext.cs @@ -0,0 +1,8 @@ +namespace Lab.ChangeTracking.Domain; + +public class AccessContext : IAccessContext +{ + public string AccessUserId { get; set; } + + public DateTimeOffset AccessNow { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AggregationRoot.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AggregationRoot.cs index 6dc74137..6e115ba5 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AggregationRoot.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/AggregationRoot.cs @@ -54,8 +54,10 @@ public int Version private readonly IList> _changeActions = new List>(); protected readonly Dictionary ChangedProperties = new(); protected readonly Dictionary OriginalValues = new(); - + protected IUUIdProvider _uuIdProvider; protected T _instance; + protected ISystemClock _systemClock; + protected IAccessContext _accessContext; public IReadOnlyList> GetChangeActions() { @@ -64,32 +66,17 @@ public IReadOnlyList> GetChangeActions() public void SetInstance(T instance) { - this.State = EntityState.Unchanged; this._instance = instance; - } - - public T GetInstance() - { - return this._instance; - - // return new T - // { - // Version = _instance.Version, - // CreatedAt = _instance.CreatedAt, - // CreatedBy = this._instance.CreatedBy, - // UpdatedAt = this.UpdatedAt, - // UpdatedBy = this.CreatedBy - // }; + this.State = EntityState.Unchanged; } /// /// SubmitChange 後則進版號 /// - /// - /// /// - public (Error err, bool changed) SubmitChange(DateTimeOffset when, string who) + public (Error err, bool changed) SubmitChange() { + var (now,accessUserId )= (this._accessContext.AccessNow,this._accessContext.AccessUserId); if (this.State == EntityState.Submitted) { return ( @@ -104,16 +91,16 @@ public T GetInstance() if (this.State == EntityState.Added) { - this.ChangeTrack(x => x.CreatedAt = when); - this.ChangeTrack(x => x.CreatedBy = who); - this.ChangeTrack(x => x.UpdatedAt = when); - this.ChangeTrack(x => x.UpdatedBy = who); + this.ChangeTrack(x => x.CreatedAt = now); + this.ChangeTrack(x => x.CreatedBy = accessUserId); + this.ChangeTrack(x => x.UpdatedAt = now); + this.ChangeTrack(x => x.UpdatedBy = accessUserId); this.ChangeTrack(x => x.Version += 1); } else { - this.ChangeTrack(x => x.UpdatedAt = when); - this.ChangeTrack(x => x.UpdatedBy = who); + this.ChangeTrack(x => x.UpdatedAt = now); + this.ChangeTrack(x => x.UpdatedBy = accessUserId); this.ChangeTrack(x => x.Version += 1); } diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs index 18a2e0a4..85d15e12 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs @@ -1,8 +1,8 @@ using Lab.ChangeTracking.Abstract; -using Lab.ChangeTracking.Domain.Entity; -using Lab.ChangeTracking.Domain.Repository; +using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; +using Lab.ChangeTracking.Domain.EmployeeAggregate.Repository; -namespace Lab.ChangeTracking.Domain; +namespace Lab.ChangeTracking.Domain.EmployeeAggregate; // public class EmployeeAggregate : AggregationRoot, // IEmployeeAggregate @@ -17,36 +17,18 @@ public class EmployeeAggregate : AggregationRoot public string Remark => this._instance.Remark; private readonly IEmployeeRepository _repository; - private readonly ISystemClock _systemClock; - private readonly IUUIdProvider _uuIdProvider; - - private string _name; public EmployeeAggregate(IEmployeeRepository repository, IUUIdProvider uuIdProvider, - ISystemClock systemClock) + ISystemClock systemClock, + IAccessContext accessContext) { this._repository = repository; this._uuIdProvider = uuIdProvider; + this._accessContext = accessContext; this._systemClock = systemClock; } - public void CreateInstance(string name, int age, string remark = null) - { - var now = this._systemClock.Now; - var instance = new EmployeeEntity - { - Id = this._uuIdProvider.GenerateId(), - Name = name, - Age = age, - Version = 1, - Remark = remark, - Identity = null - }; - this.State = EntityState.Added; - this._instance = instance; - } - public async Task GetAsync(Guid id, CancellationToken cancel = default) { this._instance = await this._repository.GetAsync(id, cancel); @@ -54,6 +36,18 @@ public async Task GetAsync(Guid id, CancellationToken cancel = default) this.State = EntityState.Unchanged; } + public void Initial(string name, int age, string remark = null) + { + this._instance = new EmployeeEntity(); + + this.ChangeTrack(p => p.Id = this._uuIdProvider.GenerateId()); + this.ChangeTrack(p => p.Age = age); + this.ChangeTrack(p => p.Name = name); + this.ChangeTrack(p => p.Version = 0); + this.ChangeTrack(p => p.Remark = remark); + this.State = EntityState.Added; + } + public async Task SaveChangeAsync(CancellationToken cancel = default) { return await this._repository.SaveChangeAsync(this, cancel); diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs index 197bb5c8..4b0f32ae 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs @@ -1,6 +1,6 @@ using Lab.ChangeTracking.Abstract; -namespace Lab.ChangeTracking.Domain.Entity; +namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; public record EmployeeEntity : IEmployeeEntity { diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs index 457beb06..09505d0f 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs @@ -1,4 +1,4 @@ -namespace Lab.ChangeTracking.Domain.Entity; +namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; public record IdentityEntity { diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs index 88550e4b..9ae35128 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs @@ -1,4 +1,4 @@ -namespace Lab.ChangeTracking.Domain.Entity; +namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; public record ProfileEntity { diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs index 986572dc..c86d7c76 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs @@ -1,7 +1,6 @@ using Lab.ChangeTracking.Abstract; -using Lab.ChangeTracking.Domain.Entity; -namespace Lab.ChangeTracking.Domain; +namespace Lab.ChangeTracking.Domain.EmployeeAggregate; public interface IEmployeeAggregate : IAggregationRoot where T : IChangeTrackable { diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs index c42e8103..29f3515a 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs @@ -1,12 +1,9 @@ -using ChangeTracking; -using EFCore.BulkExtensions; -using Lab.ChangeTracking.Abstract; -using Lab.ChangeTracking.Domain.Entity; -using Lab.ChangeTracking.Domain.Repository; +using Lab.ChangeTracking.Abstract; +using Lab.ChangeTracking.Domain.EmployeeAggregate.Entity; using Lab.ChangeTracking.Infrastructure.DB.EntityModel; using Microsoft.EntityFrameworkCore; -namespace Lab.ChangeTracking.Domain.Repository; +namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Repository; public class EmployeeRepository : IEmployeeRepository { @@ -69,7 +66,12 @@ public async Task SaveChangeAsync(EmployeeAggregate srcEmployee, Cancellati .FirstOrDefaultAsync(x => x.Id == srcEmployee.Id, cancel); if (employeeFromDb == null) { - var toDb = srcEmployee.GetInstance().ToDataEntity(); + var toDb = new Employee(); + foreach (var changeAction in srcEmployee.GetChangeActions()) + { + changeAction(toDb); + } + dbContext.Add(toDb); } else diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs index 4a2ae1fc..2ebb4645 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs @@ -1,7 +1,6 @@ using Lab.ChangeTracking.Abstract; -using Lab.ChangeTracking.Domain.Entity; -namespace Lab.ChangeTracking.Domain.Repository; +namespace Lab.ChangeTracking.Domain.EmployeeAggregate.Repository; public interface IEmployeeRepository { diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeRepositoryExtensions.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeRepositoryExtensions.cs index 9f7162be..3810dc0e 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeRepositoryExtensions.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/EmployeeRepositoryExtensions.cs @@ -1,7 +1,7 @@ using Lab.ChangeTracking.Abstract; using Lab.ChangeTracking.Infrastructure.DB.EntityModel; -namespace Lab.ChangeTracking.Domain.Repository; +namespace Lab.ChangeTracking.Domain; static class EmployeeRepositoryExtensions { diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IAccessContext.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IAccessContext.cs new file mode 100644 index 00000000..73dbed6c --- /dev/null +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IAccessContext.cs @@ -0,0 +1,8 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IAccessContext +{ + string AccessUserId { get; set; } + + DateTimeOffset AccessNow { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IAggregationRoot.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IAggregationRoot.cs index 6f41f399..2a5c4ade 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IAggregationRoot.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain/IAggregationRoot.cs @@ -8,15 +8,11 @@ public interface IAggregationRoot where T : IChangeTrackable void SetInstance(T instance); - T GetInstance(); - /// /// SubmitChange 後則進版號 /// - /// - /// /// - (Error err, bool changed) SubmitChange(DateTimeOffset when, string who); + (Error err, bool changed) SubmitChange(); void ChangeTrack(Action change); } \ No newline at end of file From e621dd242082358cbd43325f8458bad451559184 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 3 Mar 2022 09:01:20 +0800 Subject: [PATCH 167/424] add profile data --- .../ChangeTrackingUnitTest.cs | 65 +++++++++++++++++-- .../EntityModel/Employee.cs | 2 +- .../EntityModel/EmployeeDbContext.cs | 1 + .../EntityModel/Profile.cs | 4 +- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index 15eb588f..78689c66 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text.Encodings.Web; using System.Text.Json; @@ -79,26 +80,76 @@ public async Task 編輯() Assert.AreEqual(20, actual.Age); Assert.AreEqual(2, actual.Version); } + [TestMethod] + public async Task 編輯1() + { + // arrange + InsertTestData(); + await using var dbContext = await TestAssistants.EmployeeDbContextFactory.CreateDbContextAsync(); + + var employee = dbContext.Employees + .Include(p => p.Profiles) + .AsTracking() + // .Load() + .FirstOrDefault() + ; + var now = DateTimeOffset.Now; + var accessUserId = "TEST USER"; + var newProfile = new Profile + { + Id = Guid.NewGuid(), + Employee_Id = employee.Id, + CreatedAt = now, + CreatedBy = accessUserId, + UpdatedAt = now, + UpdatedBy = accessUserId, + FirstName = "first name", + LastName = "last name", + }; + employee.Profiles.Add(newProfile); + // dbContext.Profiles.Add(newProfile); + await dbContext.SaveChangesAsync(); + } private static Employee InsertTestData() { Console.WriteLine("新增資料"); using var dbContext = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + var employeeId = TestAssistants.Parse("1"); + var now = DateTimeOffset.Now; + var accessUserId = "TEST USER"; var toDB = new Employee { - Id = TestAssistants.Parse("1"), + Id = employeeId, Age = 18, Name = "yao", - CreatedAt = DateTimeOffset.Now, - CreatedBy = "TEST", - Identity = new Identity + CreatedAt = now, + CreatedBy = accessUserId, + Identity = new() { + Employee_Id = employeeId, Account = "yao", Password = "123456", - CreatedAt = DateTimeOffset.Now, - CreatedBy = "TEST", + CreatedAt = now, + CreatedBy = accessUserId, + UpdatedAt = now, + UpdatedBy = accessUserId, + }, + Version = 1, + Profiles = new List() + { + new() + { + Id = Guid.NewGuid(), + Employee_Id = employeeId, + CreatedAt = now, + CreatedBy = accessUserId, + UpdatedAt = now, + UpdatedBy = accessUserId, + FirstName = "yao", + LastName = "yu", + } }, - Version = 1 }; dbContext.Employees.Add(toDB); dbContext.SaveChanges(); diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs index 42cac8ca..49094f01 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs @@ -15,7 +15,7 @@ public class Employee : IEmployeeEntity public string Remark { get; set; } - public IList Profiles { get; set; } + public List Profiles { get; set; } = new(); public Identity Identity { get; set; } diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs index 03c02b92..03ec4140 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs @@ -45,6 +45,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfiguration(new EmployeeConfiguration()); modelBuilder.ApplyConfiguration(new IdentityConfiguration()); + modelBuilder.ApplyConfiguration(new ProfileConfiguration()); } internal class EmployeeConfiguration : IEntityTypeConfiguration diff --git a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Profile.cs b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Profile.cs index e5c4ebca..140c8f05 100644 --- a/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Profile.cs +++ b/Property Change Tracking/ChangeTrackingForORM/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Profile.cs @@ -1,4 +1,5 @@ -using Lab.ChangeTracking.Abstract; +using System.ComponentModel.DataAnnotations.Schema; +using Lab.ChangeTracking.Abstract; namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel; @@ -24,5 +25,6 @@ public class Profile : IProfileEntity public string Remark { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int SequenceId { get; set; } } \ No newline at end of file From 3ca9bd0ff24386404e357a743874577b2a49ec79 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 5 Mar 2022 17:16:02 +0800 Subject: [PATCH 168/424] =?UTF-8?q?feat:=20=E5=AF=A6=E4=BD=9C=20change=20t?= =?UTF-8?q?rack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChangeTrackProperty.sln | 41 +++++ .../ChangeTrackProperty/Makefile | 4 + .../ChangeTrackProperty/docker-compose.yml | 10 ++ .../ChangeTrackingUnitTest.cs | 81 ++++++++++ .../Lab.ChangeTracking.Domain.UnitTest.csproj | 28 ++++ .../MsTestHook.cs | 31 ++++ .../TestAssistants.cs | 57 +++++++ .../AccessContext.cs | 14 ++ .../EmployeeAggregate/EmployeeAggregate.cs | 29 ++++ .../Entity/EmployeeEntity.cs | 55 +++++++ .../Entity/IdentityEntity.cs | 14 ++ .../EmployeeAggregate/Entity/ProfileEntity.cs | 8 + .../EmployeeAggregate/IEmployeeAggregate.cs | 6 + .../Repository/EmployeeRepository.cs | 46 ++++++ .../Repository/IEmployeeRepository.cs | 6 + .../Lab.ChangeTracking.Domain/EntityBase.cs | 144 ++++++++++++++++++ .../Lab.ChangeTracking.Domain/EntityState.cs | 9 ++ .../src/Lab.ChangeTracking.Domain/Error.cs | 3 + .../Lab.ChangeTracking.Domain/IChangeTime.cs | 32 ++++ .../Lab.ChangeTracking.Domain/IChangeable.cs | 6 + .../Lab.ChangeTracking.Domain/ISystemClock.cs | 14 ++ .../IUUIdProvider.cs | 14 ++ .../Lab.ChangeTracking.Domain.csproj | 16 ++ .../PropertyChangeTracker.cs | 48 ++++++ .../src/Lab.ChangeTracking.Domain/Survey.cs | 39 +++++ .../AppDependencyInjectionExtensions.cs | 46 ++++++ .../AppEnvironmentOption.cs | 32 ++++ .../EntityModel/Employee.cs | 30 ++++ .../EntityModel/EmployeeDbContext.cs | 76 +++++++++ .../EntityModel/Identity.cs | 32 ++++ .../EntityModel/OrderHistory.cs | 29 ++++ .../EnvironmentAssistant.cs | 15 ++ ...ab.ChangeTracking.Infrastructure.DB.csproj | 15 ++ 33 files changed, 1030 insertions(+) create mode 100644 Property Change Tracking/ChangeTrackProperty/ChangeTrackProperty.sln create mode 100644 Property Change Tracking/ChangeTrackProperty/Makefile create mode 100644 Property Change Tracking/ChangeTrackProperty/docker-compose.yml create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/AccessContext.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityState.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Error.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTime.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeable.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/ISystemClock.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IUUIdProvider.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/PropertyChangeTracker.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Survey.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/OrderHistory.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj diff --git a/Property Change Tracking/ChangeTrackProperty/ChangeTrackProperty.sln b/Property Change Tracking/ChangeTrackProperty/ChangeTrackProperty.sln new file mode 100644 index 00000000..1c868fe3 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/ChangeTrackProperty.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{1F776B7D-C182-4DC3-BA57-844657BD9AC0}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + Makefile = Makefile + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{483A9EB7-C4AF-4D68-80ED-49277985C760}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Infrastructure.DB", "src\Lab.ChangeTracking.Infrastructure.DB\Lab.ChangeTracking.Infrastructure.DB.csproj", "{A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Domain", "src\Lab.ChangeTracking.Domain\Lab.ChangeTracking.Domain.csproj", "{A1C827F2-683E-470C-A3DE-BD6DD2BE5198}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Domain.UnitTest", "src\Lab.ChangeTracking.Domain.UnitTest\Lab.ChangeTracking.Domain.UnitTest.csproj", "{7066EE2C-28A8-4408-B212-E582608AB967}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Release|Any CPU.Build.0 = Release|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Release|Any CPU.Build.0 = Release|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {7066EE2C-28A8-4408-B212-E582608AB967} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + EndGlobalSection +EndGlobal diff --git a/Property Change Tracking/ChangeTrackProperty/Makefile b/Property Change Tracking/ChangeTrackProperty/Makefile new file mode 100644 index 00000000..cf17f07f --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/Makefile @@ -0,0 +1,4 @@ +G1: + echo 'Hello World' +G2: + cmd \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/docker-compose.yml b/Property Change Tracking/ChangeTrackProperty/docker-compose.yml new file mode 100644 index 00000000..8ecb1567 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.8" + +services: + db-sql: + image: mcr.microsoft.com/mssql/server:2019-latest + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=pass@w0rd1~ + ports: + - 1433:1433 \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs new file mode 100644 index 00000000..8a7736ca --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -0,0 +1,81 @@ +using System; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Unicode; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Lab.MultiTestCase.UnitTest; +using Microsoft.EntityFrameworkCore; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Employee = Lab.ChangeTracking.Infrastructure.DB.EntityModel.Employee; + +namespace Lab.ChangeTracking.Domain.UnitTest; + +[TestClass] +public class ChangeTrackingUnitTest +{ + private readonly IEmployeeAggregate _employeeAggregate = TestAssistants.EmployeeAggregate; + + private readonly IDbContextFactory _employeeDbContextFactory = + TestAssistants.EmployeeDbContextFactory; + + private ISystemClock _systemClock = TestAssistants.SystemClock; + + public static IAccessContext _accessContext = TestAssistants.AccessContext; + + public static IUUIdProvider _uuIdProvider = TestAssistants.UUIdProvider; + + [TestMethod] + public void 追蹤() + { + var employeeEntity = new EmployeeEntity + { + Id = Guid.NewGuid(), + State = EntityState.Added, + Name = "yao", + Age = 19, + Remark = "remark" + }; + employeeEntity.InitialTrack(); + employeeEntity.SetProfile("小章", 20,"remark"); + employeeEntity.SetProfile("小章", 20,"remark"); + } + + [TestMethod] + public void 追蹤集合() + { + } + + private static Employee Insert() + { + using var dbContext = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + var toDB = new Employee + { + Id = Guid.NewGuid(), + Age = 18, + Name = "yao", + CreateAt = DateTimeOffset.Now, + CreateBy = "TEST", + Identity = new Identity + { + Account = "yao", + Password = "123456", + CreateAt = DateTimeOffset.Now, + CreateBy = "TEST" + } + }; + dbContext.Employees.Add(toDB); + dbContext.SaveChanges(); + return toDB; + } + + private static string ToJson(T instance) + { + var serialize = JsonSerializer.Serialize(instance, + new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.Create( + UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs) + }); + return serialize; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj new file mode 100644 index 00000000..fbfda1f2 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + + + + + + + + diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs new file mode 100644 index 00000000..ca75b47b --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs @@ -0,0 +1,31 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.MultiTestCase.UnitTest; + +[TestClass] +public class MsTestHook +{ + [AssemblyCleanup] + public static void Cleanup() + { + TestAssistants.SetTestEnvironmentVariable(); + var db = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + if (db.Database.CanConnect()) + { + db.Database.EnsureDeleted(); + } + } + + [AssemblyInitialize] + public static void Setup(TestContext context) + { + TestAssistants.SetTestEnvironmentVariable(); + var db = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + if (db.Database.CanConnect()) + { + db.Database.EnsureDeleted(); + } + + db.Database.EnsureCreated(); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs new file mode 100644 index 00000000..af3c81ad --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs @@ -0,0 +1,57 @@ +using System; +using Lab.ChangeTracking.Domain; +using Lab.ChangeTracking.Infrastructure.DB; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Lab.MultiTestCase.UnitTest; + +// assistant +internal class TestAssistants +{ + private static IServiceProvider _serviceProvider; + + public static IDbContextFactory EmployeeDbContextFactory => + _serviceProvider.GetService>(); + + public static IEmployeeRepository EmployeeRepository => + _serviceProvider.GetService(); + + public static IEmployeeAggregate EmployeeAggregate => + _serviceProvider.GetService(); + + public static ISystemClock SystemClock => + _serviceProvider.GetService(); + + public static IAccessContext AccessContext => + _serviceProvider.GetService(); + + public static IUUIdProvider UUIdProvider => + _serviceProvider.GetService(); + + static TestAssistants() + { + var services = new ServiceCollection(); + ConfigureTestServices(services); + SetTestEnvironmentVariable(); + } + + public static void ConfigureTestServices(IServiceCollection services) + { + services.AddAppEnvironment(); + services.AddEntityFramework(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + _serviceProvider = services.BuildServiceProvider(); + } + + public static void SetTestEnvironmentVariable() + { + var option = _serviceProvider.GetService(); + option.EmployeeDbConnectionString = + "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True"; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/AccessContext.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/AccessContext.cs new file mode 100644 index 00000000..85ba24fb --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/AccessContext.cs @@ -0,0 +1,14 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IAccessContext +{ + public string? GetUserId(); +} + +public class AccessContext : IAccessContext +{ + public string? GetUserId() + { + return "Sys"; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs new file mode 100644 index 00000000..231e5bbc --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs @@ -0,0 +1,29 @@ +namespace Lab.ChangeTracking.Domain; + +public class EmployeeAggregate +{ + private IEmployeeRepository _repository; + private IUUIdProvider _idProvider; + private ISystemClock _systemClock; + private IAccessContext _accessContext; + private EmployeeEntity _employeeEntity; + + public void Create() + { + } + public EmployeeAggregate(IEmployeeRepository repository, + IUUIdProvider idProvider, + ISystemClock systemClock, + IAccessContext accessContext) + { + this._repository = repository; + this._idProvider = idProvider; + this._systemClock = systemClock; + this._accessContext = accessContext; + } + + void Save() + { + + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs new file mode 100644 index 00000000..ebb87622 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs @@ -0,0 +1,55 @@ +namespace Lab.ChangeTracking.Domain; + +public record EmployeeEntity : EntityBase +{ + public string Name + { + get => this._name; + init => this._name = value; + } + + public int? Age + { + get => this._age; + init => this._age = value; + } + + public string Remark + { + get => this._remark; + init => this._remark = value; + } + + private IAccessContext _accessContext; + private int? _age; + + // public IList Profiles { get; private set; } = new(); + // + // public IdentityEntity Identity { get; private set; } = new(); + + private IUUIdProvider _idProvider; + private string _name; + private string _remark; + private ISystemClock _systemClock; + + public EmployeeEntity SetId(string name, int age, string remark = null) + { + this._name = name; + this._age = age; + this._remark = remark; + this.ChangeTrack(nameof(this.Name), name); + this.ChangeTrack(nameof(this.Age), age); + this.ChangeTrack(nameof(this.Remark), remark); + return this; + } + public EmployeeEntity SetProfile(string name, int age, string remark = null) + { + this._name = name; + this._age = age; + this._remark = remark; + this.ChangeTrack(nameof(this.Name), name); + this.ChangeTrack(nameof(this.Age), age); + this.ChangeTrack(nameof(this.Remark), remark); + return this; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs new file mode 100644 index 00000000..1435576f --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs @@ -0,0 +1,14 @@ +namespace Lab.ChangeTracking.Domain; + +public record IdentityEntity +{ + public virtual string Account { get; set; } + + public virtual string Password { get; set; } + + public virtual string Remark { get; set; } + + public virtual DateTimeOffset CreateAt { get; set; } + + public virtual string CreateBy { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs new file mode 100644 index 00000000..8ab5d414 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs @@ -0,0 +1,8 @@ +namespace Lab.ChangeTracking.Domain; + +public record ProfileEntity +{ + public virtual string FirstName { get; set; } + + public virtual string LastName { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs new file mode 100644 index 00000000..224c9362 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs @@ -0,0 +1,6 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IEmployeeAggregate +{ + Task ModifyFlowAsync(EmployeeEntity employee, CancellationToken cancel = default); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs new file mode 100644 index 00000000..97f22e66 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs @@ -0,0 +1,46 @@ +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; + +namespace Lab.ChangeTracking.Domain; + +public class EmployeeRepository : IEmployeeRepository +{ + private readonly IDbContextFactory _memberContextFactory; + + public EmployeeRepository(IDbContextFactory memberContextFactory) + { + this._memberContextFactory = memberContextFactory; + } + + public Infrastructure.DB.EntityModel.Employee To(EmployeeEntity srcEmployee) + { + return new Infrastructure.DB.EntityModel.Employee + { + // Id = srcEmployee.Id, + // Name = srcEmployee.Name, + // Age = srcEmployee.Age, + // Remark = srcEmployee.Remark, + // CreateAt = srcEmployee.CreatedAt + + // CreateBy = srcEmployee.CreatedBy, + // Identity = this.To(srcEmployee.Identity) + }; + } + + public Identity To(IdentityEntity srcIdentity) + { + return new Identity + { + Password = srcIdentity.Password, + Remark = srcIdentity.Remark, + CreateAt = srcIdentity.CreateAt, + CreateBy = srcIdentity.CreateBy + }; + } + + public async Task SaveChangeAsync(EmployeeEntity srcEmployee, + CancellationToken cancel = default) + { + throw new Exception(); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs new file mode 100644 index 00000000..8748817c --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs @@ -0,0 +1,6 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IEmployeeRepository +{ + Task SaveChangeAsync(EmployeeEntity employee, CancellationToken cancel = default); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs new file mode 100644 index 00000000..7e675b00 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs @@ -0,0 +1,144 @@ +namespace Lab.ChangeTracking.Domain; + +public record EntityBase : IChangeTrackable +{ + public Guid? Id + { + get => this._id; + init => this._id = value; + } + + private readonly Dictionary _changedProperties = new(); + private readonly Dictionary _originalValues = new(); + private DateTimeOffset? _createdAt; + private string? _createdBy; + private Guid? _id; + private DateTimeOffset? _modifiedAt; + private string? _modifiedBy; + private EntityState _state; + private int _version; + + public (Error err, bool changed) AcceptChanges(ISystemClock systemClock, + IAccessContext accessContext, + IUUIdProvider idProvider) + { + if (this.State == EntityState.Submitted) + { + return ( + new Error("STATE_CONFLICT", + $"Entity({this.State}) was submitted and should not submit again."), false); + } + + var (now, accessUserId) = (systemClock.GetNow(), accessContext.GetUserId()); + + if (this.State == EntityState.Unchanged) + { + return (null, false); + } + + if (this.State == EntityState.Added) + { + this._id = idProvider.GenerateId(); + this._createdAt = now; + this._createdBy = accessUserId; + this._version = 1; + } + else + { + this._version = this._version++; + } + + this._modifiedAt = now; + this._modifiedBy = accessUserId; + + this._state = EntityState.Submitted; + + return (null, true); + } + + public void InitialTrack() + { + this._state = EntityState.Added; + this._version = 1; + var properties = this.GetType().GetProperties(); + foreach (var property in properties) + { + this._originalValues.Add(property.Name, property.GetValue(this)); + } + } + + public Dictionary GetChangedProperties() + { + return this._changedProperties; + } + + public bool HasChanged { get; private set; } + + public EntityState State + { + get => this._state; + init => this._state = value; + } + + public int Version + { + get => this._version; + init => this._version = value; + } + + public Dictionary GetOriginalValues() + { + return this._originalValues; + } + + protected void ChangeTrack(string propertyName, object value) + { + if (this.State == EntityState.Submitted) + { + throw new Exception("已經 Submitted ,無法再進行修改。"); + } + + var changes = this._changedProperties; + var originals = this._originalValues; + if (changes.ContainsKey(propertyName) == false) + { + if (originals[propertyName] != value) + { + changes.Add(propertyName, value); + this._state = EntityState.Added; + } + } + else + { + if (originals[propertyName] != changes[propertyName]) + { + changes[propertyName] = value; + this._state = EntityState.Modified; + } + } + } + + public DateTimeOffset? CreatedAt + { + get => this._createdAt; + init => this._createdAt = value; + } + + public string? CreatedBy + { + get => this._createdBy; + init => this._createdBy = value; + } + + public DateTimeOffset? ModifiedAt + { + get => this._modifiedAt; + init => this._modifiedAt = value; + } + + public string? ModifiedBy + { + get => this._modifiedBy; + init => this._modifiedBy = value; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityState.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityState.cs new file mode 100644 index 00000000..2573f480 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityState.cs @@ -0,0 +1,9 @@ +namespace Lab.ChangeTracking.Domain; + +public enum EntityState +{ + Unchanged = 0, + Added = 1, + Modified = 2, + Submitted = 99, +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Error.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Error.cs new file mode 100644 index 00000000..aa8f1b7a --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Error.cs @@ -0,0 +1,3 @@ +namespace Lab.ChangeTracking.Domain; + +public record Error(T Code, object Message); \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTime.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTime.cs new file mode 100644 index 00000000..c16ef976 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTime.cs @@ -0,0 +1,32 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IChangeTime +{ + DateTimeOffset? CreatedAt { get; init; } + + string? CreatedBy { get; init; } + + DateTimeOffset? ModifiedAt { get; init; } + + string? ModifiedBy { get; init; } +} + +public interface IChangeState +{ + EntityState State { get; init; } + + int Version { get; init; } +} + +public interface IChangeTrackable : IChangeTime, IChangeState +{ + bool HasChanged { get; } + + Dictionary GetChangedProperties(); + + Dictionary GetOriginalValues(); + + // void SetTrackable(); + // + // void ChangeTrack(string propertyName, object value); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeable.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeable.cs new file mode 100644 index 00000000..2c5125f8 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeable.cs @@ -0,0 +1,6 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IChangeable:IChangeTrackable +{ + +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/ISystemClock.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/ISystemClock.cs new file mode 100644 index 00000000..50076e0d --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/ISystemClock.cs @@ -0,0 +1,14 @@ +namespace Lab.ChangeTracking.Domain; + +public interface ISystemClock +{ + DateTimeOffset GetNow(); +} + +public class SystemClock : ISystemClock +{ + public DateTimeOffset GetNow() + { + return DateTimeOffset.Now; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IUUIdProvider.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IUUIdProvider.cs new file mode 100644 index 00000000..a58ce071 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IUUIdProvider.cs @@ -0,0 +1,14 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IUUIdProvider +{ + Guid GenerateId(); +} + +public class UUIdProvider : IUUIdProvider +{ + public Guid GenerateId() + { + return Guid.NewGuid(); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj new file mode 100644 index 00000000..a7984ff8 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + + + + + + + + + + diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/PropertyChangeTracker.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/PropertyChangeTracker.cs new file mode 100644 index 00000000..26cfefd8 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/PropertyChangeTracker.cs @@ -0,0 +1,48 @@ +namespace Lab.ChangeTracking.Domain; + +public class PropertyChangeTracker +{ + private readonly Dictionary _changedProperties = new(); + private readonly Dictionary _originalValues = new(); + + public Dictionary GetChangedProperties() + { + return this._changedProperties; + } + + public Dictionary GetOriginalValues() + { + throw new NotImplementedException(); + } + + public void Initial() + { + var properties = this.GetType().GetProperties(); + foreach (var property in properties) + { + this._originalValues.Add(property.Name, property.GetValue(this)); + } + } + + public void Track(string propertyName, object value) + { + var changes = this._changedProperties; + var originals = this._originalValues; + if (changes.ContainsKey(propertyName) == false) + { + changes.Add(propertyName, value); + } + else + { + if (originals[propertyName] != changes[propertyName]) + { + changes[propertyName] = value; + } + + if (originals[propertyName] == changes[propertyName]) + { + changes.Remove(propertyName); + } + } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Survey.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Survey.cs new file mode 100644 index 00000000..9a0f3d26 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Survey.cs @@ -0,0 +1,39 @@ +using System.ComponentModel; +using System.Reflection; + +namespace Lab.ChangeTracking.Domain; + +public class Base : IRevertibleChangeTracking +{ + protected readonly Dictionary ChangedProperties = new(); + protected readonly Dictionary OriginalValues = new(); + + public void Initialize() + { + var properties = this.GetType().GetProperties(); + + // Save the current value of the properties to our dictionary. + foreach (var property in properties) + { + this.OriginalValues.Add(property.Name, property.GetValue(this)); + } + } + + public bool IsChanged { get; private set; } + + public void RejectChanges() + { + foreach (var property in this.ChangedProperties) + { + this.GetType().GetRuntimeProperty(property.Key).SetValue(this, property.Value); + } + + this.AcceptChanges(); + } + + public void AcceptChanges() + { + this.ChangedProperties.Clear(); + this.IsChanged = false; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs new file mode 100644 index 00000000..a76bc397 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs @@ -0,0 +1,46 @@ +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.ChangeTracking.Infrastructure.DB; + +public static class AppDependencyInjectionExtensions +{ + public static void AddAppEnvironment(this IServiceCollection services) + { + services.AddLogging(builder => { builder.AddConsole(); }); + services.AddSingleton(); + } + + public static void AddEntityFramework(this IServiceCollection services) + { + services.AddPooledDbContextFactory((provider, optionsBuilder) => + { + var option = provider.GetService(); + var connectionString = option.EmployeeDbConnectionString; + var loggerFactory = provider.GetService(); + optionsBuilder.UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + ; + }); + + // services.AddPooledDbContextFactory((provider, options) => + // { + // var option = provider.GetService(); + // var loggerFactory = provider.GetService(); + // options.UseNpgsql( + // option.MemberDbConnectionString, //只會呼叫一次 + // builder => + // builder.EnableRetryOnFailure( + // 10, + // TimeSpan.FromSeconds(30), + // new List { "57P01" })) + // + // // .UseLazyLoadingProxies() + // .UseSnakeCaseNamingConvention() + // .UseLoggerFactory(loggerFactory) + // ; + // }); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs new file mode 100644 index 00000000..d93a85cc --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs @@ -0,0 +1,32 @@ +namespace Lab.ChangeTracking.Infrastructure.DB; + +public class AppEnvironmentOption +{ + public string EmployeeDbConnectionString + { + get + { + if (string.IsNullOrWhiteSpace(this._employeeDbConnectionString)) + { + this._employeeDbConnectionString = + EnvironmentAssistant.GetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR); + } + + return this._employeeDbConnectionString; + } + set + { + this._employeeDbConnectionString = value; + Environment.SetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR, value); + } + } + + private readonly string EMPLOYEE_DB_CONN_STR = "EMPLOYEE_DB_CONNECTION_STR"; + + private string _employeeDbConnectionString; + + public void Initial() + { + var memberDbConnectionString = this.EmployeeDbConnectionString; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs new file mode 100644 index 00000000..2e37227a --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel; + +[Table("Employee")] +public class Employee +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Id { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + [Required] + public DateTimeOffset CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + public virtual Identity Identity { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs new file mode 100644 index 00000000..f859963d --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs @@ -0,0 +1,76 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel; + +public class EmployeeDbContext : DbContext +{ + private static readonly bool[] s_migrated = { false }; + + public virtual DbSet Employees { get; set; } + + public virtual DbSet Identities { get; set; } + + public virtual DbSet OrderHistories { get; set; } + + public EmployeeDbContext(DbContextOptions options) + : base(options) + { + if (s_migrated[0]) + { + return; + } + + lock (s_migrated) + { + if (s_migrated[0] == false) + { + var memoryOptions = options.FindExtension(); + + if (memoryOptions == null) + { + var sqlOptions = options.FindExtension(); + if (sqlOptions != null) + { + this.Database.Migrate(); + } + } + + s_migrated[0] = true; + } + } + } + + //管理索引 + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(p => + { + p.HasKey(e => e.Id) + .IsClustered(false); + + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + + p.Property(p => p.Remark) + .IsRequired(false) + ; + }); + + modelBuilder.Entity(p => + { + p.HasKey(e => e.Employee_Id) + .IsClustered(false); + + p.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + + p.Property(p => p.Remark) + .IsRequired(false) + ; + }); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs new file mode 100644 index 00000000..f2251c34 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel; + +[Table("Identity")] +public class Identity +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Employee_Id { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + [Required] + public DateTimeOffset CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + [ForeignKey("Employee_Id")] + public virtual Employee Employee { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/OrderHistory.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/OrderHistory.cs new file mode 100644 index 00000000..6632c4e0 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/OrderHistory.cs @@ -0,0 +1,29 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel; + +[Table("OrderHistory")] +public class OrderHistory +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + public Guid? Employee_Id { get; set; } + + public string Remark { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Product_Id { get; set; } + + public string Product_Name { get; set; } + + [Required] + public DateTime CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs new file mode 100644 index 00000000..b115d8a2 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs @@ -0,0 +1,15 @@ +namespace Lab.ChangeTracking.Infrastructure.DB; + +public class EnvironmentAssistant +{ + public static string GetEnvironmentVariable(string key) + { + var result = Environment.GetEnvironmentVariable(key); + if (string.IsNullOrWhiteSpace(result)) + { + throw new Exception($"the key '{key}' not exist in environment variable"); + } + + return result; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj new file mode 100644 index 00000000..b1b261bb --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + + + + + + + + + From 62be34a93aaaf5d4c7fa53c9eadabca424173838 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 6 Mar 2022 01:10:28 +0800 Subject: [PATCH 169/424] =?UTF-8?q?feat:=20=E7=8B=80=E6=85=8B=E8=BF=BD?= =?UTF-8?q?=E8=B9=A4=E5=A5=97=E7=94=A8=20EF=20Core?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChangeTracking2/ChangeTracking2.sln | 48 ++++++ .../ChangeTracking2/Makefile | 4 + .../ChangeTracking2/docker-compose.yml | 10 ++ .../Lab.ChangeTracking.Abstract/IEntity.cs | 6 + .../Lab.ChangeTracking.Abstract.csproj | 9 + .../ChangeTrackingUnitTest.cs | 154 ++++++++++++++++++ .../Lab.ChangeTracking.Domain.UnitTest.csproj | 28 ++++ .../MsTestHook.cs | 31 ++++ .../TestAssistants.cs | 46 ++++++ .../Employee/Aggregate/EmployeeAggregate.cs | 25 +++ .../Employee/Aggregate/IEmployeeAggregate.cs | 6 + .../Employee/Entity/AddressEntity.cs | 24 +++ .../Employee/Entity/EmployeeEntity.cs | 26 +++ .../Employee/Entity/IdentityEntity.cs | 20 +++ .../Employee/Repository/EmployeeRepository.cs | 30 ++++ .../Repository/IEmployeeRepository.cs | 6 + .../Employee/Repository/RepositoryBase.cs | 72 ++++++++ .../Repository/TypeConverterExtensions.cs | 138 ++++++++++++++++ .../Lab.ChangeTracking.Domain.csproj | 20 +++ .../AppDependencyInjectionExtensions.cs | 46 ++++++ .../AppEnvironmentOption.cs | 32 ++++ .../EntityModel/Address.cs | 30 ++++ .../EntityModel/Employee.cs | 35 ++++ .../EntityModel/EmployeeDbContext.cs | 120 ++++++++++++++ .../EntityModel/Identity.cs | 32 ++++ .../EnvironmentAssistant.cs | 15 ++ ...ab.ChangeTracking.Infrastructure.DB.csproj | 18 ++ 27 files changed, 1031 insertions(+) create mode 100644 Property Change Tracking/ChangeTracking2/ChangeTracking2.sln create mode 100644 Property Change Tracking/ChangeTracking2/Makefile create mode 100644 Property Change Tracking/ChangeTracking2/docker-compose.yml create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Abstract/IEntity.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Abstract/Lab.ChangeTracking.Abstract.csproj create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Aggregate/EmployeeAggregate.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Aggregate/IEmployeeAggregate.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/AddressEntity.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/EmployeeEntity.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/IdentityEntity.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/EmployeeRepository.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/IEmployeeRepository.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/TypeConverterExtensions.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs create mode 100644 Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj diff --git a/Property Change Tracking/ChangeTracking2/ChangeTracking2.sln b/Property Change Tracking/ChangeTracking2/ChangeTracking2.sln new file mode 100644 index 00000000..030aecc7 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/ChangeTracking2.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{1F776B7D-C182-4DC3-BA57-844657BD9AC0}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + Makefile = Makefile + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{483A9EB7-C4AF-4D68-80ED-49277985C760}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Infrastructure.DB", "src\Lab.ChangeTracking.Infrastructure.DB\Lab.ChangeTracking.Infrastructure.DB.csproj", "{A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Domain", "src\Lab.ChangeTracking.Domain\Lab.ChangeTracking.Domain.csproj", "{A1C827F2-683E-470C-A3DE-BD6DD2BE5198}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Domain.UnitTest", "src\Lab.ChangeTracking.Domain.UnitTest\Lab.ChangeTracking.Domain.UnitTest.csproj", "{7066EE2C-28A8-4408-B212-E582608AB967}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Abstract", "src\Lab.ChangeTracking.Abstract\Lab.ChangeTracking.Abstract.csproj", "{43C40083-77B5-4068-A707-1993D3B29410}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Release|Any CPU.Build.0 = Release|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Release|Any CPU.Build.0 = Release|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Release|Any CPU.Build.0 = Release|Any CPU + {43C40083-77B5-4068-A707-1993D3B29410}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43C40083-77B5-4068-A707-1993D3B29410}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43C40083-77B5-4068-A707-1993D3B29410}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43C40083-77B5-4068-A707-1993D3B29410}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {7066EE2C-28A8-4408-B212-E582608AB967} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {43C40083-77B5-4068-A707-1993D3B29410} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + EndGlobalSection +EndGlobal diff --git a/Property Change Tracking/ChangeTracking2/Makefile b/Property Change Tracking/ChangeTracking2/Makefile new file mode 100644 index 00000000..cf17f07f --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/Makefile @@ -0,0 +1,4 @@ +G1: + echo 'Hello World' +G2: + cmd \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/docker-compose.yml b/Property Change Tracking/ChangeTracking2/docker-compose.yml new file mode 100644 index 00000000..8ecb1567 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.8" + +services: + db-sql: + image: mcr.microsoft.com/mssql/server:2019-latest + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=pass@w0rd1~ + ports: + - 1433:1433 \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Abstract/IEntity.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Abstract/IEntity.cs new file mode 100644 index 00000000..a861cb96 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Abstract/IEntity.cs @@ -0,0 +1,6 @@ +namespace Lab.ChangeTracking.Abstract; + +public interface IEntity +{ + Guid Id { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Abstract/Lab.ChangeTracking.Abstract.csproj b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Abstract/Lab.ChangeTracking.Abstract.csproj new file mode 100644 index 00000000..eb2460e9 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Abstract/Lab.ChangeTracking.Abstract.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs new file mode 100644 index 00000000..ffac8aec --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Unicode; +using ChangeTracking; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.ChangeTracking.Domain.UnitTest; + +[TestClass] +public class ChangeTrackingUnitTest +{ + private readonly IEmployeeAggregate _employeeAggregate = TestAssistants.EmployeeAggregate; + + private readonly IDbContextFactory _employeeDbContextFactory = + TestAssistants.EmployeeDbContextFactory; + + private readonly IEmployeeRepository _employeeRepository = TestAssistants.EmployeeRepository; + + [TestMethod] + public void 異動追蹤後存檔() + { + var toDB = Insert().To(); + var trackable = toDB.AsTrackable(); + trackable.Age = 20; + trackable.Name = "小章"; + trackable.Identity.Remark = "我變了"; + trackable.Addresses[0].Remark = "我變了"; + trackable.Addresses.RemoveAt(1); + trackable.Addresses.Add(new AddressEntity() + { + Id = Guid.NewGuid(), + Employee_Id = toDB.Id, + CreatedAt = DateTimeOffset.Now, + CreatedBy = "sys", + Country = "Taipei", + Street = "Street", + Remark = "我新的" + }); + var employeeEntity = this._employeeRepository.SaveChangeAsync(trackable).Result; + } + + [TestMethod] + public void 異動追蹤後存檔_回傳不可變的物件() + { + var toDB = Insert(); + var source = new EmployeeEntity + { + Id = toDB.Id, + Name = "yao", + Age = 12, + Identity = new IdentityEntity + { + Employee_Id = toDB.Identity.Employee_Id + }, + Addresses = new List + { + new() + { + Id = toDB.Addresses[0] + .Id, + Employee_Id = toDB.Id, + Remark = "AAA" + }, + new() + { + Id = toDB.Addresses[1] + .Id, + Employee_Id = toDB.Id, + Remark = "AAA" + } + } + }; + var employeeEntity = this._employeeAggregate.ModifyFlowAsync(source).Result; + this.DataShouldOk(source); + } + + private void DataShouldOk(EmployeeEntity source) + { + var dbContext = this._employeeDbContextFactory.CreateDbContext(); + var actual = dbContext.Employees + .Where(p => p.Id == source.Id) + .Include(p => p.Identity) + .First() + ; + + Assert.AreEqual("小章", actual.Name); + Assert.AreEqual("9527", actual.Identity.Password); + } + + private static Employee Insert() + { + using var dbContext = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + var employeeId = Guid.NewGuid(); + var toDB = new Employee + { + Id = employeeId, + Age = 18, + Name = "yao", + CreatedAt = DateTimeOffset.Now, + CreatedBy = "Sys", + Identity = new Identity + { + Employee_Id = employeeId, + Account = "yao", + Password = "123456", + CreatedAt = DateTimeOffset.Now, + CreatedBy = "Sys", + Remark = "編輯" + }, + Addresses = new List
+ { + new() + { + Id = Guid.NewGuid(), + Employee_Id = employeeId, + CreatedAt = DateTimeOffset.Now, + CreatedBy = "sys", + Country = "Taipei", + Street = "Street", + Remark = "修改的" + }, + new() + { + Id = Guid.NewGuid(), + Employee_Id = employeeId, + CreatedAt = DateTimeOffset.Now, + CreatedBy = "sys", + Country = "Taipei", + Street = "Street", + Remark = "刪除的" + } + } + }; + dbContext.Employees.Add(toDB); + dbContext.SaveChanges(); + return toDB; + } + + private static string ToJson(T instance) + { + var serialize = JsonSerializer.Serialize(instance, + new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.Create( + UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs) + }); + return serialize; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj new file mode 100644 index 00000000..fbfda1f2 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + + + + + + + + diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs new file mode 100644 index 00000000..817754fe --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs @@ -0,0 +1,31 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.ChangeTracking.Domain.UnitTest; + +[TestClass] +public class MsTestHook +{ + [AssemblyCleanup] + public static void Cleanup() + { + TestAssistants.SetTestEnvironmentVariable(); + var db = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + if (db.Database.CanConnect()) + { + db.Database.EnsureDeleted(); + } + } + + [AssemblyInitialize] + public static void Setup(TestContext context) + { + TestAssistants.SetTestEnvironmentVariable(); + var db = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + if (db.Database.CanConnect()) + { + db.Database.EnsureDeleted(); + } + + db.Database.EnsureCreated(); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs new file mode 100644 index 00000000..d9741d2a --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs @@ -0,0 +1,46 @@ +using System; +using Lab.ChangeTracking.Infrastructure.DB; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Lab.ChangeTracking.Domain.UnitTest; + +// assistant +internal class TestAssistants +{ + private static IServiceProvider _serviceProvider; + + public static IDbContextFactory EmployeeDbContextFactory => + _serviceProvider.GetService>(); + + public static IEmployeeRepository EmployeeRepository => + _serviceProvider.GetService(); + + public static IEmployeeAggregate EmployeeAggregate => + _serviceProvider.GetService(); + + static TestAssistants() + { + var services = new ServiceCollection(); + ConfigureTestServices(services); + SetTestEnvironmentVariable(); + } + + public static void ConfigureTestServices(IServiceCollection services) + { + services.AddAppEnvironment(); + services.AddEntityFramework(); + + services.AddSingleton(); + services.AddSingleton(); + _serviceProvider = services.BuildServiceProvider(); + } + + public static void SetTestEnvironmentVariable() + { + var option = _serviceProvider.GetService(); + option.EmployeeDbConnectionString = + "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True"; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Aggregate/EmployeeAggregate.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Aggregate/EmployeeAggregate.cs new file mode 100644 index 00000000..875cfc6c --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Aggregate/EmployeeAggregate.cs @@ -0,0 +1,25 @@ +using ChangeTracking; + +namespace Lab.ChangeTracking.Domain; + +public class EmployeeAggregate : IEmployeeAggregate +{ + private readonly IEmployeeRepository _repository; + + public EmployeeAggregate(IEmployeeRepository repository) + { + this._repository = repository; + } + + public async Task ModifyFlowAsync(EmployeeEntity srcEmployee, CancellationToken cancel = default) + { + var memberTrackable = srcEmployee.AsTrackable(); + + memberTrackable.Remark = "我變了"; + memberTrackable.Identity.Remark = "我變了"; + memberTrackable.Addresses[0].Remark = "我變了"; + memberTrackable.Addresses.RemoveAt(1); + var changeCount = await this._repository.SaveChangeAsync(memberTrackable, cancel); + return memberTrackable; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Aggregate/IEmployeeAggregate.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Aggregate/IEmployeeAggregate.cs new file mode 100644 index 00000000..224c9362 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Aggregate/IEmployeeAggregate.cs @@ -0,0 +1,6 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IEmployeeAggregate +{ + Task ModifyFlowAsync(EmployeeEntity employee, CancellationToken cancel = default); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/AddressEntity.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/AddressEntity.cs new file mode 100644 index 00000000..67249ce5 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/AddressEntity.cs @@ -0,0 +1,24 @@ +using Lab.ChangeTracking.Abstract; + +namespace Lab.ChangeTracking.Domain; + +public record AddressEntity:IEntity +{ + public virtual Guid Id { get; set; } + + public virtual Guid Employee_Id { get; set; } + + public virtual string Country { get; set; } + + public virtual string Street { get; set; } + + public virtual DateTimeOffset CreatedAt { get; set; } + + public virtual string CreatedBy { get; set; } + + public virtual DateTimeOffset? ModifiedAt { get; set; } + + public virtual string ModifiedBy { get; set; } + + public virtual string Remark { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/EmployeeEntity.cs new file mode 100644 index 00000000..5bd37ec2 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/EmployeeEntity.cs @@ -0,0 +1,26 @@ +namespace Lab.ChangeTracking.Domain; + +public record EmployeeEntity +{ + public virtual Guid Id { get; set; } + + public virtual string Name { get; set; } + + public virtual int? Age { get; set; } + + public virtual int Version { get; set; } + + public virtual IdentityEntity Identity { get; set; } + + public virtual IList Addresses { get; set; } + + public virtual DateTimeOffset CreatedAt { get; set; } + + public virtual string CreatedBy { get; set; } + + public virtual DateTimeOffset? ModifiedAt { get; set; } + + public virtual string ModifiedBy { get; set; } + + public virtual string Remark { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/IdentityEntity.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/IdentityEntity.cs new file mode 100644 index 00000000..96092ab9 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/IdentityEntity.cs @@ -0,0 +1,20 @@ +namespace Lab.ChangeTracking.Domain; + +public record IdentityEntity +{ + public virtual Guid Employee_Id { get; set; } + + public virtual string Account { get; set; } + + public virtual string Password { get; set; } + + public virtual DateTimeOffset CreatedAt { get; set; } + + public virtual string CreatedBy { get; set; } + + public virtual DateTimeOffset? ModifiedAt { get; set; } + + public virtual string? ModifiedBy { get; set; } + + public virtual string Remark { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/EmployeeRepository.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/EmployeeRepository.cs new file mode 100644 index 00000000..712b70fc --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/EmployeeRepository.cs @@ -0,0 +1,30 @@ +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; + +namespace Lab.ChangeTracking.Domain; + +public class EmployeeRepository : RepositoryBase, IEmployeeRepository +{ + private readonly IDbContextFactory _memberContextFactory; + + public EmployeeRepository(IDbContextFactory memberContextFactory) + { + this._memberContextFactory = memberContextFactory; + } + + public async Task SaveChangeAsync(EmployeeEntity srcEmployee, + CancellationToken cancel = default) + { + await using var dbContext = await this._memberContextFactory.CreateDbContextAsync(cancel); + var destEmployee = srcEmployee.To(); + this.ApplyChange(dbContext, srcEmployee, destEmployee, new List + { + "Identity", + "Addresses" + } + ); + this.ApplyChange(dbContext, srcEmployee.Identity, destEmployee.Identity); + this.ApplyChanges(dbContext, srcEmployee.Addresses, destEmployee.Addresses); + return await dbContext.SaveChangesAsync(cancel); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/IEmployeeRepository.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/IEmployeeRepository.cs new file mode 100644 index 00000000..8748817c --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/IEmployeeRepository.cs @@ -0,0 +1,6 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IEmployeeRepository +{ + Task SaveChangeAsync(EmployeeEntity employee, CancellationToken cancel = default); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs new file mode 100644 index 00000000..bb60a73e --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs @@ -0,0 +1,72 @@ +using ChangeTracking; +using Lab.ChangeTracking.Abstract; +using Microsoft.EntityFrameworkCore; + +namespace Lab.ChangeTracking.Domain; + +public class RepositoryBase +{ + protected void ApplyChange(DbContext dbContext, + TSource source, + TTarget target, + IEnumerable excludeProperties = null) + where TSource : class + where TTarget : class + { + var sourceTrackable = source.CastToIChangeTrackable(); + var changedProperties = sourceTrackable.ChangedProperties; + dbContext.Set().Attach(target); + foreach (var property in changedProperties) + { + if (excludeProperties != null + && excludeProperties.Any(p => p == property)) + { + continue; + } + + dbContext.Entry(target).Property(property).IsModified = true; + } + } + + protected void ApplyChanges(DbContext dbContext, + IList sources, + IList targets, + IEnumerable excludeProperties = null) + where TSource : class, IEntity + where TTarget : class, IEntity + + { + var targetsTrackable = sources.CastToIChangeTrackableCollection(); + if (targetsTrackable == null) + { + return; + } + + var addedItems = targetsTrackable.AddedItems; + var deletedItems = targetsTrackable.DeletedItems; + foreach (var source in targetsTrackable.ChangedItems) + { + var target = targets.FirstOrDefault(p => p.Id == source.Id); + if (target == null) + { + continue; + } + + this.ApplyChange(dbContext, source, target, excludeProperties); + } + + foreach (var addedItem in addedItems) + { + + // dbContext.Add(addedItem as TTarget); + } + + foreach (var source in deletedItems) + { + var target = Activator.CreateInstance(); + target.Id = source.Id; + dbContext.Set().Attach(target); + dbContext.Entry(target).State = EntityState.Deleted; + } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/TypeConverterExtensions.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/TypeConverterExtensions.cs new file mode 100644 index 00000000..8e8ae5d7 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/TypeConverterExtensions.cs @@ -0,0 +1,138 @@ +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; + +namespace Lab.ChangeTracking.Domain; + +public static class TypeConverterExtensions +{ + public static Employee To(this EmployeeEntity srcEmployee) + { + if (srcEmployee == null) + { + return null; + } + + return new Employee + { + Id = srcEmployee.Id, + Name = srcEmployee.Name, + Age = srcEmployee.Age, + Version = srcEmployee.Version, + Remark = srcEmployee.Remark, + Addresses = srcEmployee.Addresses.To()?.ToList(), + Identity = srcEmployee.Identity.To(), + CreatedAt = srcEmployee.CreatedAt, + CreatedBy = srcEmployee.CreatedBy, + ModifiedAt = srcEmployee.ModifiedAt, + ModifiedBy = srcEmployee.ModifiedBy + }; + } + public static EmployeeEntity To(this Employee srcEmployee) + { + if (srcEmployee == null) + { + return null; + } + + return new EmployeeEntity + { + Id = srcEmployee.Id, + Name = srcEmployee.Name, + Age = srcEmployee.Age, + Version = srcEmployee.Version, + Remark = srcEmployee.Remark, + Addresses = srcEmployee.Addresses.To()?.ToList(), + Identity = srcEmployee.Identity.To(), + CreatedAt = srcEmployee.CreatedAt, + CreatedBy = srcEmployee.CreatedBy, + ModifiedAt = srcEmployee.ModifiedAt, + ModifiedBy = srcEmployee.ModifiedBy + }; + } + public static Identity To(this IdentityEntity srcIdentity) + { + if (srcIdentity == null) + { + return null; + } + + return new Identity + { + Employee_Id = srcIdentity.Employee_Id, + Account = srcIdentity.Account, + Password = srcIdentity.Password, + Remark = srcIdentity.Remark, + CreatedAt = srcIdentity.CreatedAt, + CreatedBy = srcIdentity.CreatedBy, + ModifiedAt = srcIdentity.ModifiedAt, + ModifiedBy = srcIdentity.ModifiedBy + }; + } + public static IdentityEntity To(this Identity srcIdentity) + { + if (srcIdentity == null) + { + return null; + } + + return new IdentityEntity + { + Employee_Id = srcIdentity.Employee_Id, + Account = srcIdentity.Account, + Password = srcIdentity.Password, + Remark = srcIdentity.Remark, + CreatedAt = srcIdentity.CreatedAt, + CreatedBy = srcIdentity.CreatedBy, + ModifiedAt = srcIdentity.ModifiedAt, + ModifiedBy = srcIdentity.ModifiedBy + }; + } + public static Address To(this AddressEntity srcAddress) + { + if (srcAddress == null) + { + return null; + } + + return new Address + { + Id = srcAddress.Id, + Employee_Id = srcAddress.Employee_Id, + Country = srcAddress.Country, + Street = srcAddress.Street, + CreatedAt = srcAddress.CreatedAt, + CreatedBy = srcAddress.CreatedBy, + ModifiedAt = srcAddress.ModifiedAt, + ModifiedBy = srcAddress.ModifiedBy, + Remark = srcAddress.Remark + }; + } + public static AddressEntity To(this Address srcAddress) + { + if (srcAddress == null) + { + return null; + } + + return new AddressEntity + { + Id = srcAddress.Id, + Employee_Id = srcAddress.Employee_Id, + Country = srcAddress.Country, + Street = srcAddress.Street, + CreatedAt = srcAddress.CreatedAt, + CreatedBy = srcAddress.CreatedBy, + ModifiedAt = srcAddress.ModifiedAt, + ModifiedBy = srcAddress.ModifiedBy, + Remark = srcAddress.Remark + }; + } + public static IEnumerable
To(this IEnumerable srcProfiles) + { + return srcProfiles?.Select(p => p?.To()); + } + + public static IEnumerable To(this IEnumerable
srcProfiles) + { + return srcProfiles?.Select(p => p?.To()); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj new file mode 100644 index 00000000..def7c560 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj @@ -0,0 +1,20 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs new file mode 100644 index 00000000..a76bc397 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs @@ -0,0 +1,46 @@ +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.ChangeTracking.Infrastructure.DB; + +public static class AppDependencyInjectionExtensions +{ + public static void AddAppEnvironment(this IServiceCollection services) + { + services.AddLogging(builder => { builder.AddConsole(); }); + services.AddSingleton(); + } + + public static void AddEntityFramework(this IServiceCollection services) + { + services.AddPooledDbContextFactory((provider, optionsBuilder) => + { + var option = provider.GetService(); + var connectionString = option.EmployeeDbConnectionString; + var loggerFactory = provider.GetService(); + optionsBuilder.UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + ; + }); + + // services.AddPooledDbContextFactory((provider, options) => + // { + // var option = provider.GetService(); + // var loggerFactory = provider.GetService(); + // options.UseNpgsql( + // option.MemberDbConnectionString, //只會呼叫一次 + // builder => + // builder.EnableRetryOnFailure( + // 10, + // TimeSpan.FromSeconds(30), + // new List { "57P01" })) + // + // // .UseLazyLoadingProxies() + // .UseSnakeCaseNamingConvention() + // .UseLoggerFactory(loggerFactory) + // ; + // }); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs new file mode 100644 index 00000000..d93a85cc --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs @@ -0,0 +1,32 @@ +namespace Lab.ChangeTracking.Infrastructure.DB; + +public class AppEnvironmentOption +{ + public string EmployeeDbConnectionString + { + get + { + if (string.IsNullOrWhiteSpace(this._employeeDbConnectionString)) + { + this._employeeDbConnectionString = + EnvironmentAssistant.GetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR); + } + + return this._employeeDbConnectionString; + } + set + { + this._employeeDbConnectionString = value; + Environment.SetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR, value); + } + } + + private readonly string EMPLOYEE_DB_CONN_STR = "EMPLOYEE_DB_CONNECTION_STR"; + + private string _employeeDbConnectionString; + + public void Initial() + { + var memberDbConnectionString = this.EmployeeDbConnectionString; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs new file mode 100644 index 00000000..1bbc95fc --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Lab.ChangeTracking.Abstract; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel; + +public class Address : IEntity +{ + public Guid Id { get; set; } + + public Guid Employee_Id { get; set; } + + public Employee Employee { get; set; } + + public string Country { get; set; } + + public string Street { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset? ModifiedAt { get; set; } + + public string ModifiedBy { get; set; } + + public string Remark { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int SequenceId { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs new file mode 100644 index 00000000..dd1e6721 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Lab.ChangeTracking.Abstract; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel +{ + public class Employee : IEntity + { + public Guid Id { get; set; } + + public int Version { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + public IList
Addresses { get; set; } + + public Identity Identity { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset? ModifiedAt { get; set; } + + public string ModifiedBy { get; set; } + + public string Remark { get; set; } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs new file mode 100644 index 00000000..d16a1f19 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs @@ -0,0 +1,120 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel +{ + public class EmployeeDbContext : DbContext + { + private static readonly bool[] s_migrated = { false }; + + public virtual DbSet Employees { get; set; } + + public virtual DbSet Identity { get; set; } + + public virtual DbSet
Addresses { get; set; } + + public EmployeeDbContext(DbContextOptions options) + : base(options) + { + // 改用 CI 執行 Migrate + + // if (s_migrated[0]) + // { + // return; + // } + // + // lock (s_migrated) + // { + // if (s_migrated[0] == false) + // { + // var sqlOptions = options.FindExtension(); + // if (sqlOptions != null) + // { + // this.Database.Migrate(); + // } + // + // s_migrated[0] = true; + // } + // } + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new EmployeeConfiguration()); + modelBuilder.ApplyConfiguration(new IdentityConfiguration()); + modelBuilder.ApplyConfiguration(new ProfileConfiguration()); + } + + internal class EmployeeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Employee"); + builder.HasKey(p => p.Id) + .IsClustered(false); + + builder.Property(p => p.Name).IsRequired(); + builder.Property(p => p.CreatedBy).IsRequired(); + builder.Property(p => p.CreatedAt).IsRequired(); + builder.Property(p => p.ModifiedAt).IsRequired(false); + builder.Property(p => p.ModifiedBy).IsRequired(false); + builder.Property(p => p.Remark).IsRequired(false); + + builder.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + } + } + + private class IdentityConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Identity"); + builder.HasKey(p => p.Employee_Id).IsClustered(false); + builder.HasOne(e => e.Employee) + .WithOne(p => p.Identity) + .HasForeignKey(p => p.Employee_Id) + .OnDelete(DeleteBehavior.Cascade) + ; + + builder.Property(p => p.Account).IsRequired(); + builder.Property(p => p.Password).IsRequired(); + builder.Property(p => p.CreatedBy).IsRequired(); + builder.Property(p => p.CreatedAt).IsRequired(); + builder.Property(p => p.ModifiedAt).IsRequired(false); + builder.Property(p => p.ModifiedBy).IsRequired(false); + builder.Property(p => p.Remark).IsRequired(false); + + builder.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + } + } + + private class ProfileConfiguration : IEntityTypeConfiguration
+ { + public void Configure(EntityTypeBuilder
builder) + { + builder.ToTable("Profile"); + builder.HasKey(p => p.Id).IsClustered(false); + builder.HasOne(e => e.Employee) + .WithMany(p => p.Addresses) + .HasForeignKey(p => p.Employee_Id) + .OnDelete(DeleteBehavior.Cascade); + + builder.Property(p => p.Country).IsRequired(); + builder.Property(p => p.Street).IsRequired(); + builder.Property(p => p.CreatedBy).IsRequired(); + builder.Property(p => p.CreatedAt).IsRequired(); + builder.Property(p => p.ModifiedBy).IsRequired(false); + builder.Property(p => p.ModifiedAt).IsRequired(false); + builder.Property(p => p.Remark).IsRequired(false); + + builder.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + } + } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs new file mode 100644 index 00000000..e9e2f6d9 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel +{ + public class Identity + { + public Guid Employee_Id { get; set; } + + // [ForeignKey("Employee_Id")] + public virtual Employee Employee { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset? ModifiedAt { get; set; } + + public string? ModifiedBy { get; set; } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs new file mode 100644 index 00000000..b115d8a2 --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs @@ -0,0 +1,15 @@ +namespace Lab.ChangeTracking.Infrastructure.DB; + +public class EnvironmentAssistant +{ + public static string GetEnvironmentVariable(string key) + { + var result = Environment.GetEnvironmentVariable(key); + if (string.IsNullOrWhiteSpace(result)) + { + throw new Exception($"the key '{key}' not exist in environment variable"); + } + + return result; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj new file mode 100644 index 00000000..d944728b --- /dev/null +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + From 246e2239508be19b6a39e73fe992e6b5ad0a12b7 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 6 Mar 2022 11:22:49 +0800 Subject: [PATCH 170/424] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E7=84=A1?= =?UTF-8?q?=E6=B3=95=E5=AD=98=E6=AA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChangeTrackingUnitTest.cs | 14 ++--- .../Employee/Entity/AddressEntity.cs | 2 +- .../Employee/Entity/EmployeeEntity.cs | 6 ++- .../Employee/Entity/IdentityEntity.cs | 7 ++- .../Employee/Repository/RepositoryBase.cs | 51 ++++++++++++++++--- .../EntityModel/EmployeeDbContext.cs | 6 +-- 6 files changed, 65 insertions(+), 21 deletions(-) diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index ffac8aec..bd02c7dd 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -24,24 +24,26 @@ public class ChangeTrackingUnitTest [TestMethod] public void 異動追蹤後存檔() { - var toDB = Insert().To(); - var trackable = toDB.AsTrackable(); + var employeeEntity = Insert().To(); + var trackable = employeeEntity.AsTrackable(); trackable.Age = 20; trackable.Name = "小章"; + trackable.Remark = "我變了"; trackable.Identity.Remark = "我變了"; trackable.Addresses[0].Remark = "我變了"; + trackable.Addresses[1].Remark = "我掉了"; trackable.Addresses.RemoveAt(1); trackable.Addresses.Add(new AddressEntity() { Id = Guid.NewGuid(), - Employee_Id = toDB.Id, + Employee_Id = employeeEntity.Id, CreatedAt = DateTimeOffset.Now, CreatedBy = "sys", - Country = "Taipei", - Street = "Street", + Country = "Taipei1", + Street = "Street1", Remark = "我新的" }); - var employeeEntity = this._employeeRepository.SaveChangeAsync(trackable).Result; + var count = this._employeeRepository.SaveChangeAsync(trackable).Result; } [TestMethod] diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/AddressEntity.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/AddressEntity.cs index 67249ce5..349c5663 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/AddressEntity.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/AddressEntity.cs @@ -2,7 +2,7 @@ namespace Lab.ChangeTracking.Domain; -public record AddressEntity:IEntity +public record AddressEntity : IEntity { public virtual Guid Id { get; set; } diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/EmployeeEntity.cs index 5bd37ec2..f27dd033 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/EmployeeEntity.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/EmployeeEntity.cs @@ -1,6 +1,8 @@ -namespace Lab.ChangeTracking.Domain; +using Lab.ChangeTracking.Abstract; -public record EmployeeEntity +namespace Lab.ChangeTracking.Domain; + +public record EmployeeEntity : IEntity { public virtual Guid Id { get; set; } diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/IdentityEntity.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/IdentityEntity.cs index 96092ab9..6594aa0d 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/IdentityEntity.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/IdentityEntity.cs @@ -1,6 +1,8 @@ -namespace Lab.ChangeTracking.Domain; +using Lab.ChangeTracking.Abstract; -public record IdentityEntity +namespace Lab.ChangeTracking.Domain; + +public record IdentityEntity { public virtual Guid Employee_Id { get; set; } @@ -17,4 +19,5 @@ public record IdentityEntity public virtual string? ModifiedBy { get; set; } public virtual string Remark { get; set; } + } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs index bb60a73e..ea01bf76 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs @@ -14,17 +14,19 @@ protected void ApplyChange(DbContext dbContext, where TTarget : class { var sourceTrackable = source.CastToIChangeTrackable(); + var targetInstance = CreateTargetInstance(source, excludeProperties); + dbContext.Set().Attach(targetInstance); + var changedProperties = sourceTrackable.ChangedProperties; - dbContext.Set().Attach(target); - foreach (var property in changedProperties) + foreach (var changedProperty in changedProperties) { if (excludeProperties != null - && excludeProperties.Any(p => p == property)) + && excludeProperties.Any(p => p == changedProperty)) { continue; } - dbContext.Entry(target).Property(property).IsModified = true; + dbContext.Entry(targetInstance).Property(changedProperty).IsModified = true; } } @@ -42,9 +44,10 @@ protected void ApplyChanges(DbContext dbContext, return; } + var modifyItems = targetsTrackable.ChangedItems; var addedItems = targetsTrackable.AddedItems; var deletedItems = targetsTrackable.DeletedItems; - foreach (var source in targetsTrackable.ChangedItems) + foreach (var source in modifyItems) { var target = targets.FirstOrDefault(p => p.Id == source.Id); if (target == null) @@ -57,8 +60,8 @@ protected void ApplyChanges(DbContext dbContext, foreach (var addedItem in addedItems) { - - // dbContext.Add(addedItem as TTarget); + var targetInstance = CreateTargetInstance(addedItem,excludeProperties); + dbContext.Entry(targetInstance).State = EntityState.Added; } foreach (var source in deletedItems) @@ -69,4 +72,38 @@ protected void ApplyChanges(DbContext dbContext, dbContext.Entry(target).State = EntityState.Deleted; } } + + private static TTarget CreateTargetInstance(TSource sourceInstance, + IEnumerable excludeProperties = null) + where TSource : class + where TTarget : class + { + var targetType = typeof(TTarget); + var targetInstance = (TTarget)Activator.CreateInstance(targetType); + var targetProperties = targetInstance.GetType().GetProperties(); + var sourceType = typeof(TSource); + var sourceProperties = sourceType.GetProperties(); + foreach (var sourceProperty in sourceProperties) + { + if (excludeProperties != null && + excludeProperties.Contains(sourceProperty.Name)) + { + continue; + } + + foreach (var targetProperty in targetProperties) + { + if (sourceProperty.Name == targetProperty.Name + & sourceProperty.PropertyType == targetProperty.PropertyType + ) + { + var value = sourceProperty.GetValue(sourceInstance, null); + targetProperty.SetValue(targetInstance, value, null); + break; + } + } + } + + return targetInstance; + } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs index d16a1f19..9ecfbc28 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs @@ -42,7 +42,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfiguration(new EmployeeConfiguration()); modelBuilder.ApplyConfiguration(new IdentityConfiguration()); - modelBuilder.ApplyConfiguration(new ProfileConfiguration()); + modelBuilder.ApplyConfiguration(new AddressConfiguration()); } internal class EmployeeConfiguration : IEntityTypeConfiguration @@ -92,11 +92,11 @@ public void Configure(EntityTypeBuilder builder) } } - private class ProfileConfiguration : IEntityTypeConfiguration
+ private class AddressConfiguration : IEntityTypeConfiguration
{ public void Configure(EntityTypeBuilder
builder) { - builder.ToTable("Profile"); + builder.ToTable("Address"); builder.HasKey(p => p.Id).IsClustered(false); builder.HasOne(e => e.Employee) .WithMany(p => p.Addresses) From 1c7955253c71729feaf0af7cc76fbd8669acd18c Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 6 Mar 2022 11:52:37 +0800 Subject: [PATCH 171/424] refactor --- .../Lab.ChangeTracking.Abstract/IEntity.cs | 12 +-- .../ChangeTrackingUnitTest.cs | 76 ++++++++------- .../Employee/Entity/AddressEntity.cs | 5 +- .../Employee/Entity/EmployeeEntity.cs | 6 +- .../Employee/Entity/IdentityEntity.cs | 3 +- .../Employee/Repository/EmployeeRepository.cs | 7 +- .../Employee/Repository/RepositoryBase.cs | 97 +++++++++++-------- .../EntityModel/Address.cs | 3 +- .../EntityModel/Employee.cs | 3 +- 9 files changed, 117 insertions(+), 95 deletions(-) diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Abstract/IEntity.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Abstract/IEntity.cs index a861cb96..1076bfc0 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Abstract/IEntity.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Abstract/IEntity.cs @@ -1,6 +1,6 @@ -namespace Lab.ChangeTracking.Abstract; - -public interface IEntity -{ - Guid Id { get; set; } -} \ No newline at end of file +// namespace Lab.ChangeTracking.Abstract; +// +// public interface IEntity +// { +// Guid Id { get; set; } +// } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index bd02c7dd..345a17be 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -31,7 +31,6 @@ public void 異動追蹤後存檔() trackable.Remark = "我變了"; trackable.Identity.Remark = "我變了"; trackable.Addresses[0].Remark = "我變了"; - trackable.Addresses[1].Remark = "我掉了"; trackable.Addresses.RemoveAt(1); trackable.Addresses.Add(new AddressEntity() { @@ -39,46 +38,57 @@ public void 異動追蹤後存檔() Employee_Id = employeeEntity.Id, CreatedAt = DateTimeOffset.Now, CreatedBy = "sys", - Country = "Taipei1", - Street = "Street1", + Country = "Taipei", + Street = "Street", Remark = "我新的" }); var count = this._employeeRepository.SaveChangeAsync(trackable).Result; + var dbContext = this._employeeDbContextFactory.CreateDbContext(); + var actual = dbContext.Employees + .Where(p => p.Id == employeeEntity.Id) + .Include(p => p.Identity) + .Include(p => p.Addresses) + .First() + ; + Assert.AreEqual("我變了",actual.Remark); + Assert.AreEqual("我變了",actual.Identity.Remark); + Assert.AreEqual("我變了",actual.Addresses[0].Remark); + Assert.AreEqual("我新的",actual.Addresses[1].Remark); } [TestMethod] public void 異動追蹤後存檔_回傳不可變的物件() { - var toDB = Insert(); - var source = new EmployeeEntity - { - Id = toDB.Id, - Name = "yao", - Age = 12, - Identity = new IdentityEntity - { - Employee_Id = toDB.Identity.Employee_Id - }, - Addresses = new List - { - new() - { - Id = toDB.Addresses[0] - .Id, - Employee_Id = toDB.Id, - Remark = "AAA" - }, - new() - { - Id = toDB.Addresses[1] - .Id, - Employee_Id = toDB.Id, - Remark = "AAA" - } - } - }; - var employeeEntity = this._employeeAggregate.ModifyFlowAsync(source).Result; - this.DataShouldOk(source); + // var toDB = Insert(); + // var source = new EmployeeEntity + // { + // Id = toDB.Id, + // Name = "yao", + // Age = 12, + // Identity = new IdentityEntity + // { + // Employee_Id = toDB.Identity.Employee_Id + // }, + // Addresses = new List + // { + // new() + // { + // Id = toDB.Addresses[0] + // .Id, + // Employee_Id = toDB.Id, + // Remark = "AAA" + // }, + // new() + // { + // Id = toDB.Addresses[1] + // .Id, + // Employee_Id = toDB.Id, + // Remark = "AAA" + // } + // } + // }; + // var employeeEntity = this._employeeAggregate.ModifyFlowAsync(source).Result; + // this.DataShouldOk(source); } private void DataShouldOk(EmployeeEntity source) diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/AddressEntity.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/AddressEntity.cs index 349c5663..5e8d45e1 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/AddressEntity.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/AddressEntity.cs @@ -1,8 +1,7 @@ -using Lab.ChangeTracking.Abstract; - + namespace Lab.ChangeTracking.Domain; -public record AddressEntity : IEntity +public record AddressEntity { public virtual Guid Id { get; set; } diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/EmployeeEntity.cs index f27dd033..5bd37ec2 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/EmployeeEntity.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/EmployeeEntity.cs @@ -1,8 +1,6 @@ -using Lab.ChangeTracking.Abstract; +namespace Lab.ChangeTracking.Domain; -namespace Lab.ChangeTracking.Domain; - -public record EmployeeEntity : IEntity +public record EmployeeEntity { public virtual Guid Id { get; set; } diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/IdentityEntity.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/IdentityEntity.cs index 6594aa0d..8982147c 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/IdentityEntity.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Entity/IdentityEntity.cs @@ -1,5 +1,4 @@ -using Lab.ChangeTracking.Abstract; - + namespace Lab.ChangeTracking.Domain; public record IdentityEntity diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/EmployeeRepository.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/EmployeeRepository.cs index 712b70fc..cde717bc 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/EmployeeRepository.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/EmployeeRepository.cs @@ -16,15 +16,14 @@ public async Task SaveChangeAsync(EmployeeEntity srcEmployee, CancellationToken cancel = default) { await using var dbContext = await this._memberContextFactory.CreateDbContextAsync(cancel); - var destEmployee = srcEmployee.To(); - this.ApplyChange(dbContext, srcEmployee, destEmployee, new List + this.ApplyModify(dbContext, srcEmployee, new List { "Identity", "Addresses" } ); - this.ApplyChange(dbContext, srcEmployee.Identity, destEmployee.Identity); - this.ApplyChanges(dbContext, srcEmployee.Addresses, destEmployee.Addresses); + this.ApplyModify(dbContext, srcEmployee.Identity); + this.ApplyChanges(dbContext, srcEmployee.Addresses); return await dbContext.SaveChangesAsync(cancel); } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs index ea01bf76..64fbb6ee 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs @@ -1,41 +1,33 @@ using ChangeTracking; -using Lab.ChangeTracking.Abstract; using Microsoft.EntityFrameworkCore; namespace Lab.ChangeTracking.Domain; public class RepositoryBase { - protected void ApplyChange(DbContext dbContext, - TSource source, - TTarget target, - IEnumerable excludeProperties = null) + protected void ApplyAdd(DbContext dbContext, + TSource sourceInstance, + IEnumerable excludeProperties = null) where TSource : class where TTarget : class { - var sourceTrackable = source.CastToIChangeTrackable(); - var targetInstance = CreateTargetInstance(source, excludeProperties); - dbContext.Set().Attach(targetInstance); - - var changedProperties = sourceTrackable.ChangedProperties; - foreach (var changedProperty in changedProperties) - { - if (excludeProperties != null - && excludeProperties.Any(p => p == changedProperty)) - { - continue; - } + var targetInstance = CreateNewInstance(sourceInstance, excludeProperties); + dbContext.Entry(targetInstance).State = EntityState.Added; + } - dbContext.Entry(targetInstance).Property(changedProperty).IsModified = true; - } + protected void ApplyAdd(DbContext dbContext, TSource source) + where TSource : class where TTarget : class + { + var targetInstance = CreateDeleteInstance(source, "Id"); + dbContext.Set().Attach(targetInstance); + dbContext.Entry(targetInstance).State = EntityState.Deleted; } protected void ApplyChanges(DbContext dbContext, IList sources, - IList targets, IEnumerable excludeProperties = null) - where TSource : class, IEntity - where TTarget : class, IEntity + where TSource : class + where TTarget : class { var targetsTrackable = sources.CastToIChangeTrackableCollection(); @@ -49,32 +41,60 @@ protected void ApplyChanges(DbContext dbContext, var deletedItems = targetsTrackable.DeletedItems; foreach (var source in modifyItems) { - var target = targets.FirstOrDefault(p => p.Id == source.Id); - if (target == null) - { - continue; - } - - this.ApplyChange(dbContext, source, target, excludeProperties); + this.ApplyModify(dbContext, source, excludeProperties); } foreach (var addedItem in addedItems) { - var targetInstance = CreateTargetInstance(addedItem,excludeProperties); - dbContext.Entry(targetInstance).State = EntityState.Added; + this.ApplyAdd(dbContext, addedItem, excludeProperties); } foreach (var source in deletedItems) { - var target = Activator.CreateInstance(); - target.Id = source.Id; - dbContext.Set().Attach(target); - dbContext.Entry(target).State = EntityState.Deleted; + this.ApplyAdd(dbContext, source); + } + } + + protected void ApplyModify(DbContext dbContext, + TSource source, + IEnumerable excludeProperties = null) + where TSource : class + where TTarget : class + { + var sourceTrackable = source.CastToIChangeTrackable(); + var targetInstance = CreateNewInstance(source, excludeProperties); + dbContext.Set().Attach(targetInstance); + + var changedProperties = sourceTrackable.ChangedProperties; + foreach (var changedProperty in changedProperties) + { + if (excludeProperties != null + && excludeProperties.Any(p => p == changedProperty)) + { + continue; + } + + dbContext.Entry(targetInstance).Property(changedProperty).IsModified = true; } } - private static TTarget CreateTargetInstance(TSource sourceInstance, - IEnumerable excludeProperties = null) + private static TTarget CreateDeleteInstance(TSource sourceInstance, string propertyName) + where TSource : class + where TTarget : class + { + var targetType = typeof(TTarget); + var targetInstance = (TTarget)Activator.CreateInstance(targetType); + var targetProperty = targetType.GetProperty(propertyName); + var sourceType = sourceInstance.GetType(); + var sourceProperty = sourceType.GetProperty(propertyName); + var value = sourceProperty.GetValue(sourceInstance, null); + targetProperty.SetValue(targetInstance, value, null); + + return targetInstance; + } + + private static TTarget CreateNewInstance(TSource sourceInstance, + IEnumerable excludeProperties = null) where TSource : class where TTarget : class { @@ -94,8 +114,7 @@ private static TTarget CreateTargetInstance(TSource sourceInst foreach (var targetProperty in targetProperties) { if (sourceProperty.Name == targetProperty.Name - & sourceProperty.PropertyType == targetProperty.PropertyType - ) + & sourceProperty.PropertyType == targetProperty.PropertyType) { var value = sourceProperty.GetValue(sourceInstance, null); targetProperty.SetValue(targetInstance, value, null); diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs index 1bbc95fc..cdddbfa0 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs @@ -1,9 +1,8 @@ using System.ComponentModel.DataAnnotations.Schema; -using Lab.ChangeTracking.Abstract; namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel; -public class Address : IEntity +public class Address { public Guid Id { get; set; } diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs index dd1e6721..42bc21b0 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs @@ -1,10 +1,9 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using Lab.ChangeTracking.Abstract; namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel { - public class Employee : IEntity + public class Employee { public Guid Id { get; set; } From 337483374c2f18aa26a5d9fbbcf83399f2e3df87 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 6 Mar 2022 16:57:29 +0800 Subject: [PATCH 172/424] refactor --- .../Employee/Repository/RepositoryBase.cs | 2 +- .../AppDependencyInjectionExtensions.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs index 64fbb6ee..16d5ff97 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain/Employee/Repository/RepositoryBase.cs @@ -94,7 +94,7 @@ private static TTarget CreateDeleteInstance(TSource sourceInst } private static TTarget CreateNewInstance(TSource sourceInstance, - IEnumerable excludeProperties = null) + IEnumerable excludeProperties = null) where TSource : class where TTarget : class { diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs index a76bc397..9d906132 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs @@ -21,6 +21,8 @@ public static void AddEntityFramework(this IServiceCollection services) var connectionString = option.EmployeeDbConnectionString; var loggerFactory = provider.GetService(); optionsBuilder.UseSqlServer(connectionString) + .LogTo(Console.WriteLine) + .EnableSensitiveDataLogging() .UseLoggerFactory(loggerFactory) ; }); From af1f1340d0caeb963baa908da9336cdcdac0dd14 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 6 Mar 2022 19:15:02 +0800 Subject: [PATCH 173/424] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E4=B8=80?= =?UTF-8?q?=E7=AD=86=E5=BE=8C=E5=AD=98=E6=AA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChangeTrackingUnitTest.cs | 98 +++++++++--- .../Lab.ChangeTracking.Domain.UnitTest.csproj | 1 - .../TestAssistants.cs | 1 + .../Lab.ChangeTracking.Domain/CommitState.cs | 8 + .../EmployeeAggregate/Entity/AddressEntity.cs | 22 +++ .../Entity/EmployeeEntity.cs | 55 +++++-- .../Entity/IdentityEntity.cs | 16 +- .../EmployeeAggregate/Entity/ProfileEntity.cs | 8 - .../Repository/EmployeeRepository.cs | 48 +++--- .../Repository/TypeConverterExtensions.cs | 144 +++++++++++++++++ .../Lab.ChangeTracking.Domain/EntityBase.cs | 123 ++++++++------- .../Lab.ChangeTracking.Domain/EntityState.cs | 3 +- .../Lab.ChangeTracking.Domain/IChangeState.cs | 10 ++ .../Lab.ChangeTracking.Domain/IChangeTime.cs | 24 +-- .../IChangeTrackable.cs | 21 +++ .../src/Lab.ChangeTracking.Domain/Survey.cs | 39 ----- .../AppDependencyInjectionExtensions.cs | 2 + .../EntityModel/Address.cs | 29 ++++ .../EntityModel/Employee.cs | 40 ++--- .../EntityModel/EmployeeDbContext.cs | 148 ++++++++++++------ .../EntityModel/Identity.cs | 40 ++--- .../EntityModel/OrderHistory.cs | 29 ---- ...ab.ChangeTracking.Infrastructure.DB.csproj | 8 +- 23 files changed, 601 insertions(+), 316 deletions(-) create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/CommitState.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/AddressEntity.cs delete mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/TypeConverterExtensions.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeState.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs delete mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Survey.cs create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs delete mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/OrderHistory.cs diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index 8a7736ca..54bbf1e8 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Unicode; @@ -6,61 +8,111 @@ using Lab.MultiTestCase.UnitTest; using Microsoft.EntityFrameworkCore; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Employee = Lab.ChangeTracking.Infrastructure.DB.EntityModel.Employee; namespace Lab.ChangeTracking.Domain.UnitTest; [TestClass] public class ChangeTrackingUnitTest { + private static readonly IAccessContext _accessContext = TestAssistants.AccessContext; + + private static readonly IUUIdProvider _uuIdProvider = TestAssistants.UUIdProvider; private readonly IEmployeeAggregate _employeeAggregate = TestAssistants.EmployeeAggregate; private readonly IDbContextFactory _employeeDbContextFactory = TestAssistants.EmployeeDbContextFactory; - private ISystemClock _systemClock = TestAssistants.SystemClock; - - public static IAccessContext _accessContext = TestAssistants.AccessContext; + private readonly IEmployeeRepository _employeeRepository = TestAssistants.EmployeeRepository; - public static IUUIdProvider _uuIdProvider = TestAssistants.UUIdProvider; + private readonly ISystemClock _systemClock = TestAssistants.SystemClock; [TestMethod] - public void 追蹤() + public void 異動追蹤後存檔() { - var employeeEntity = new EmployeeEntity - { - Id = Guid.NewGuid(), - State = EntityState.Added, - Name = "yao", - Age = 19, - Remark = "remark" - }; - employeeEntity.InitialTrack(); - employeeEntity.SetProfile("小章", 20,"remark"); - employeeEntity.SetProfile("小章", 20,"remark"); + var employeeEntity = Insert().To(); + employeeEntity.AsTrackable(); + employeeEntity.SetProfile("小章", 19,"新來的"); + employeeEntity.AcceptChanges(this._systemClock, _accessContext, _uuIdProvider); + + var count = this._employeeRepository.SaveChangeAsync(employeeEntity).Result; + var dbContext = this._employeeDbContextFactory.CreateDbContext(); + + var actual = dbContext.Employees + .Where(p => p.Id == employeeEntity.Id) + .Include(p => p.Identity) + .Include(p => p.Addresses) + .First() + ; + Assert.AreEqual("小章", actual.Name); + Assert.AreEqual(19, actual.Age); + Assert.AreEqual("新來的", actual.Remark); + Assert.AreEqual("我新的", actual.Addresses[1].Remark); } [TestMethod] - public void 追蹤集合() + public void 新增一筆後存檔() { + var employeeEntity = new EmployeeEntity(); + employeeEntity.New("yao", 10); + employeeEntity.AcceptChanges(this._systemClock, _accessContext, _uuIdProvider); + + var count = this._employeeRepository.SaveChangeAsync(employeeEntity).Result; + var dbContext = this._employeeDbContextFactory.CreateDbContext(); + + var actual = dbContext.Employees + .Where(p => p.Id == employeeEntity.Id) + .Include(p => p.Identity) + .Include(p => p.Addresses) + .First() + ; + Assert.AreEqual("我變了", actual.Remark); + Assert.AreEqual("我變了", actual.Identity.Remark); + Assert.AreEqual("我變了", actual.Addresses[0].Remark); + Assert.AreEqual("我新的", actual.Addresses[1].Remark); } private static Employee Insert() { using var dbContext = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + var employeeId = Guid.NewGuid(); var toDB = new Employee { - Id = Guid.NewGuid(), + Id = employeeId, Age = 18, Name = "yao", - CreateAt = DateTimeOffset.Now, - CreateBy = "TEST", + CreatedAt = DateTimeOffset.Now, + CreatedBy = "Sys", Identity = new Identity { + Employee_Id = employeeId, Account = "yao", Password = "123456", - CreateAt = DateTimeOffset.Now, - CreateBy = "TEST" + CreatedAt = DateTimeOffset.Now, + CreatedBy = "Sys", + Remark = "編輯" + }, + Addresses = new List
+ { + new() + { + Id = Guid.NewGuid(), + Employee_Id = employeeId, + CreatedAt = DateTimeOffset.Now, + CreatedBy = "sys", + Country = "Taipei", + Street = "Street", + Remark = "修改的" + }, + new() + { + Id = Guid.NewGuid(), + Employee_Id = employeeId, + CreatedAt = DateTimeOffset.Now, + CreatedBy = "sys", + Country = "Taipei", + Street = "Street", + Remark = "刪除的" + } } }; dbContext.Employees.Add(toDB); diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj index fbfda1f2..e560c2d6 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj @@ -8,7 +8,6 @@ - diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs index af3c81ad..82708555 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs @@ -45,6 +45,7 @@ public static void ConfigureTestServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); _serviceProvider = services.BuildServiceProvider(); } diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/CommitState.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/CommitState.cs new file mode 100644 index 00000000..0ba74bce --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/CommitState.cs @@ -0,0 +1,8 @@ +namespace Lab.ChangeTracking.Domain; + +public enum CommitState +{ + Unchanged = 0, + Rejected = 1, + Accepted = 99, +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/AddressEntity.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/AddressEntity.cs new file mode 100644 index 00000000..6b6a7214 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/AddressEntity.cs @@ -0,0 +1,22 @@ +namespace Lab.ChangeTracking.Domain; + +public record AddressEntity +{ + public Guid Id { get; set; } + + public Guid Employee_Id { get; set; } + + public string Country { get; set; } + + public string Street { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset? ModifiedAt { get; set; } + + public string ModifiedBy { get; set; } + + public string Remark { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs index ebb87622..83ab26e4 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs @@ -1,4 +1,6 @@ -namespace Lab.ChangeTracking.Domain; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; + +namespace Lab.ChangeTracking.Domain; public record EmployeeEntity : EntityBase { @@ -20,28 +22,55 @@ public string Remark init => this._remark = value; } - private IAccessContext _accessContext; - private int? _age; + public List Addresses { get; init; } - // public IList Profiles { get; private set; } = new(); - // - // public IdentityEntity Identity { get; private set; } = new(); + public IdentityEntity Identity { get; init; } - private IUUIdProvider _idProvider; + private int? _age; private string _name; private string _remark; - private ISystemClock _systemClock; - public EmployeeEntity SetId(string name, int age, string remark = null) + public EmployeeEntity Delete() + { + this._entityState = EntityState.Deleted; + return this; + } + + public EmployeeEntity New(string name, int age, string remark = null) { + this._entityState = EntityState.Added; + this._commitState = CommitState.Unchanged; + this._version = 1; this._name = name; this._age = age; this._remark = remark; - this.ChangeTrack(nameof(this.Name), name); - this.ChangeTrack(nameof(this.Age), age); - this.ChangeTrack(nameof(this.Remark), remark); return this; - } + } + + public override void Reset() + { + throw new NotImplementedException(); + } + + /// + /// 從資料庫查到之後放進去 + /// + /// + /// + public EmployeeEntity AsTrackable(Employee employee) + { + this._changedProperties.Clear(); + this._originalValues.Clear(); + this._entityState = EntityState.Unchanged; + this._commitState = CommitState.Unchanged; + this._version = employee.Version; + this._name = employee.Name; + this._age = employee.Age; + this._remark = employee.Remark; + this.AsTrackable(); + return this; + } + public EmployeeEntity SetProfile(string name, int age, string remark = null) { this._name = name; diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs index 1435576f..bcdde865 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs @@ -2,13 +2,19 @@ public record IdentityEntity { - public virtual string Account { get; set; } + public Guid Employee_Id { get; set; } - public virtual string Password { get; set; } + public string Account { get; set; } - public virtual string Remark { get; set; } + public string Password { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } - public virtual DateTimeOffset CreateAt { get; set; } + public DateTimeOffset? ModifiedAt { get; set; } - public virtual string CreateBy { get; set; } + public string? ModifiedBy { get; set; } + + public virtual string Remark { get; set; } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs deleted file mode 100644 index 8ab5d414..00000000 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/ProfileEntity.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Lab.ChangeTracking.Domain; - -public record ProfileEntity -{ - public virtual string FirstName { get; set; } - - public virtual string LastName { get; set; } -} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs index 97f22e66..0738cfc8 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs @@ -5,42 +5,36 @@ namespace Lab.ChangeTracking.Domain; public class EmployeeRepository : IEmployeeRepository { - private readonly IDbContextFactory _memberContextFactory; + private readonly IDbContextFactory _employeeDbContextFactory; public EmployeeRepository(IDbContextFactory memberContextFactory) { - this._memberContextFactory = memberContextFactory; + this._employeeDbContextFactory = memberContextFactory; } - public Infrastructure.DB.EntityModel.Employee To(EmployeeEntity srcEmployee) + public async Task SaveChangeAsync(EmployeeEntity srcEmployee, + CancellationToken cancel = default) { - return new Infrastructure.DB.EntityModel.Employee + if (srcEmployee.CommitState != CommitState.Accepted) { - // Id = srcEmployee.Id, - // Name = srcEmployee.Name, - // Age = srcEmployee.Age, - // Remark = srcEmployee.Remark, - // CreateAt = srcEmployee.CreatedAt + throw new Exception($"{nameof(srcEmployee)} 尚未核准,不得儲存"); + } - // CreateBy = srcEmployee.CreatedBy, - // Identity = this.To(srcEmployee.Identity) - }; - } - - public Identity To(IdentityEntity srcIdentity) - { - return new Identity + await using var dbContext = await this._employeeDbContextFactory.CreateDbContextAsync(cancel); + switch (srcEmployee.EntityState) { - Password = srcIdentity.Password, - Remark = srcIdentity.Remark, - CreateAt = srcIdentity.CreateAt, - CreateBy = srcIdentity.CreateBy - }; - } + case EntityState.Added: + dbContext.Set().Add(srcEmployee.To()); + break; + case EntityState.Modified: + break; + case EntityState.Deleted: + break; + + default: + throw new ArgumentOutOfRangeException(); + } - public async Task SaveChangeAsync(EmployeeEntity srcEmployee, - CancellationToken cancel = default) - { - throw new Exception(); + return await dbContext.SaveChangesAsync(cancel); } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/TypeConverterExtensions.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/TypeConverterExtensions.cs new file mode 100644 index 00000000..5cf3fe8c --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/TypeConverterExtensions.cs @@ -0,0 +1,144 @@ +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; + +namespace Lab.ChangeTracking.Domain; + +public static class TypeConverterExtensions +{ + public static Employee To(this EmployeeEntity srcEmployee) + { + if (srcEmployee == null) + { + return null; + } + + return new Employee + { + Id = srcEmployee.Id ?? default, + Name = srcEmployee.Name, + Age = srcEmployee.Age, + Version = srcEmployee.Version, + Remark = srcEmployee.Remark, + Addresses = srcEmployee.Addresses != null ? srcEmployee.Addresses.To().ToList() : null, + Identity = srcEmployee.Identity.To(), + CreatedAt = srcEmployee.CreatedAt ?? default, + CreatedBy = srcEmployee.CreatedBy, + ModifiedAt = srcEmployee.ModifiedAt, + ModifiedBy = srcEmployee.ModifiedBy + }; + } + + public static EmployeeEntity To(this Employee srcEmployee) + { + if (srcEmployee == null) + { + return null; + } + + return new EmployeeEntity + { + Id = srcEmployee.Id, + Name = srcEmployee.Name, + Age = srcEmployee.Age, + Version = srcEmployee.Version, + Remark = srcEmployee.Remark, + Addresses = srcEmployee.Addresses.To()?.ToList(), + Identity = srcEmployee.Identity.To(), + CreatedAt = srcEmployee.CreatedAt, + CreatedBy = srcEmployee.CreatedBy, + ModifiedAt = srcEmployee.ModifiedAt, + ModifiedBy = srcEmployee.ModifiedBy + }; + } + + public static Identity To(this IdentityEntity srcIdentity) + { + if (srcIdentity == null) + { + return null; + } + + return new Identity + { + Employee_Id = srcIdentity.Employee_Id, + Account = srcIdentity.Account, + Password = srcIdentity.Password, + Remark = srcIdentity.Remark, + CreatedAt = srcIdentity.CreatedAt, + CreatedBy = srcIdentity.CreatedBy, + ModifiedAt = srcIdentity.ModifiedAt, + ModifiedBy = srcIdentity.ModifiedBy + }; + } + + public static IdentityEntity To(this Identity srcIdentity) + { + if (srcIdentity == null) + { + return null; + } + + return new IdentityEntity + { + Employee_Id = srcIdentity.Employee_Id, + Account = srcIdentity.Account, + Password = srcIdentity.Password, + Remark = srcIdentity.Remark, + CreatedAt = srcIdentity.CreatedAt, + CreatedBy = srcIdentity.CreatedBy, + ModifiedAt = srcIdentity.ModifiedAt, + ModifiedBy = srcIdentity.ModifiedBy + }; + } + + public static Address To(this AddressEntity srcAddress) + { + if (srcAddress == null) + { + return null; + } + + return new Address + { + Id = srcAddress.Id, + Employee_Id = srcAddress.Employee_Id, + Country = srcAddress.Country, + Street = srcAddress.Street, + CreatedAt = srcAddress.CreatedAt, + CreatedBy = srcAddress.CreatedBy, + ModifiedAt = srcAddress.ModifiedAt, + ModifiedBy = srcAddress.ModifiedBy, + Remark = srcAddress.Remark + }; + } + + public static AddressEntity To(this Address srcAddress) + { + if (srcAddress == null) + { + return null; + } + + return new AddressEntity + { + Id = srcAddress.Id, + Employee_Id = srcAddress.Employee_Id, + Country = srcAddress.Country, + Street = srcAddress.Street, + CreatedAt = srcAddress.CreatedAt, + CreatedBy = srcAddress.CreatedBy, + ModifiedAt = srcAddress.ModifiedAt, + ModifiedBy = srcAddress.ModifiedBy, + Remark = srcAddress.Remark + }; + } + + public static IEnumerable
To(this IEnumerable srcProfiles) + { + return srcProfiles?.Select(p => p?.To()); + } + + public static IEnumerable To(this IEnumerable
srcProfiles) + { + return srcProfiles?.Select(p => p?.To()); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs index 7e675b00..68926968 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs @@ -1,6 +1,6 @@ namespace Lab.ChangeTracking.Domain; -public record EntityBase : IChangeTrackable +public abstract record EntityBase : IChangeTrackable { public Guid? Id { @@ -8,35 +8,48 @@ public Guid? Id init => this._id = value; } - private readonly Dictionary _changedProperties = new(); - private readonly Dictionary _originalValues = new(); + protected readonly Dictionary _changedProperties = new(); + protected readonly Dictionary _originalValues = new(); + protected CommitState _commitState; private DateTimeOffset? _createdAt; private string? _createdBy; + protected EntityState _entityState; private Guid? _id; private DateTimeOffset? _modifiedAt; private string? _modifiedBy; - private EntityState _state; - private int _version; + protected int _version; + + public EntityBase AsTrackable() + { + this.Validate(); + + // this._entityState = EntityState.Added; + // this._commitState = CommitState.Unchanged; + // this._version = 1; + var properties = this.GetType().GetProperties(); + foreach (var property in properties) + { + this._originalValues.Add(property.Name, property.GetValue(this)); + } + + return this; + } public (Error err, bool changed) AcceptChanges(ISystemClock systemClock, IAccessContext accessContext, IUUIdProvider idProvider) { - if (this.State == EntityState.Submitted) - { - return ( - new Error("STATE_CONFLICT", - $"Entity({this.State}) was submitted and should not submit again."), false); - } + this.Validate(); + this._commitState = CommitState.Accepted; var (now, accessUserId) = (systemClock.GetNow(), accessContext.GetUserId()); - if (this.State == EntityState.Unchanged) + if (this.EntityState == EntityState.Unchanged) { return (null, false); } - if (this.State == EntityState.Added) + if (this.EntityState == EntityState.Added) { this._id = idProvider.GenerateId(); this._createdAt = now; @@ -51,33 +64,28 @@ public Guid? Id this._modifiedAt = now; this._modifiedBy = accessUserId; - this._state = EntityState.Submitted; + // this._entityState = EntityState.Submitted; return (null, true); } - public void InitialTrack() - { - this._state = EntityState.Added; - this._version = 1; - var properties = this.GetType().GetProperties(); - foreach (var property in properties) - { - this._originalValues.Add(property.Name, property.GetValue(this)); - } - } + public abstract void Reset(); public Dictionary GetChangedProperties() { return this._changedProperties; } - public bool HasChanged { get; private set; } + public EntityState EntityState + { + get => this._entityState; + init => this._entityState = value; + } - public EntityState State + public CommitState CommitState { - get => this._state; - init => this._state = value; + get => this._commitState; + init => this._commitState = value; } public int Version @@ -91,33 +99,6 @@ public Dictionary GetOriginalValues() return this._originalValues; } - protected void ChangeTrack(string propertyName, object value) - { - if (this.State == EntityState.Submitted) - { - throw new Exception("已經 Submitted ,無法再進行修改。"); - } - - var changes = this._changedProperties; - var originals = this._originalValues; - if (changes.ContainsKey(propertyName) == false) - { - if (originals[propertyName] != value) - { - changes.Add(propertyName, value); - this._state = EntityState.Added; - } - } - else - { - if (originals[propertyName] != changes[propertyName]) - { - changes[propertyName] = value; - this._state = EntityState.Modified; - } - } - } - public DateTimeOffset? CreatedAt { get => this._createdAt; @@ -141,4 +122,36 @@ public string? ModifiedBy get => this._modifiedBy; init => this._modifiedBy = value; } + + public void ChangeTrack(string propertyName, object value) + { + this.Validate(); + + var changes = this._changedProperties; + var originals = this._originalValues; + if (changes.ContainsKey(propertyName) == false) + { + if (originals[propertyName] != value) + { + changes.Add(propertyName, value); + this._entityState = EntityState.Added; + } + } + else + { + if (originals[propertyName] != changes[propertyName]) + { + changes[propertyName] = value; + this._entityState = EntityState.Modified; + } + } + } + + private void Validate() + { + if (this.CommitState == CommitState.Accepted) + { + throw new Exception("已經同意,無法再進行修改"); + } + } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityState.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityState.cs index 2573f480..ee413431 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityState.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityState.cs @@ -5,5 +5,6 @@ public enum EntityState Unchanged = 0, Added = 1, Modified = 2, - Submitted = 99, + Deleted=3, + // Submitted = 99, } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeState.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeState.cs new file mode 100644 index 00000000..90225b7f --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeState.cs @@ -0,0 +1,10 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IChangeState +{ + EntityState EntityState { get; init; } + + CommitState CommitState { get; init; } + + int Version { get; init; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTime.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTime.cs index c16ef976..a0f626c9 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTime.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTime.cs @@ -1,4 +1,6 @@ -namespace Lab.ChangeTracking.Domain; +using System.ComponentModel; + +namespace Lab.ChangeTracking.Domain; public interface IChangeTime { @@ -9,24 +11,4 @@ public interface IChangeTime DateTimeOffset? ModifiedAt { get; init; } string? ModifiedBy { get; init; } -} - -public interface IChangeState -{ - EntityState State { get; init; } - - int Version { get; init; } -} - -public interface IChangeTrackable : IChangeTime, IChangeState -{ - bool HasChanged { get; } - - Dictionary GetChangedProperties(); - - Dictionary GetOriginalValues(); - - // void SetTrackable(); - // - // void ChangeTrack(string propertyName, object value); } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs new file mode 100644 index 00000000..02ceeab3 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs @@ -0,0 +1,21 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IChangeTrackable : IChangeTime, IChangeState +{ + // bool HasChanged { get; } + public (Error err, bool changed) AcceptChanges(ISystemClock systemClock, + IAccessContext accessContext, + IUUIdProvider idProvider); + + void ChangeTrack(string propertyName, object value); + + void Reset(); + + Dictionary GetChangedProperties(); + + Dictionary GetOriginalValues(); + + // void SetTrackable(); + // + // void ChangeTrack(string propertyName, object value); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Survey.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Survey.cs deleted file mode 100644 index 9a0f3d26..00000000 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/Survey.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.ComponentModel; -using System.Reflection; - -namespace Lab.ChangeTracking.Domain; - -public class Base : IRevertibleChangeTracking -{ - protected readonly Dictionary ChangedProperties = new(); - protected readonly Dictionary OriginalValues = new(); - - public void Initialize() - { - var properties = this.GetType().GetProperties(); - - // Save the current value of the properties to our dictionary. - foreach (var property in properties) - { - this.OriginalValues.Add(property.Name, property.GetValue(this)); - } - } - - public bool IsChanged { get; private set; } - - public void RejectChanges() - { - foreach (var property in this.ChangedProperties) - { - this.GetType().GetRuntimeProperty(property.Key).SetValue(this, property.Value); - } - - this.AcceptChanges(); - } - - public void AcceptChanges() - { - this.ChangedProperties.Clear(); - this.IsChanged = false; - } -} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs index a76bc397..332cf598 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs @@ -22,6 +22,8 @@ public static void AddEntityFramework(this IServiceCollection services) var loggerFactory = provider.GetService(); optionsBuilder.UseSqlServer(connectionString) .UseLoggerFactory(loggerFactory) + .LogTo(Console.WriteLine) + .EnableSensitiveDataLogging() ; }); diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs new file mode 100644 index 00000000..cdddbfa0 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs @@ -0,0 +1,29 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel; + +public class Address +{ + public Guid Id { get; set; } + + public Guid Employee_Id { get; set; } + + public Employee Employee { get; set; } + + public string Country { get; set; } + + public string Street { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset? ModifiedAt { get; set; } + + public string ModifiedBy { get; set; } + + public string Remark { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int SequenceId { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs index 2e37227a..42bc21b0 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs @@ -1,30 +1,34 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel; - -[Table("Employee")] -public class Employee +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel { - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.None)] - public Guid Id { get; set; } + public class Employee + { + public Guid Id { get; set; } + + public int Version { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + public IList
Addresses { get; set; } - [Required] - public string Name { get; set; } + public Identity Identity { get; set; } - public int? Age { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long SequenceId { get; set; } + public DateTimeOffset CreatedAt { get; set; } - public string Remark { get; set; } + public string CreatedBy { get; set; } - [Required] - public DateTimeOffset CreateAt { get; set; } + public DateTimeOffset? ModifiedAt { get; set; } - [Required] - public string CreateBy { get; set; } + public string ModifiedBy { get; set; } - public virtual Identity Identity { get; set; } + public string Remark { get; set; } + } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs index f859963d..9ecfbc28 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs @@ -1,76 +1,120 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; -using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel; - -public class EmployeeDbContext : DbContext +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel { - private static readonly bool[] s_migrated = { false }; + public class EmployeeDbContext : DbContext + { + private static readonly bool[] s_migrated = { false }; - public virtual DbSet Employees { get; set; } + public virtual DbSet Employees { get; set; } - public virtual DbSet Identities { get; set; } + public virtual DbSet Identity { get; set; } - public virtual DbSet OrderHistories { get; set; } + public virtual DbSet
Addresses { get; set; } - public EmployeeDbContext(DbContextOptions options) - : base(options) - { - if (s_migrated[0]) + public EmployeeDbContext(DbContextOptions options) + : base(options) { - return; + // 改用 CI 執行 Migrate + + // if (s_migrated[0]) + // { + // return; + // } + // + // lock (s_migrated) + // { + // if (s_migrated[0] == false) + // { + // var sqlOptions = options.FindExtension(); + // if (sqlOptions != null) + // { + // this.Database.Migrate(); + // } + // + // s_migrated[0] = true; + // } + // } } - lock (s_migrated) + protected override void OnModelCreating(ModelBuilder modelBuilder) { - if (s_migrated[0] == false) + modelBuilder.ApplyConfiguration(new EmployeeConfiguration()); + modelBuilder.ApplyConfiguration(new IdentityConfiguration()); + modelBuilder.ApplyConfiguration(new AddressConfiguration()); + } + + internal class EmployeeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) { - var memoryOptions = options.FindExtension(); - - if (memoryOptions == null) - { - var sqlOptions = options.FindExtension(); - if (sqlOptions != null) - { - this.Database.Migrate(); - } - } - - s_migrated[0] = true; + builder.ToTable("Employee"); + builder.HasKey(p => p.Id) + .IsClustered(false); + + builder.Property(p => p.Name).IsRequired(); + builder.Property(p => p.CreatedBy).IsRequired(); + builder.Property(p => p.CreatedAt).IsRequired(); + builder.Property(p => p.ModifiedAt).IsRequired(false); + builder.Property(p => p.ModifiedBy).IsRequired(false); + builder.Property(p => p.Remark).IsRequired(false); + + builder.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); } } - } - //管理索引 - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(p => + private class IdentityConfiguration : IEntityTypeConfiguration { - p.HasKey(e => e.Id) - .IsClustered(false); + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Identity"); + builder.HasKey(p => p.Employee_Id).IsClustered(false); + builder.HasOne(e => e.Employee) + .WithOne(p => p.Identity) + .HasForeignKey(p => p.Employee_Id) + .OnDelete(DeleteBehavior.Cascade) + ; - p.HasIndex(e => e.SequenceId) - .IsUnique() - .IsClustered(); + builder.Property(p => p.Account).IsRequired(); + builder.Property(p => p.Password).IsRequired(); + builder.Property(p => p.CreatedBy).IsRequired(); + builder.Property(p => p.CreatedAt).IsRequired(); + builder.Property(p => p.ModifiedAt).IsRequired(false); + builder.Property(p => p.ModifiedBy).IsRequired(false); + builder.Property(p => p.Remark).IsRequired(false); - p.Property(p => p.Remark) - .IsRequired(false) - ; - }); + builder.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + } + } - modelBuilder.Entity(p => + private class AddressConfiguration : IEntityTypeConfiguration
{ - p.HasKey(e => e.Employee_Id) - .IsClustered(false); + public void Configure(EntityTypeBuilder
builder) + { + builder.ToTable("Address"); + builder.HasKey(p => p.Id).IsClustered(false); + builder.HasOne(e => e.Employee) + .WithMany(p => p.Addresses) + .HasForeignKey(p => p.Employee_Id) + .OnDelete(DeleteBehavior.Cascade); - p.HasIndex(e => e.SequenceId) - .IsUnique() - .IsClustered(); + builder.Property(p => p.Country).IsRequired(); + builder.Property(p => p.Street).IsRequired(); + builder.Property(p => p.CreatedBy).IsRequired(); + builder.Property(p => p.CreatedAt).IsRequired(); + builder.Property(p => p.ModifiedBy).IsRequired(false); + builder.Property(p => p.ModifiedAt).IsRequired(false); + builder.Property(p => p.Remark).IsRequired(false); - p.Property(p => p.Remark) - .IsRequired(false) - ; - }); + builder.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + } + } } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs index f2251c34..e9e2f6d9 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs @@ -1,32 +1,32 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel; - -[Table("Identity")] -public class Identity +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel { - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.None)] - public Guid Employee_Id { get; set; } + public class Identity + { + public Guid Employee_Id { get; set; } + + // [ForeignKey("Employee_Id")] + public virtual Employee Employee { get; set; } + + [Required] + public string Account { get; set; } - [Required] - public string Account { get; set; } + [Required] + public string Password { get; set; } - [Required] - public string Password { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long SequenceId { get; set; } + public string Remark { get; set; } - public string Remark { get; set; } + public DateTimeOffset CreatedAt { get; set; } - [Required] - public DateTimeOffset CreateAt { get; set; } + public string CreatedBy { get; set; } - [Required] - public string CreateBy { get; set; } + public DateTimeOffset? ModifiedAt { get; set; } - [ForeignKey("Employee_Id")] - public virtual Employee Employee { get; set; } + public string? ModifiedBy { get; set; } + } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/OrderHistory.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/OrderHistory.cs deleted file mode 100644 index 6632c4e0..00000000 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/OrderHistory.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel; - -[Table("OrderHistory")] -public class OrderHistory -{ - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public Guid Id { get; set; } - - public Guid? Employee_Id { get; set; } - - public string Remark { get; set; } - - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long SequenceId { get; set; } - - public string Product_Id { get; set; } - - public string Product_Name { get; set; } - - [Required] - public DateTime CreateAt { get; set; } - - [Required] - public string CreateBy { get; set; } -} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj index b1b261bb..9fcdb0d8 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj @@ -6,10 +6,10 @@ enable - - - - + + + + From 200f218dcbd9e94690faea00f8481f5689e60bb0 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 7 Mar 2022 01:12:44 +0800 Subject: [PATCH 174/424] =?UTF-8?q?feat:=20=E7=B7=A8=E8=BC=AF=E3=80=81?= =?UTF-8?q?=E5=88=AA=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChangeTrackingUnitTest.cs | 101 ++++++++++++++---- .../Entity/EmployeeEntity.cs | 50 +++++---- .../Repository/EmployeeRepository.cs | 48 ++++++++- .../Repository/IEmployeeRepository.cs | 4 +- .../Repository/TypeConverterExtensions.cs | 4 +- .../Lab.ChangeTracking.Domain/EntityBase.cs | 35 +++--- .../Lab.ChangeTracking.Domain/IChangeTime.cs | 4 +- .../PropertyChangeTracker.cs | 48 --------- 8 files changed, 188 insertions(+), 106 deletions(-) delete mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/PropertyChangeTracker.cs diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index 54bbf1e8..91b6b981 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -4,6 +4,7 @@ using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Unicode; +using EFCore.BulkExtensions; using Lab.ChangeTracking.Infrastructure.DB.EntityModel; using Lab.MultiTestCase.UnitTest; using Microsoft.EntityFrameworkCore; @@ -17,47 +18,101 @@ public class ChangeTrackingUnitTest private static readonly IAccessContext _accessContext = TestAssistants.AccessContext; private static readonly IUUIdProvider _uuIdProvider = TestAssistants.UUIdProvider; - private readonly IEmployeeAggregate _employeeAggregate = TestAssistants.EmployeeAggregate; - private readonly IDbContextFactory _employeeDbContextFactory = + private static readonly IDbContextFactory s_employeeDbContextFactory = TestAssistants.EmployeeDbContextFactory; + private readonly IEmployeeAggregate _employeeAggregate = TestAssistants.EmployeeAggregate; + private readonly IEmployeeRepository _employeeRepository = TestAssistants.EmployeeRepository; private readonly ISystemClock _systemClock = TestAssistants.SystemClock; + [ClassCleanup] + public static void ClassCleanup() + { + DeleteAllTable(); + } + + [ClassInitialize] + public static void ClassInitialize(TestContext context) + { + DeleteAllTable(); + } + [TestMethod] - public void 異動追蹤後存檔() + public void 刪除一筆資料() { - var employeeEntity = Insert().To(); - employeeEntity.AsTrackable(); - employeeEntity.SetProfile("小章", 19,"新來的"); - employeeEntity.AcceptChanges(this._systemClock, _accessContext, _uuIdProvider); + var fromDb = Insert(); + var employeeEntity = new EmployeeEntity(); + employeeEntity.AsTrackable(fromDb) + .SetDelete() + .AcceptChanges(this._systemClock, _accessContext, _uuIdProvider); var count = this._employeeRepository.SaveChangeAsync(employeeEntity).Result; - var dbContext = this._employeeDbContextFactory.CreateDbContext(); + + Assert.AreEqual(1, count); + var dbContext = s_employeeDbContextFactory.CreateDbContext(); var actual = dbContext.Employees - .Where(p => p.Id == employeeEntity.Id) + .Where(p => p.Id == fromDb.Id) + .Include(p => p.Identity) + .Include(p => p.Addresses) + .FirstOrDefault() + ; + Assert.AreEqual(null, actual); + } + + [TestMethod] + public void 更新一筆資料() + { + var fromDb = Insert(); + var employeeEntity = new EmployeeEntity(); + employeeEntity.AsTrackable(fromDb) + .SetProfile("小章", 19, "我變了") + .AcceptChanges(this._systemClock, _accessContext, _uuIdProvider); + + var count = this._employeeRepository.SaveChangeAsync(employeeEntity).Result; + + Assert.AreEqual(1, count); + var dbContext = s_employeeDbContextFactory.CreateDbContext(); + + var actual = dbContext.Employees + .Where(p => p.Id == fromDb.Id) .Include(p => p.Identity) .Include(p => p.Addresses) .First() ; Assert.AreEqual("小章", actual.Name); Assert.AreEqual(19, actual.Age); - Assert.AreEqual("新來的", actual.Remark); - Assert.AreEqual("我新的", actual.Addresses[1].Remark); + Assert.AreEqual("我變了", actual.Remark); } [TestMethod] - public void 新增一筆後存檔() + public void 沒有異動() { + var fromDb = Insert(); var employeeEntity = new EmployeeEntity(); - employeeEntity.New("yao", 10); - employeeEntity.AcceptChanges(this._systemClock, _accessContext, _uuIdProvider); + employeeEntity.AsTrackable(fromDb) + .SetProfile("小章", 19, "新來的") + .SetProfile("yao", 18, "編輯") + .AcceptChanges(this._systemClock, _accessContext, _uuIdProvider); var count = this._employeeRepository.SaveChangeAsync(employeeEntity).Result; - var dbContext = this._employeeDbContextFactory.CreateDbContext(); + Assert.AreEqual(0, count); + } + + [TestMethod] + public void 新增一筆資料() + { + var employeeEntity = new EmployeeEntity(); + employeeEntity.New("yao", 10, "新的") + .AcceptChanges(this._systemClock, _accessContext, _uuIdProvider); + + var count = this._employeeRepository.SaveChangeAsync(employeeEntity).Result; + + Assert.AreEqual(1, count); + var dbContext = s_employeeDbContextFactory.CreateDbContext(); var actual = dbContext.Employees .Where(p => p.Id == employeeEntity.Id) @@ -65,10 +120,17 @@ public void 新增一筆後存檔() .Include(p => p.Addresses) .First() ; - Assert.AreEqual("我變了", actual.Remark); - Assert.AreEqual("我變了", actual.Identity.Remark); - Assert.AreEqual("我變了", actual.Addresses[0].Remark); - Assert.AreEqual("我新的", actual.Addresses[1].Remark); + Assert.AreEqual("yao", actual.Name); + Assert.AreEqual(10, actual.Age); + Assert.AreEqual("新的", actual.Remark); + } + + private static void DeleteAllTable() + { + var dbContext = s_employeeDbContextFactory.CreateDbContext(); + dbContext.Employees.BatchDelete(); + dbContext.Addresses.BatchDelete(); + dbContext.Identity.BatchDelete(); } private static Employee Insert() @@ -82,6 +144,7 @@ private static Employee Insert() Name = "yao", CreatedAt = DateTimeOffset.Now, CreatedBy = "Sys", + Remark = "編輯", Identity = new Identity { Employee_Id = employeeId, diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs index 83ab26e4..d481562c 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs @@ -30,7 +30,36 @@ public string Remark private string _name; private string _remark; - public EmployeeEntity Delete() + /// + /// 從資料庫查到之後放進去 + /// + /// + /// + public EmployeeEntity AsTrackable(Employee employee) + { + this._changedProperties.Clear(); + this._originalValues.Clear(); + this._entityState = EntityState.Unchanged; + this._commitState = CommitState.Unchanged; + this._id = employee.Id; + this._version = employee.Version; + this._createdAt = employee.CreatedAt; + this._createdBy = employee.CreatedBy; + this._modifiedAt = employee.ModifiedAt; + this._modifiedBy = employee.ModifiedBy; + this._version = employee.Version; + this._name = employee.Name; + this._age = employee.Age; + this._remark = employee.Remark; + + // Addresses = null, + // Identity = null, + + this.AsTrackable(); + return this; + } + + public EmployeeEntity SetDelete() { this._entityState = EntityState.Deleted; return this; @@ -52,25 +81,6 @@ public override void Reset() throw new NotImplementedException(); } - /// - /// 從資料庫查到之後放進去 - /// - /// - /// - public EmployeeEntity AsTrackable(Employee employee) - { - this._changedProperties.Clear(); - this._originalValues.Clear(); - this._entityState = EntityState.Unchanged; - this._commitState = CommitState.Unchanged; - this._version = employee.Version; - this._name = employee.Name; - this._age = employee.Age; - this._remark = employee.Remark; - this.AsTrackable(); - return this; - } - public EmployeeEntity SetProfile(string name, int age, string remark = null) { this._name = name; diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs index 0738cfc8..2791fd8f 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs @@ -13,6 +13,7 @@ public EmployeeRepository(IDbContextFactory memberContextFact } public async Task SaveChangeAsync(EmployeeEntity srcEmployee, + IEnumerable excludeProperties = null, CancellationToken cancel = default) { if (srcEmployee.CommitState != CommitState.Accepted) @@ -24,17 +25,60 @@ public async Task SaveChangeAsync(EmployeeEntity srcEmployee, switch (srcEmployee.EntityState) { case EntityState.Added: - dbContext.Set().Add(srcEmployee.To()); + ApplyAdd(dbContext, srcEmployee); break; case EntityState.Modified: + ApplyModify(dbContext, srcEmployee, excludeProperties); + break; case EntityState.Deleted: + ApplyDelete(srcEmployee, dbContext); + break; - + + case EntityState.Unchanged: + return 0; default: throw new ArgumentOutOfRangeException(); } return await dbContext.SaveChangesAsync(cancel); } + + private static void ApplyDelete(EmployeeEntity srcEmployee, EmployeeDbContext dbContext) + { + dbContext.Set().Remove(new Employee() { Id = srcEmployee.Id }); + } + + private static void ApplyAdd(EmployeeDbContext dbContext, EmployeeEntity srcEmployee) + { + dbContext.Set().Add(srcEmployee.To()); + } + + private static void ApplyModify(EmployeeDbContext dbContext, + EmployeeEntity srcEmployee, + IEnumerable excludeProperties = null) + { + var destEmployee = new Employee() + { + Id = srcEmployee.Id + }; + + dbContext.Set().Attach(destEmployee); + var employeeEntry = dbContext.Entry(destEmployee); + + foreach (var property in srcEmployee.GetChangedProperties()) + { + var propertyName = property.Key; + var value = property.Value; + if (excludeProperties != null + && excludeProperties.Any(p => p == propertyName)) + { + continue; + } + + dbContext.Entry(destEmployee).Property(propertyName).CurrentValue = value; + employeeEntry.Property(propertyName).IsModified = true; + } + } } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs index 8748817c..779211fc 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs @@ -2,5 +2,7 @@ public interface IEmployeeRepository { - Task SaveChangeAsync(EmployeeEntity employee, CancellationToken cancel = default); + Task SaveChangeAsync(EmployeeEntity employee, + IEnumerable excludeProperties = null, + CancellationToken cancel = default); } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/TypeConverterExtensions.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/TypeConverterExtensions.cs index 5cf3fe8c..4b0d422d 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/TypeConverterExtensions.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/TypeConverterExtensions.cs @@ -13,14 +13,14 @@ public static Employee To(this EmployeeEntity srcEmployee) return new Employee { - Id = srcEmployee.Id ?? default, + Id = srcEmployee.Id, Name = srcEmployee.Name, Age = srcEmployee.Age, Version = srcEmployee.Version, Remark = srcEmployee.Remark, Addresses = srcEmployee.Addresses != null ? srcEmployee.Addresses.To().ToList() : null, Identity = srcEmployee.Identity.To(), - CreatedAt = srcEmployee.CreatedAt ?? default, + CreatedAt = srcEmployee.CreatedAt, CreatedBy = srcEmployee.CreatedBy, ModifiedAt = srcEmployee.ModifiedAt, ModifiedBy = srcEmployee.ModifiedBy diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs index 68926968..d6477afb 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs @@ -1,8 +1,10 @@ -namespace Lab.ChangeTracking.Domain; +using System.Collections; + +namespace Lab.ChangeTracking.Domain; public abstract record EntityBase : IChangeTrackable { - public Guid? Id + public Guid Id { get => this._id; init => this._id = value; @@ -11,12 +13,12 @@ public Guid? Id protected readonly Dictionary _changedProperties = new(); protected readonly Dictionary _originalValues = new(); protected CommitState _commitState; - private DateTimeOffset? _createdAt; - private string? _createdBy; + protected DateTimeOffset _createdAt; + protected string _createdBy; protected EntityState _entityState; - private Guid? _id; - private DateTimeOffset? _modifiedAt; - private string? _modifiedBy; + protected Guid _id; + protected DateTimeOffset? _modifiedAt; + protected string? _modifiedBy; protected int _version; public EntityBase AsTrackable() @@ -99,7 +101,7 @@ public Dictionary GetOriginalValues() return this._originalValues; } - public DateTimeOffset? CreatedAt + public DateTimeOffset CreatedAt { get => this._createdAt; init => this._createdAt = value; @@ -129,22 +131,31 @@ public void ChangeTrack(string propertyName, object value) var changes = this._changedProperties; var originals = this._originalValues; + if (originals.Count <= 0) + { + throw new Exception("尚未啟用追蹤"); + } + if (changes.ContainsKey(propertyName) == false) { if (originals[propertyName] != value) { changes.Add(propertyName, value); - this._entityState = EntityState.Added; + this._entityState = EntityState.Modified; } } else { - if (originals[propertyName] != changes[propertyName]) + if (originals[propertyName].ToString() == value.ToString()) { - changes[propertyName] = value; - this._entityState = EntityState.Modified; + changes.Remove(propertyName); } } + + if (changes.Count <= 0) + { + this._entityState = EntityState.Unchanged; + } } private void Validate() diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTime.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTime.cs index a0f626c9..605ff333 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTime.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTime.cs @@ -4,9 +4,9 @@ namespace Lab.ChangeTracking.Domain; public interface IChangeTime { - DateTimeOffset? CreatedAt { get; init; } + DateTimeOffset CreatedAt { get; init; } - string? CreatedBy { get; init; } + string CreatedBy { get; init; } DateTimeOffset? ModifiedAt { get; init; } diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/PropertyChangeTracker.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/PropertyChangeTracker.cs deleted file mode 100644 index 26cfefd8..00000000 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/PropertyChangeTracker.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace Lab.ChangeTracking.Domain; - -public class PropertyChangeTracker -{ - private readonly Dictionary _changedProperties = new(); - private readonly Dictionary _originalValues = new(); - - public Dictionary GetChangedProperties() - { - return this._changedProperties; - } - - public Dictionary GetOriginalValues() - { - throw new NotImplementedException(); - } - - public void Initial() - { - var properties = this.GetType().GetProperties(); - foreach (var property in properties) - { - this._originalValues.Add(property.Name, property.GetValue(this)); - } - } - - public void Track(string propertyName, object value) - { - var changes = this._changedProperties; - var originals = this._originalValues; - if (changes.ContainsKey(propertyName) == false) - { - changes.Add(propertyName, value); - } - else - { - if (originals[propertyName] != changes[propertyName]) - { - changes[propertyName] = value; - } - - if (originals[propertyName] == changes[propertyName]) - { - changes.Remove(propertyName); - } - } - } -} \ No newline at end of file From c9d8b65c268550868c8625f59cc91c8de52b2802 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 7 Mar 2022 01:15:12 +0800 Subject: [PATCH 175/424] =?UTF-8?q?=E6=A1=88=E4=BE=8B=E9=96=8B=E5=A7=8B?= =?UTF-8?q?=E5=89=8D=E5=BE=8C=E5=88=AA=E9=99=A4=E8=B3=87=E6=96=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChangeTrackingUnitTest.cs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs index 345a17be..9942df35 100644 --- a/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs +++ b/Property Change Tracking/ChangeTracking2/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -5,6 +5,7 @@ using System.Text.Json; using System.Text.Unicode; using ChangeTracking; +using EFCore.BulkExtensions; using Lab.ChangeTracking.Infrastructure.DB.EntityModel; using Microsoft.EntityFrameworkCore; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -16,7 +17,7 @@ public class ChangeTrackingUnitTest { private readonly IEmployeeAggregate _employeeAggregate = TestAssistants.EmployeeAggregate; - private readonly IDbContextFactory _employeeDbContextFactory = + private static readonly IDbContextFactory s_employeeDbContextFactory = TestAssistants.EmployeeDbContextFactory; private readonly IEmployeeRepository _employeeRepository = TestAssistants.EmployeeRepository; @@ -43,7 +44,7 @@ public void 異動追蹤後存檔() Remark = "我新的" }); var count = this._employeeRepository.SaveChangeAsync(trackable).Result; - var dbContext = this._employeeDbContextFactory.CreateDbContext(); + var dbContext = s_employeeDbContextFactory.CreateDbContext(); var actual = dbContext.Employees .Where(p => p.Id == employeeEntity.Id) .Include(p => p.Identity) @@ -93,7 +94,7 @@ public void 異動追蹤後存檔_回傳不可變的物件() private void DataShouldOk(EmployeeEntity source) { - var dbContext = this._employeeDbContextFactory.CreateDbContext(); + var dbContext = s_employeeDbContextFactory.CreateDbContext(); var actual = dbContext.Employees .Where(p => p.Id == source.Id) .Include(p => p.Identity) @@ -152,7 +153,24 @@ private static Employee Insert() dbContext.SaveChanges(); return toDB; } + [ClassCleanup] + public static void ClassCleanup() + { + DeleteAllTable(); + } + [ClassInitialize] + public static void ClassInitialize(TestContext context) + { + DeleteAllTable(); + } + private static void DeleteAllTable() + { + var dbContext = s_employeeDbContextFactory.CreateDbContext(); + dbContext.Employees.BatchDelete(); + dbContext.Addresses.BatchDelete(); + dbContext.Identity.BatchDelete(); + } private static string ToJson(T instance) { var serialize = JsonSerializer.Serialize(instance, From 165a9c2bf5e017eb45513a0db98ddba133386ed1 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 7 Mar 2022 01:30:54 +0800 Subject: [PATCH 176/424] refactor --- .../src/Lab.ChangeTracking.Domain/EntityState.cs | 3 +-- .../src/Lab.ChangeTracking.Domain/IChangeTrackable.cs | 4 ---- .../src/Lab.ChangeTracking.Domain/IChangeable.cs | 6 ------ 3 files changed, 1 insertion(+), 12 deletions(-) delete mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeable.cs diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityState.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityState.cs index ee413431..7fff1298 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityState.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityState.cs @@ -5,6 +5,5 @@ public enum EntityState Unchanged = 0, Added = 1, Modified = 2, - Deleted=3, - // Submitted = 99, + Deleted = 3, } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs index 02ceeab3..e891f5f9 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs @@ -14,8 +14,4 @@ public interface IChangeTrackable : IChangeTime, IChangeState Dictionary GetChangedProperties(); Dictionary GetOriginalValues(); - - // void SetTrackable(); - // - // void ChangeTrack(string propertyName, object value); } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeable.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeable.cs deleted file mode 100644 index 2c5125f8..00000000 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeable.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Lab.ChangeTracking.Domain; - -public interface IChangeable:IChangeTrackable -{ - -} \ No newline at end of file From 6117d954fe494e397d850d4d9766d03877adc88a Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 7 Mar 2022 16:19:15 +0800 Subject: [PATCH 177/424] refactor --- .../EmployeeAggregate/Entity/EmployeeEntity.cs | 2 +- .../src/Lab.ChangeTracking.Domain/EntityBase.cs | 2 +- .../src/Lab.ChangeTracking.Domain/IChangeContent.cs | 8 ++++++++ .../src/Lab.ChangeTracking.Domain/IChangeTrackable.cs | 8 ++------ 4 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeContent.cs diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs index d481562c..b40da0a2 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs @@ -76,7 +76,7 @@ public EmployeeEntity New(string name, int age, string remark = null) return this; } - public override void Reset() + public override void RejectChanges() { throw new NotImplementedException(); } diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs index d6477afb..f0f0dfdf 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/EntityBase.cs @@ -71,7 +71,7 @@ public EntityBase AsTrackable() return (null, true); } - public abstract void Reset(); + public abstract void RejectChanges(); public Dictionary GetChangedProperties() { diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeContent.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeContent.cs new file mode 100644 index 00000000..6d899ee9 --- /dev/null +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeContent.cs @@ -0,0 +1,8 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IChangeContent +{ + Dictionary GetChangedProperties(); + + Dictionary GetOriginalValues(); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs index e891f5f9..4c565a05 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs @@ -1,6 +1,6 @@ namespace Lab.ChangeTracking.Domain; -public interface IChangeTrackable : IChangeTime, IChangeState +public interface IChangeTrackable : IChangeContent, IChangeTime, IChangeState { // bool HasChanged { get; } public (Error err, bool changed) AcceptChanges(ISystemClock systemClock, @@ -9,9 +9,5 @@ public interface IChangeTrackable : IChangeTime, IChangeState void ChangeTrack(string propertyName, object value); - void Reset(); - - Dictionary GetChangedProperties(); - - Dictionary GetOriginalValues(); + void RejectChanges(); } \ No newline at end of file From 6f54bd217b3300c4137162c362f57af75931d44e Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 8 Mar 2022 01:44:25 +0800 Subject: [PATCH 178/424] refactor --- .../src/Lab.ChangeTracking.Domain/CommitState.cs | 4 ++-- .../src/Lab.ChangeTracking.Domain/IChangeTrackable.cs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/CommitState.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/CommitState.cs index 0ba74bce..bc28b09f 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/CommitState.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/CommitState.cs @@ -3,6 +3,6 @@ namespace Lab.ChangeTracking.Domain; public enum CommitState { Unchanged = 0, - Rejected = 1, - Accepted = 99, + Accepted = 1, + Rejected = 2, } \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs index 4c565a05..ae42e1f4 100644 --- a/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs +++ b/Property Change Tracking/ChangeTrackProperty/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs @@ -2,7 +2,6 @@ namespace Lab.ChangeTracking.Domain; public interface IChangeTrackable : IChangeContent, IChangeTime, IChangeState { - // bool HasChanged { get; } public (Error err, bool changed) AcceptChanges(ISystemClock systemClock, IAccessContext accessContext, IUUIdProvider idProvider); From 6a94eb0a1747f8edaf129fd0be0af3e53803ac42 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 12 Mar 2022 12:24:34 +0800 Subject: [PATCH 179/424] add swagger doc project --- .../Swagger/Lab.SwaggerDoc/Lab.SwaggerDoc.sln | 16 +++++ .../Controllers/WeatherForecastController.cs | 32 +++++++++ .../Lab.Swashbuckle.AspNetCore6.csproj | 15 +++++ .../Lab.Swashbuckle.AspNetCore6/Program.cs | 67 +++++++++++++++++++ .../Properties/launchSettings.json | 31 +++++++++ .../WeatherForecast.cs | 12 ++++ .../appsettings.Development.json | 8 +++ .../appsettings.json | 9 +++ 8 files changed, 190 insertions(+) create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.SwaggerDoc.sln create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/WeatherForecastController.cs create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Properties/launchSettings.json create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/WeatherForecast.cs create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/appsettings.Development.json create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/appsettings.json diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.SwaggerDoc.sln b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.SwaggerDoc.sln new file mode 100644 index 00000000..59eaf419 --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.SwaggerDoc.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Swashbuckle.AspNetCore6", "Lab.Swashbuckle.AspNetCore6\Lab.Swashbuckle.AspNetCore6.csproj", "{2DA608D5-BC95-4DF6-AB7A-0A22085E9257}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2DA608D5-BC95-4DF6-AB7A-0A22085E9257}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DA608D5-BC95-4DF6-AB7A-0A22085E9257}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DA608D5-BC95-4DF6-AB7A-0A22085E9257}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DA608D5-BC95-4DF6-AB7A-0A22085E9257}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/WeatherForecastController.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..539c6b34 --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/WeatherForecastController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.Swashbuckle.AspNetCore6.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + this._logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj new file mode 100644 index 00000000..257d3e70 --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + bin\ + bin\Lab.Swashbuckle.AspNetCore6.xml + + + + + + + diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs new file mode 100644 index 00000000..f046f113 --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs @@ -0,0 +1,67 @@ +using System.Reflection; +using System.Xml.XPath; +using Swashbuckle.AspNetCore.SwaggerGen; + +void IncludeXmlComments(Assembly assembly, SwaggerGenOptions swaggerGenOptions) +{ + var directory = AppDomain.CurrentDomain.BaseDirectory; + if (assembly != null) + { + foreach (var name in assembly.GetManifestResourceNames() + .Where(x => x.ToUpper() + .EndsWith(".XML")) + ) + { + try + { + var xPath = new XPathDocument(assembly.GetManifestResourceStream(name)); + swaggerGenOptions.IncludeXmlComments((Func)(() => xPath)); + } + catch + { + } + } + } + + if (string.IsNullOrEmpty(directory)) + { + return; + } + + foreach (var file in Directory.GetFiles(directory, "*.XML", SearchOption.AllDirectories)) + { + swaggerGenOptions.IncludeXmlComments(file); + } +} + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(c => +{ + var assembly = typeof(Program).Assembly; + + IncludeXmlComments(assembly, c); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Properties/launchSettings.json b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Properties/launchSettings.json new file mode 100644 index 00000000..a270a625 --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:51786", + "sslPort": 44377 + } + }, + "profiles": { + "Lab.Swashbuckle.AspNetCore6": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7236;http://localhost:5236", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/WeatherForecast.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/WeatherForecast.cs new file mode 100644 index 00000000..e896017e --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace Lab.Swashbuckle.AspNetCore6; + +public class WeatherForecast +{ + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(this.TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/appsettings.Development.json b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/appsettings.json b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From b763c2bba3f55cfec9ddc1a4476e6588c3a4afa8 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 12 Mar 2022 17:40:40 +0800 Subject: [PATCH 180/424] refactor --- .../Controllers/EmployeeController.cs | 47 +++++++++++++++++++ .../Controllers/WeatherForecastController.cs | 32 ------------- .../EmployeeResponse.cs | 12 +++++ .../Lab.Swashbuckle.AspNetCore6/Program.cs | 26 ++++++++-- .../QueryEmployeeRequest.cs | 19 ++++++++ .../WeatherForecast.cs | 12 ----- 6 files changed, 100 insertions(+), 48 deletions(-) create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs delete mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/WeatherForecastController.cs create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/EmployeeResponse.cs create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/QueryEmployeeRequest.cs delete mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/WeatherForecast.cs diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs new file mode 100644 index 00000000..2e78a73f --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.Swashbuckle.AspNetCore6.Controllers; + +[ApiController] +[Route("[controller]")] +public class EmployeeController : ControllerBase +{ + private static readonly string[] Summaries = + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public EmployeeController(ILogger logger) + { + this._logger = logger; + } + + // [HttpGet(Name = "GetEmployee")] + [HttpGet] + public async Task Get(QueryEmployeeRequest request) + { + if (this.ModelState.IsValid == false) + { + return this.BadRequest(); + } + + return this.Ok(new List + { + new() + { + Id = Guid.NewGuid(), + Name = "yao", + Age = 20 + }, + new() + { + Id = Guid.NewGuid(), + Name = "小章", + Age = 18, + Remark = "說明" + } + }); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/WeatherForecastController.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/WeatherForecastController.cs deleted file mode 100644 index 539c6b34..00000000 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace Lab.Swashbuckle.AspNetCore6.Controllers; - -[ApiController] -[Route("[controller]")] -public class WeatherForecastController : ControllerBase -{ - private static readonly string[] Summaries = - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - this._logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateTime.Now.AddDays(index), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } -} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/EmployeeResponse.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/EmployeeResponse.cs new file mode 100644 index 00000000..58a58d54 --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/EmployeeResponse.cs @@ -0,0 +1,12 @@ +namespace Lab.Swashbuckle.AspNetCore6; + +public class EmployeeResponse +{ + public Guid Id { get; set; } + + public string Name { get; set; } + + public int Age { get; set; } + + public string Remark { get; set; } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs index f046f113..680dfdc1 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs @@ -1,5 +1,6 @@ using System.Reflection; using System.Xml.XPath; +using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; void IncludeXmlComments(Assembly assembly, SwaggerGenOptions swaggerGenOptions) @@ -42,11 +43,28 @@ void IncludeXmlComments(Assembly assembly, SwaggerGenOptions swaggerGenOptions) // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(c => +builder.Services.AddSwaggerGen(options => { - var assembly = typeof(Program).Assembly; - - IncludeXmlComments(assembly, c); + options.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "ToDo API", + Description = "An ASP.NET Core Web API for managing ToDo items", + TermsOfService = new Uri("https://example.com/terms"), + Contact = new OpenApiContact + { + Name = "Example Contact", + Url = new Uri("https://example.com/contact") + }, + License = new OpenApiLicense + { + Name = "Example License", + Url = new Uri("https://example.com/license") + } + }); + + var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); }); var app = builder.Build(); diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/QueryEmployeeRequest.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/QueryEmployeeRequest.cs new file mode 100644 index 00000000..c91fe73f --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/QueryEmployeeRequest.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; + +namespace Lab.Swashbuckle.AspNetCore6; + +public class QueryEmployeeRequest +{ + /// + /// 姓名 + /// + /// 小章 + [Required] + public string Name { get; set; } + + /// + /// 年齡 + /// + /// 18 + public int Age { get; set; } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/WeatherForecast.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/WeatherForecast.cs deleted file mode 100644 index e896017e..00000000 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/WeatherForecast.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Lab.Swashbuckle.AspNetCore6; - -public class WeatherForecast -{ - public DateTime Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(this.TemperatureC / 0.5556); - - public string? Summary { get; set; } -} \ No newline at end of file From f7c6739caf51e6cbf296bfd964b4600547a9db71 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 12 Mar 2022 21:37:50 +0800 Subject: [PATCH 181/424] add api info --- .../Controllers/EmployeeController.cs | 14 ++++++++++++++ .../Lab.Swashbuckle.AspNetCore6.csproj | 11 ++++++++++- .../Lab.Swashbuckle.AspNetCore6/Program.cs | 15 ++++++++++++--- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs index 2e78a73f..7d75e956 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs @@ -18,6 +18,20 @@ public EmployeeController(ILogger logger) this._logger = logger; } + /// + /// 取得會員 + /// + /// + /// + /// Sample request: + /// + /// POST /Todo + /// { + /// "id": 1, + /// "name": "Item #1", + /// "isComplete": true + /// } + /// // [HttpGet(Name = "GetEmployee")] [HttpGet] public async Task Get(QueryEmployeeRequest request) diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj index 257d3e70..4ad950f2 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj @@ -9,7 +9,16 @@ - + + + + + + + + + + diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs index 680dfdc1..87f39d14 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs @@ -48,8 +48,8 @@ void IncludeXmlComments(Assembly assembly, SwaggerGenOptions swaggerGenOptions) options.SwaggerDoc("v1", new OpenApiInfo { Version = "v1", - Title = "ToDo API", - Description = "An ASP.NET Core Web API for managing ToDo items", + Title = "Employee API", + Description = "An ASP.NET Core Web API for managing employees", TermsOfService = new Uri("https://example.com/terms"), Contact = new OpenApiContact { @@ -62,6 +62,11 @@ void IncludeXmlComments(Assembly assembly, SwaggerGenOptions swaggerGenOptions) Url = new Uri("https://example.com/license") } }); + options.SwaggerDoc("v2", new OpenApiInfo + { + Version = "v2", + Title = "Employee API", + }); var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); @@ -73,7 +78,11 @@ void IncludeXmlComments(Assembly assembly, SwaggerGenOptions swaggerGenOptions) if (app.Environment.IsDevelopment()) { app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwaggerUI(options=> + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); + options.SwaggerEndpoint("/swagger/v2/swagger.json", "v2"); + }); } app.UseHttpsRedirection(); From 19ebd71111207c19c671b251cc0edab31bdeba87 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 12 Mar 2022 22:43:48 +0800 Subject: [PATCH 182/424] add example --- .../Controllers/EmployeeController.cs | 9 +++++++++ .../EmployeeResponse.cs | 12 ++++++++++++ .../Examples/EmployeeResponseExample.cs | 17 +++++++++++++++++ .../Examples/QueryEmployeeRequestExample.cs | 15 +++++++++++++++ .../Lab.Swashbuckle.AspNetCore6.csproj | 11 ++--------- .../Lab.Swashbuckle.AspNetCore6/Program.cs | 5 ++++- 6 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Examples/EmployeeResponseExample.cs create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Examples/QueryEmployeeRequestExample.cs diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs index 7d75e956..8be2ffb6 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs @@ -1,4 +1,7 @@ +using Lab.Swashbuckle.AspNetCore6.Examples; using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.Filters; namespace Lab.Swashbuckle.AspNetCore6.Controllers; @@ -32,8 +35,14 @@ public EmployeeController(ILogger logger) /// "isComplete": true /// } /// + // [HttpGet(Name = "GetEmployee")] [HttpGet] + [Produces("application/json")] + // [ProducesResponseType(typeof(EmployeeResponse), StatusCodes.Status200OK)] + // [SwaggerResponse(200, "The list of countries", typeof(IEnumerable))] + [SwaggerRequestExample(typeof(QueryEmployeeRequest), typeof(QueryEmployeeRequestExample))] + [SwaggerResponseExample(200, typeof(EmployeeResponseExample))] public async Task Get(QueryEmployeeRequest request) { if (this.ModelState.IsValid == false) diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/EmployeeResponse.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/EmployeeResponse.cs index 58a58d54..19f88949 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/EmployeeResponse.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/EmployeeResponse.cs @@ -2,11 +2,23 @@ namespace Lab.Swashbuckle.AspNetCore6; public class EmployeeResponse { + /// + /// 編號 + /// public Guid Id { get; set; } + /// + /// 姓名 + /// public string Name { get; set; } + /// + /// 年齡 + /// public int Age { get; set; } + /// + /// 註解 + /// public string Remark { get; set; } } \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Examples/EmployeeResponseExample.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Examples/EmployeeResponseExample.cs new file mode 100644 index 00000000..f0e56706 --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Examples/EmployeeResponseExample.cs @@ -0,0 +1,17 @@ +using Swashbuckle.AspNetCore.Filters; + +namespace Lab.Swashbuckle.AspNetCore6.Examples; + +public class EmployeeResponseExample : IExamplesProvider +{ + public EmployeeResponse GetExamples() + { + return new EmployeeResponse + { + Id = Guid.Parse("00000000-0000-0000-0000-000000000001"), + Name = "小章", + Age = 18, + Remark = "說明" + }; + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Examples/QueryEmployeeRequestExample.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Examples/QueryEmployeeRequestExample.cs new file mode 100644 index 00000000..51b1a09c --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Examples/QueryEmployeeRequestExample.cs @@ -0,0 +1,15 @@ +using Swashbuckle.AspNetCore.Filters; + +namespace Lab.Swashbuckle.AspNetCore6.Examples; + +public class QueryEmployeeRequestExample : IExamplesProvider +{ + public QueryEmployeeRequest GetExamples() + { + return new QueryEmployeeRequest + { + Name = "小章", + Age = 18 + }; + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj index 4ad950f2..3da1ae7f 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj @@ -10,15 +10,8 @@ - - - - - - - - - + + diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs index 87f39d14..8a2990d9 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs @@ -1,6 +1,7 @@ using System.Reflection; using System.Xml.XPath; using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.Filters; using Swashbuckle.AspNetCore.SwaggerGen; void IncludeXmlComments(Assembly assembly, SwaggerGenOptions swaggerGenOptions) @@ -67,10 +68,12 @@ void IncludeXmlComments(Assembly assembly, SwaggerGenOptions swaggerGenOptions) Version = "v2", Title = "Employee API", }); - + options.ExampleFilters(); + var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); }); +builder.Services.AddSwaggerExamplesFromAssemblies(Assembly.GetEntryAssembly()); var app = builder.Build(); From 5c627f5ec69afa78e5027c394da0555bbc968f0c Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 12 Mar 2022 23:19:04 +0800 Subject: [PATCH 183/424] add JsonStringEnumMemberConverter --- .../Controllers/EmployeeController.cs | 2 +- .../EmployeeResponse.cs | 8 +++--- .../Examples/QueryEmployeeRequestExample.cs | 3 ++- .../Lab.Swashbuckle.AspNetCore6.csproj | 7 ++--- .../Lab.Swashbuckle.AspNetCore6/Program.cs | 10 +++---- .../QueryEmployeeRequest.cs | 27 +++++++++++++++++-- 6 files changed, 41 insertions(+), 16 deletions(-) diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs index 8be2ffb6..bee2d16e 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Controllers/EmployeeController.cs @@ -40,7 +40,7 @@ public EmployeeController(ILogger logger) [HttpGet] [Produces("application/json")] // [ProducesResponseType(typeof(EmployeeResponse), StatusCodes.Status200OK)] - // [SwaggerResponse(200, "The list of countries", typeof(IEnumerable))] + [SwaggerResponse(200, "查詢結果", typeof(EmployeeResponse))] [SwaggerRequestExample(typeof(QueryEmployeeRequest), typeof(QueryEmployeeRequestExample))] [SwaggerResponseExample(200, typeof(EmployeeResponseExample))] public async Task Get(QueryEmployeeRequest request) diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/EmployeeResponse.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/EmployeeResponse.cs index 19f88949..99f842b9 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/EmployeeResponse.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/EmployeeResponse.cs @@ -3,22 +3,22 @@ namespace Lab.Swashbuckle.AspNetCore6; public class EmployeeResponse { /// - /// 編號 + /// 編號 /// public Guid Id { get; set; } /// - /// 姓名 + /// 姓名 /// public string Name { get; set; } /// - /// 年齡 + /// 年齡 /// public int Age { get; set; } /// - /// 註解 + /// 註解 /// public string Remark { get; set; } } \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Examples/QueryEmployeeRequestExample.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Examples/QueryEmployeeRequestExample.cs index 51b1a09c..44932cf9 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Examples/QueryEmployeeRequestExample.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Examples/QueryEmployeeRequestExample.cs @@ -9,7 +9,8 @@ public QueryEmployeeRequest GetExamples() return new QueryEmployeeRequest { Name = "小章", - Age = 18 + Age = 18, + // State = (State)1 }; } } \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj index 3da1ae7f..bc9b1642 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj @@ -9,9 +9,10 @@ - - - + + + + diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs index 8a2990d9..61e705bd 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/Program.cs @@ -66,10 +66,10 @@ void IncludeXmlComments(Assembly assembly, SwaggerGenOptions swaggerGenOptions) options.SwaggerDoc("v2", new OpenApiInfo { Version = "v2", - Title = "Employee API", - }); + Title = "Employee API" + }); options.ExampleFilters(); - + var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); }); @@ -81,10 +81,10 @@ void IncludeXmlComments(Assembly assembly, SwaggerGenOptions swaggerGenOptions) if (app.Environment.IsDevelopment()) { app.UseSwagger(); - app.UseSwaggerUI(options=> + app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); - options.SwaggerEndpoint("/swagger/v2/swagger.json", "v2"); + options.SwaggerEndpoint("/swagger/v2/swagger.json", "v2"); }); } diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/QueryEmployeeRequest.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/QueryEmployeeRequest.cs index c91fe73f..c4cf8982 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/QueryEmployeeRequest.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/QueryEmployeeRequest.cs @@ -1,19 +1,42 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; namespace Lab.Swashbuckle.AspNetCore6; public class QueryEmployeeRequest { /// - /// 姓名 + /// 姓名 /// /// 小章 [Required] public string Name { get; set; } /// - /// 年齡 + /// 年齡 /// /// 18 public int Age { get; set; } + + /// + /// 狀態 + /// + /// 1 + public State State { get; set; } +} + +// [JsonConverter(typeof(JsonStringEnumMemberConverter))] // This custom converter was placed in a system namespace. +public enum State +{ + None = 0, + + /// + /// Approved + /// + Approved = 1, + + /// + /// Rejected + /// + Rejected = 2 } \ No newline at end of file From db68b2460da77b7e9df0c1f6b85409fa0ed1b4e8 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 12 Mar 2022 23:33:43 +0800 Subject: [PATCH 184/424] enum to string --- .../QueryEmployeeRequest.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/QueryEmployeeRequest.cs b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/QueryEmployeeRequest.cs index c4cf8982..48f021d9 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/QueryEmployeeRequest.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc/Lab.Swashbuckle.AspNetCore6/QueryEmployeeRequest.cs @@ -1,4 +1,6 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; using System.Text.Json.Serialization; namespace Lab.Swashbuckle.AspNetCore6; @@ -25,18 +27,24 @@ public class QueryEmployeeRequest public State State { get; set; } } -// [JsonConverter(typeof(JsonStringEnumMemberConverter))] // This custom converter was placed in a system namespace. +[JsonConverter(typeof(JsonStringEnumMemberConverter))] // This custom converter was placed in a system namespace. public enum State { + [EnumMember(Value = "UNKNOWN_DEFINITION_000")] + None = 0, /// /// Approved /// + /// Approved + // [Description("Approved")] + [EnumMember(Value = "Approved")] Approved = 1, /// /// Rejected /// + [EnumMember(Value = "Rejected")] Rejected = 2 } \ No newline at end of file From 655b2fdbcdc892d5fb0dae70e135d1e962769f71 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 13 Mar 2022 18:53:45 +0800 Subject: [PATCH 185/424] feat: add initial project --- .../Lab.SwaggerDoc.MultiVersion.sln | 16 +++ .../Employee/v1_0/DemoController.cs | 19 ++++ .../Employee/v1_1/DemoController.cs | 19 ++++ .../Lab.Swashbuckle.AspNetCore6.csproj | 23 +++++ .../Lab.Swashbuckle.AspNetCore6/Program.cs | 97 +++++++++++++++++++ .../Properties/launchSettings.json | 31 ++++++ .../appsettings.Development.json | 8 ++ .../appsettings.json | 9 ++ 8 files changed, 222 insertions(+) create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.SwaggerDoc.MultiVersion.sln create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_0/DemoController.cs create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_1/DemoController.cs create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Program.cs create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Properties/launchSettings.json create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/appsettings.Development.json create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/appsettings.json diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.SwaggerDoc.MultiVersion.sln b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.SwaggerDoc.MultiVersion.sln new file mode 100644 index 00000000..59eaf419 --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.SwaggerDoc.MultiVersion.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Swashbuckle.AspNetCore6", "Lab.Swashbuckle.AspNetCore6\Lab.Swashbuckle.AspNetCore6.csproj", "{2DA608D5-BC95-4DF6-AB7A-0A22085E9257}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2DA608D5-BC95-4DF6-AB7A-0A22085E9257}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DA608D5-BC95-4DF6-AB7A-0A22085E9257}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DA608D5-BC95-4DF6-AB7A-0A22085E9257}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DA608D5-BC95-4DF6-AB7A-0A22085E9257}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_0/DemoController.cs b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_0/DemoController.cs new file mode 100644 index 00000000..cf1b00bd --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_0/DemoController.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.Swashbuckle.AspNetCore6.Controllers.Employee.v1_0; + +[ApiVersion("1.0", Deprecated = true)] +[ApiController] +[Route("api/[controller]")] +public class DemoController : ControllerBase +{ + [HttpGet] + public IActionResult Get() + { + return this.Ok(new + { + Version = 1.0, + Name = "1.0" + }); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_1/DemoController.cs b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_1/DemoController.cs new file mode 100644 index 00000000..9042a16a --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_1/DemoController.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.Swashbuckle.AspNetCore6.Controllers.Employee.v1_1; + +[ApiVersion("1.1")] +[ApiController] +[Route("api/[controller]")] +public class DemoController : ControllerBase +{ + [HttpGet] + public IActionResult Get() + { + return this.Ok(new + { + Version = 1.1, + Name = "1.1" + }); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj new file mode 100644 index 00000000..c2f22baf --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Lab.Swashbuckle.AspNetCore6.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + enable + bin\ + bin\Lab.Swashbuckle.AspNetCore6.xml + + + + + + + + + + + + + + + diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Program.cs b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Program.cs new file mode 100644 index 00000000..a4fa85c6 --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Program.cs @@ -0,0 +1,97 @@ +using System.Reflection; +using System.Xml.XPath; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Versioning; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.Filters; +using Swashbuckle.AspNetCore.SwaggerGen; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(options => +{ + options.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "Employee API", + Description = "An ASP.NET Core Web API for managing employees", + TermsOfService = new Uri("https://example.com/terms"), + Contact = new OpenApiContact + { + Name = "Example Contact", + Url = new Uri("https://example.com/contact") + }, + License = new OpenApiLicense + { + Name = "Example License", + Url = new Uri("https://example.com/license") + } + }); + options.SwaggerDoc("v2", new OpenApiInfo + { + Version = "v2", + Title = "Employee API" + }); + options.ExampleFilters(); + + var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); +}); +builder.Services.AddSwaggerExamplesFromAssemblies(Assembly.GetEntryAssembly()); +builder.Services.AddApiVersioning(option => +{ + //返回響應標頭中支援的版本資訊 + option.ReportApiVersions = true; + + //未提供版本請請時,使用預設版號 + option.AssumeDefaultVersionWhenUnspecified = true; + + //預設api版本號,支援時間或數字版本號 + option.DefaultApiVersion = new ApiVersion(1, 0); + + //支援MediaType、Header、QueryString 設定版本號;預設為 QueryString、UrlSegment + option.ApiVersionReader = ApiVersionReader.Combine( + new MediaTypeApiVersionReader("api-version"), + new HeaderApiVersionReader("api-version"), + new QueryStringApiVersionReader("api-version"), + new UrlSegmentApiVersionReader()); +}); + +// builder.Services.AddVersionedApiExplorer(options => +// { +// // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service +// // note: the specified format code will format the version as "'v'major[.minor][-status]" +// options.GroupNameFormat = "'v'VVV"; +// +// // note: this option is only necessary when versioning by url segment. the SubstitutionFormat +// // can also be used to control the format of the API version in route templates +// options.SubstituteApiVersionInUrl = true; +// }); +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); + options.SwaggerEndpoint("/swagger/v2/swagger.json", "v2"); + }); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.UseApiVersioning(); + +app.Run(); \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Properties/launchSettings.json b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Properties/launchSettings.json new file mode 100644 index 00000000..a270a625 --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:51786", + "sslPort": 44377 + } + }, + "profiles": { + "Lab.Swashbuckle.AspNetCore6": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7236;http://localhost:5236", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/appsettings.Development.json b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/appsettings.json b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From 78ac76b6e1ab9a686414d439f450b6d23e5a43b2 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 13 Mar 2022 20:29:33 +0800 Subject: [PATCH 186/424] feat: set UrlSegmentApiVersionReader --- .../Controllers/Employee/v1_0/DemoController.cs | 1 + .../Controllers/Employee/v1_1/DemoController.cs | 1 + .../Lab.Swashbuckle.AspNetCore6/Program.cs | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_0/DemoController.cs b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_0/DemoController.cs index cf1b00bd..2bd788c8 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_0/DemoController.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_0/DemoController.cs @@ -5,6 +5,7 @@ namespace Lab.Swashbuckle.AspNetCore6.Controllers.Employee.v1_0; [ApiVersion("1.0", Deprecated = true)] [ApiController] [Route("api/[controller]")] +[Route("api/v{version:apiVersion}/[controller]")] public class DemoController : ControllerBase { [HttpGet] diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_1/DemoController.cs b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_1/DemoController.cs index 9042a16a..e46c81e4 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_1/DemoController.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_1/DemoController.cs @@ -5,6 +5,7 @@ namespace Lab.Swashbuckle.AspNetCore6.Controllers.Employee.v1_1; [ApiVersion("1.1")] [ApiController] [Route("api/[controller]")] +[Route("api/v{version:apiVersion}/[controller]")] public class DemoController : ControllerBase { [HttpGet] diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Program.cs b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Program.cs index a4fa85c6..559b4593 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Program.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Program.cs @@ -50,12 +50,12 @@ option.ReportApiVersions = true; //未提供版本請請時,使用預設版號 - option.AssumeDefaultVersionWhenUnspecified = true; + option.AssumeDefaultVersionWhenUnspecified = false; //預設api版本號,支援時間或數字版本號 option.DefaultApiVersion = new ApiVersion(1, 0); - //支援MediaType、Header、QueryString 設定版本號;預設為 QueryString、UrlSegment + //支援MediaType、Header、QueryString 設定版本號 option.ApiVersionReader = ApiVersionReader.Combine( new MediaTypeApiVersionReader("api-version"), new HeaderApiVersionReader("api-version"), From 84cbfe2af6ad9314c97b1495c538183d009629b8 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 13 Mar 2022 21:28:00 +0800 Subject: [PATCH 187/424] =?UTF-8?q?feat:=20Swagger=20UI=20=E5=A5=97?= =?UTF-8?q?=E7=94=A8=E5=A4=9A=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Lab.SwaggerDoc.MultiVersion.sln | 6 ++ .../ConfigureApiVersionSwaggerGenOptions.cs | 55 +++++++++++++++ .../Employee/v1_0/DemoController.cs | 2 +- .../Employee/v1_1/DemoController.cs | 2 +- .../Lab.Swashbuckle.AspNetCore6/Program.cs | 67 +++++++++---------- 5 files changed, 93 insertions(+), 39 deletions(-) create mode 100644 WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/ConfigureApiVersionSwaggerGenOptions.cs diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.SwaggerDoc.MultiVersion.sln b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.SwaggerDoc.MultiVersion.sln index 59eaf419..db633377 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.SwaggerDoc.MultiVersion.sln +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.SwaggerDoc.MultiVersion.sln @@ -2,6 +2,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Swashbuckle.AspNetCore6", "Lab.Swashbuckle.AspNetCore6\Lab.Swashbuckle.AspNetCore6.csproj", "{2DA608D5-BC95-4DF6-AB7A-0A22085E9257}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiVersioningSample", "..\..\..\..\lab\blogsamples\ApiVersioningSample\ApiVersioningSample.csproj", "{D3DAD3B2-CA83-40E0-90B7-BF005C7C7210}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -12,5 +14,9 @@ Global {2DA608D5-BC95-4DF6-AB7A-0A22085E9257}.Debug|Any CPU.Build.0 = Debug|Any CPU {2DA608D5-BC95-4DF6-AB7A-0A22085E9257}.Release|Any CPU.ActiveCfg = Release|Any CPU {2DA608D5-BC95-4DF6-AB7A-0A22085E9257}.Release|Any CPU.Build.0 = Release|Any CPU + {D3DAD3B2-CA83-40E0-90B7-BF005C7C7210}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3DAD3B2-CA83-40E0-90B7-BF005C7C7210}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3DAD3B2-CA83-40E0-90B7-BF005C7C7210}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3DAD3B2-CA83-40E0-90B7-BF005C7C7210}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/ConfigureApiVersionSwaggerGenOptions.cs b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/ConfigureApiVersionSwaggerGenOptions.cs new file mode 100644 index 00000000..a401bd5f --- /dev/null +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/ConfigureApiVersionSwaggerGenOptions.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Lab.Swashbuckle.AspNetCore6; + +public class ConfigureApiVersionSwaggerGenOptions : IConfigureOptions +{ + private readonly IApiVersionDescriptionProvider _provider; + + public ConfigureApiVersionSwaggerGenOptions(IApiVersionDescriptionProvider provider) + { + _provider = provider; + } + + public void Configure(SwaggerGenOptions options) + { + foreach (var description in _provider.ApiVersionDescriptions) + { + options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)); + } + } + + private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description) + { + //產生 API 資訊 + var info = new OpenApiInfo + { + Version = description.ApiVersion.ToString(), + Title = "Employee API", + Description = + @"

Sample API with versioning including Swagger.

Partly taken from this repository.

", + TermsOfService = new Uri("https://example.com/terms"), + Contact = new OpenApiContact + { + Name = "Example Contact", + Url = new Uri("https://example.com/contact") + }, + License = new OpenApiLicense + { + Name = "Example License", + Url = new Uri("https://example.com/license") + } + }; + + if (description.IsDeprecated) + { + info.Description += + @"

VERSION IS DEPRECATED

"; + } + + return info; + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_0/DemoController.cs b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_0/DemoController.cs index 2bd788c8..8c32b471 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_0/DemoController.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_0/DemoController.cs @@ -4,7 +4,7 @@ namespace Lab.Swashbuckle.AspNetCore6.Controllers.Employee.v1_0; [ApiVersion("1.0", Deprecated = true)] [ApiController] -[Route("api/[controller]")] +// [Route("api/[controller]")] [Route("api/v{version:apiVersion}/[controller]")] public class DemoController : ControllerBase { diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_1/DemoController.cs b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_1/DemoController.cs index e46c81e4..7c29fdeb 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_1/DemoController.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Controllers/Employee/v1_1/DemoController.cs @@ -4,7 +4,7 @@ namespace Lab.Swashbuckle.AspNetCore6.Controllers.Employee.v1_1; [ApiVersion("1.1")] [ApiController] -[Route("api/[controller]")] +// [Route("api/[controller]")] [Route("api/v{version:apiVersion}/[controller]")] public class DemoController : ControllerBase { diff --git a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Program.cs b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Program.cs index 559b4593..8158e877 100644 --- a/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Program.cs +++ b/WebAPI/Swagger/Lab.SwaggerDoc.MultiVersion/Lab.Swashbuckle.AspNetCore6/Program.cs @@ -1,7 +1,10 @@ using System.Reflection; using System.Xml.XPath; +using Lab.Swashbuckle.AspNetCore6; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Versioning; +using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.Filters; using Swashbuckle.AspNetCore.SwaggerGen; @@ -16,28 +19,6 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { - options.SwaggerDoc("v1", new OpenApiInfo - { - Version = "v1", - Title = "Employee API", - Description = "An ASP.NET Core Web API for managing employees", - TermsOfService = new Uri("https://example.com/terms"), - Contact = new OpenApiContact - { - Name = "Example Contact", - Url = new Uri("https://example.com/contact") - }, - License = new OpenApiLicense - { - Name = "Example License", - Url = new Uri("https://example.com/license") - } - }); - options.SwaggerDoc("v2", new OpenApiInfo - { - Version = "v2", - Title = "Employee API" - }); options.ExampleFilters(); var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; @@ -63,27 +44,39 @@ new UrlSegmentApiVersionReader()); }); -// builder.Services.AddVersionedApiExplorer(options => -// { -// // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service -// // note: the specified format code will format the version as "'v'major[.minor][-status]" -// options.GroupNameFormat = "'v'VVV"; -// -// // note: this option is only necessary when versioning by url segment. the SubstitutionFormat -// // can also be used to control the format of the API version in route templates -// options.SubstituteApiVersionInUrl = true; -// }); +builder.Services.AddVersionedApiExplorer(options => +{ + + // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service + // note: the specified format code will format the version as "'v'major[.minor][-status]" + //options.GroupNameFormat = "'v'VVV"; + + // note: this option is only necessary when versioning by url segment. the SubstitutionFormat + // can also be used to control the format of the API version in route templates + //options.SubstituteApiVersionInUrl = true; +}); +builder.Services.AddSingleton, ConfigureApiVersionSwaggerGenOptions>(); + var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); - app.UseSwaggerUI(options => - { - options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); - options.SwaggerEndpoint("/swagger/v2/swagger.json", "v2"); - }); + + app.UseSwaggerUI( + options => + { + var provider = app.Services.GetService(); + + // build a swagger endpoint for each discovered API version + foreach (var description in provider.ApiVersionDescriptions) + { + var url = $"/swagger/{description.GroupName}/swagger.json"; + options.SwaggerEndpoint(url, + description.GroupName.ToUpperInvariant()); + } + }); } app.UseHttpsRedirection(); From 5d9e8a45c8ad0bfe77e766d8e6fa1232b84db500 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 23 Mar 2022 00:13:35 +0800 Subject: [PATCH 188/424] first project --- .../ChangeTrackVersion/ChangeTrackVersion.sln | 41 ++++ .../ChangeTrackVersion/Makefile | 4 + .../ChangeTrackVersion/docker-compose.yml | 10 + .../ChangeTrackingUnitTest.cs | 190 ++++++++++++++++++ .../Lab.ChangeTracking.Domain.UnitTest.csproj | 28 +++ .../MsTestHook.cs | 31 +++ .../TestAssistants.cs | 59 ++++++ .../AccessContext.cs | 14 ++ .../Lab.ChangeTracking.Domain/ChangeState.cs | 8 + .../Lab.ChangeTracking.Domain/CommitState.cs | 9 + .../EmployeeAggregate/EmployeeAggregate.cs | 187 +++++++++++++++++ .../EmployeeAggregate/Entity/AddressEntity.cs | 24 +++ .../Entity/EmployeeEntity.cs | 30 +++ .../Entity/IdentityEntity.cs | 20 ++ .../EmployeeAggregate/IEmployeeAggregate.cs | 1 + .../Repository/EmployeeRepository.cs | 41 ++++ .../Repository/IEmployeeRepository.cs | 8 + .../Repository/TypeConverterExtensions.cs | 145 +++++++++++++ .../Lab.ChangeTracking.Domain/EntityBase.cs | 140 +++++++++++++ .../src/Lab.ChangeTracking.Domain/Error.cs | 3 + .../IChangeContent.cs | 8 + .../Lab.ChangeTracking.Domain/IChangeState.cs | 10 + .../Lab.ChangeTracking.Domain/IChangeTime.cs | 16 ++ .../IChangeTrackable.cs | 12 ++ .../Lab.ChangeTracking.Domain/ISystemClock.cs | 14 ++ .../IUUIdProvider.cs | 14 ++ .../Lab.ChangeTracking.Domain.csproj | 17 ++ .../TypeConverterExtension.cs | 114 +++++++++++ .../AppDependencyInjectionExtensions.cs | 48 +++++ .../AppEnvironmentOption.cs | 32 +++ .../EntityModel/Address.cs | 29 +++ .../EntityModel/Employee.cs | 34 ++++ .../EntityModel/EmployeeDbContext.cs | 120 +++++++++++ .../EntityModel/Identity.cs | 32 +++ .../EnvironmentAssistant.cs | 15 ++ ...ab.ChangeTracking.Infrastructure.DB.csproj | 16 ++ 36 files changed, 1524 insertions(+) create mode 100644 Property Change Tracking/ChangeTrackVersion/ChangeTrackVersion.sln create mode 100644 Property Change Tracking/ChangeTrackVersion/Makefile create mode 100644 Property Change Tracking/ChangeTrackVersion/docker-compose.yml create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/AccessContext.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/ChangeState.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/CommitState.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/AddressEntity.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/TypeConverterExtensions.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EntityBase.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/Error.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeContent.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeState.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeTime.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/ISystemClock.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IUUIdProvider.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/TypeConverterExtension.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs create mode 100644 Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj diff --git a/Property Change Tracking/ChangeTrackVersion/ChangeTrackVersion.sln b/Property Change Tracking/ChangeTrackVersion/ChangeTrackVersion.sln new file mode 100644 index 00000000..1c868fe3 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/ChangeTrackVersion.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{1F776B7D-C182-4DC3-BA57-844657BD9AC0}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + Makefile = Makefile + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{483A9EB7-C4AF-4D68-80ED-49277985C760}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Infrastructure.DB", "src\Lab.ChangeTracking.Infrastructure.DB\Lab.ChangeTracking.Infrastructure.DB.csproj", "{A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Domain", "src\Lab.ChangeTracking.Domain\Lab.ChangeTracking.Domain.csproj", "{A1C827F2-683E-470C-A3DE-BD6DD2BE5198}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ChangeTracking.Domain.UnitTest", "src\Lab.ChangeTracking.Domain.UnitTest\Lab.ChangeTracking.Domain.UnitTest.csproj", "{7066EE2C-28A8-4408-B212-E582608AB967}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C}.Release|Any CPU.Build.0 = Release|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198}.Release|Any CPU.Build.0 = Release|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7066EE2C-28A8-4408-B212-E582608AB967}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A2FDDEA2-F3BD-43B2-96FE-B12EA5BE909C} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {A1C827F2-683E-470C-A3DE-BD6DD2BE5198} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + {7066EE2C-28A8-4408-B212-E582608AB967} = {483A9EB7-C4AF-4D68-80ED-49277985C760} + EndGlobalSection +EndGlobal diff --git a/Property Change Tracking/ChangeTrackVersion/Makefile b/Property Change Tracking/ChangeTrackVersion/Makefile new file mode 100644 index 00000000..cf17f07f --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/Makefile @@ -0,0 +1,4 @@ +G1: + echo 'Hello World' +G2: + cmd \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/docker-compose.yml b/Property Change Tracking/ChangeTrackVersion/docker-compose.yml new file mode 100644 index 00000000..8ecb1567 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.8" + +services: + db-sql: + image: mcr.microsoft.com/mssql/server:2019-latest + environment: + - ACCEPT_EULA=Y + - SA_PASSWORD=pass@w0rd1~ + ports: + - 1433:1433 \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs new file mode 100644 index 00000000..8c989b04 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/ChangeTrackingUnitTest.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Unicode; +using EFCore.BulkExtensions; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Lab.MultiTestCase.UnitTest; +using Microsoft.EntityFrameworkCore; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.ChangeTracking.Domain.UnitTest; + +[TestClass] +public class ChangeTrackingUnitTest +{ + private static readonly IAccessContext _accessContext = TestAssistants.AccessContext; + + private static readonly IUUIdProvider _uuIdProvider = TestAssistants.UUIdProvider; + + private static readonly IDbContextFactory s_employeeDbContextFactory = + TestAssistants.EmployeeDbContextFactory; + + private readonly EmployeeAggregate _employeeAggregate = TestAssistants.EmployeeAggregate; + + private readonly IEmployeeRepository _employeeRepository = TestAssistants.EmployeeRepository; + + private readonly ISystemClock _systemClock = TestAssistants.SystemClock; + + [ClassCleanup] + public static void ClassCleanup() + { + DeleteAllTable(); + } + + [ClassInitialize] + public static void ClassInitialize(TestContext context) + { + DeleteAllTable(); + } + + [TestMethod] + public void 刪除一筆資料() + { + // var fromDb = Insert(); + // var employeeEntity = new EmployeeValueObject(); + // employeeEntity.AsTrackable(fromDb) + // .SetDelete() + // .AcceptChanges(this._systemClock, _accessContext, _uuIdProvider); + // + // var count = this._employeeRepository.SaveChangeAsync(employeeEntity).Result; + // + // Assert.AreEqual(1, count); + // var dbContext = s_employeeDbContextFactory.CreateDbContext(); + // + // var actual = dbContext.Employees + // .Where(p => p.Id == fromDb.Id) + // .Include(p => p.Identity) + // .Include(p => p.Addresses) + // .FirstOrDefault() + // ; + // Assert.AreEqual(null, actual); + } + + [TestMethod] + public void 更新一筆資料() + { + var fromDb = Insert(); + var target = this._employeeAggregate; + target.SetEntity(fromDb.To()) + .SetProfile("小章", 28); + target.AcceptChanges(); + var result = target.CommitChangeAsync().Result; + } + + [TestMethod] + public void 沒有異動() + { + // var fromDb = Insert(); + // var employeeEntity = new EmployeeValueObject(); + // employeeEntity.AsTrackable(fromDb) + // .SetProfile("小章", 19, "新來的") + // .SetProfile("yao", 18, "編輯") + // .AcceptChanges(this._systemClock, _accessContext, _uuIdProvider); + // + // var count = this._employeeRepository.SaveChangeAsync(employeeEntity).Result; + // Assert.AreEqual(0, count); + } + + [TestMethod] + public void 新增() + { + var target = this._employeeAggregate; + target.NewEmployee("yao", 18); + target.AcceptChanges(); + var result = target.CommitChangeAsync().Result; + } + + [TestMethod] + public void 新增一筆資料() + { + // var employeeEntity = new EmployeeValueObject(); + // employeeEntity.New("yao", 10, "新的") + // .AcceptChanges(this._systemClock, _accessContext, _uuIdProvider); + // + // var count = this._employeeRepository.SaveChangeAsync(employeeEntity).Result; + // + // Assert.AreEqual(1, count); + // var dbContext = s_employeeDbContextFactory.CreateDbContext(); + // + // var actual = dbContext.Employees + // .Where(p => p.Id == employeeEntity.Id) + // .Include(p => p.Identity) + // .Include(p => p.Addresses) + // .First() + // ; + // Assert.AreEqual("yao", actual.Name); + // Assert.AreEqual(10, actual.Age); + // Assert.AreEqual("新的", actual.Remark); + } + + private static void DeleteAllTable() + { + var dbContext = s_employeeDbContextFactory.CreateDbContext(); + dbContext.Employees.BatchDelete(); + dbContext.Addresses.BatchDelete(); + dbContext.Identity.BatchDelete(); + } + + private static Employee Insert() + { + using var dbContext = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + var employeeId = Guid.NewGuid(); + var toDB = new Employee + { + Id = employeeId, + Age = 18, + Name = "yao", + CreatedAt = DateTimeOffset.Now, + CreatedBy = "Sys", + Remark = "編輯", + Identity = new Identity + { + Employee_Id = employeeId, + Account = "yao", + Password = "123456", + CreatedAt = DateTimeOffset.Now, + CreatedBy = "Sys", + Remark = "編輯" + }, + Addresses = new List
+ { + new() + { + Id = Guid.NewGuid(), + Employee_Id = employeeId, + CreatedAt = DateTimeOffset.Now, + CreatedBy = "sys", + Country = "Taipei", + Street = "Street", + Remark = "修改的" + }, + new() + { + Id = Guid.NewGuid(), + Employee_Id = employeeId, + CreatedAt = DateTimeOffset.Now, + CreatedBy = "sys", + Country = "Taipei", + Street = "Street", + Remark = "刪除的" + } + } + }; + dbContext.Employees.Add(toDB); + dbContext.SaveChanges(); + return toDB; + } + + private static string ToJson(T instance) + { + var serialize = JsonSerializer.Serialize(instance, + new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.Create( + UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs) + }); + return serialize; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj new file mode 100644 index 00000000..fbfda1f2 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/Lab.ChangeTracking.Domain.UnitTest.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + + + + + + + + diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs new file mode 100644 index 00000000..ca75b47b --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/MsTestHook.cs @@ -0,0 +1,31 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.MultiTestCase.UnitTest; + +[TestClass] +public class MsTestHook +{ + [AssemblyCleanup] + public static void Cleanup() + { + TestAssistants.SetTestEnvironmentVariable(); + var db = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + if (db.Database.CanConnect()) + { + db.Database.EnsureDeleted(); + } + } + + [AssemblyInitialize] + public static void Setup(TestContext context) + { + TestAssistants.SetTestEnvironmentVariable(); + var db = TestAssistants.EmployeeDbContextFactory.CreateDbContext(); + if (db.Database.CanConnect()) + { + db.Database.EnsureDeleted(); + } + + db.Database.EnsureCreated(); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs new file mode 100644 index 00000000..1fb69070 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain.UnitTest/TestAssistants.cs @@ -0,0 +1,59 @@ +using System; +using Lab.ChangeTracking.Domain; +using Lab.ChangeTracking.Infrastructure.DB; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Lab.MultiTestCase.UnitTest; + +// assistant +internal class TestAssistants +{ + private static IServiceProvider _serviceProvider; + + public static IDbContextFactory EmployeeDbContextFactory => + _serviceProvider.GetService>(); + + public static IEmployeeRepository EmployeeRepository => + _serviceProvider.GetService(); + + public static EmployeeAggregate EmployeeAggregate => + _serviceProvider.GetService(); + + public static ISystemClock SystemClock => + _serviceProvider.GetService(); + + public static IAccessContext AccessContext => + _serviceProvider.GetService(); + + public static IUUIdProvider UUIdProvider => + _serviceProvider.GetService(); + + static TestAssistants() + { + var services = new ServiceCollection(); + ConfigureTestServices(services); + SetTestEnvironmentVariable(); + } + + public static void ConfigureTestServices(IServiceCollection services) + { + services.AddAppEnvironment(); + services.AddEntityFramework(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + _serviceProvider = services.BuildServiceProvider(); + } + + public static void SetTestEnvironmentVariable() + { + var option = _serviceProvider.GetService(); + option.EmployeeDbConnectionString = + "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True"; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/AccessContext.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/AccessContext.cs new file mode 100644 index 00000000..85ba24fb --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/AccessContext.cs @@ -0,0 +1,14 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IAccessContext +{ + public string? GetUserId(); +} + +public class AccessContext : IAccessContext +{ + public string? GetUserId() + { + return "Sys"; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/ChangeState.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/ChangeState.cs new file mode 100644 index 00000000..34f040f5 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/ChangeState.cs @@ -0,0 +1,8 @@ +namespace Lab.ChangeTracking.Domain; + +public enum ChangeState +{ + Added = 0, + Modified = 1, + Deleted = 2, +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/CommitState.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/CommitState.cs new file mode 100644 index 00000000..650245f3 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/CommitState.cs @@ -0,0 +1,9 @@ +namespace Lab.ChangeTracking.Domain; + +public enum CommitState +{ + Unchanged = 0, + Accepted = 1, + Rejected = 2, + Commited = 3 +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs new file mode 100644 index 00000000..e72ad508 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/EmployeeAggregate.cs @@ -0,0 +1,187 @@ +using ChangeTracking; + +namespace Lab.ChangeTracking.Domain; + +public class EmployeeAggregate +{ + public CommitState CommitState { get; private set; } + + private readonly IAccessContext _accessContext; + private readonly IUUIdProvider _idProvider; + + private readonly EmployeeRepository _repository; + private readonly ISystemClock _systemClock; + + private EmployeeEntity _instance; + + public EmployeeAggregate(IUUIdProvider idProvider, + ISystemClock systemClock, + IAccessContext accessContext, + EmployeeRepository repository) + { + this._idProvider = idProvider; + this._systemClock = systemClock; + this._accessContext = accessContext; + this._repository = repository; + } + + public EmployeeAggregate AcceptChanges() + { + if (this.CommitState == CommitState.Accepted) + { + throw new Exception("已接受核准"); + } + + this.CommitState = CommitState.Accepted; + var trackable = this._instance.CastToIChangeTrackable(); + if (trackable.IsChanged) + { + this._instance.Version++; + } + else + { + this._instance.Version = 1; + } + + return this; + } + + public EmployeeAggregate AddAddress(AddressEntity instance) + { + this.ValidateAcceptedState(); + var (when, who) = (this._systemClock.GetNow(), this._accessContext.GetUserId()); + instance.Id = this._idProvider.GenerateId(); + instance.CreatedAt = when; + instance.CreatedBy = who; + + this._instance.Addresses.Add(instance); + return this; + } + + public async Task CommitChangeAsync(CancellationToken cancel = default) + { + if (this.CommitState != CommitState.Accepted) + { + throw new Exception("未被認可"); + } + + var changeCount = 0; + switch (this._instance.EntityState) + { + case ChangeState.Added: + changeCount = await this._repository.InsertEmployeeAsync(this._instance, cancel); + + break; + case ChangeState.Modified: + changeCount = await this._repository.SaveChangesAsync(this._instance, cancel); + + break; + case ChangeState.Deleted: + break; + default: + throw new ArgumentOutOfRangeException(); + } + + this.CommitState = CommitState.Unchanged; + return changeCount; + } + + public Guid GetEmployeeId() + { + return this._instance.Id; + } + + public EmployeeAggregate NewAddress(Guid employeeId, AddressEntity source) + { + this.CommitState = CommitState.Unchanged; + + var target = this._instance + .Addresses + .Where(p => p.Employee_Id == source.Employee_Id) + .FirstOrDefault(); + + if (target == null) + { + return this; + } + + var (when, who) = (this._systemClock.GetNow(), this._accessContext.GetUserId()); + + target.Id = source.Id; + target.Country = source.Country; + target.Street = source.Street; + target.Remark = source.Remark; + target.EntityState = ChangeState.Added; + target.ModifiedBy = who; + target.ModifiedAt = when; + return this; + } + + public Guid NewEmployee(string name, + int age, + string remark = null) + { + this.CommitState = CommitState.Unchanged; + + var (when, who) = (this._systemClock.GetNow(), this._accessContext.GetUserId()); + + this._instance = new EmployeeEntity + { + Id = this._idProvider.GenerateId(), + EntityState = ChangeState.Added, + Name = name, + Age = age, + Remark = remark, + CreatedAt = when, + CreatedBy = who, + ModifiedAt = when, + ModifiedBy = who, + }.AsTrackable(); + + return this._instance.Id; + } + + public EmployeeAggregate SetEntity(EmployeeEntity instance) + { + this.CommitState = CommitState.Unchanged; + + this._instance = instance.AsTrackable(); + this._instance.EntityState = ChangeState.Modified; + + return this; + } + + public void SetProfile(string name, + int age, + string remark = null) + { + this.ValidateAcceptedState(); + this.ValidateAddState(); + var (when, who) = + (this._systemClock.GetNow(), this._accessContext.GetUserId()); + this._instance.ModifiedBy = who; + this._instance.ModifiedAt = when; + + this._instance.Age = age; + this._instance.Name = name; + this._instance.Remark = remark; + } + + private void ValidateAcceptedState() + { + if (this.CommitState == CommitState.Accepted) + { + throw new Exception("已接受核准,不能改變狀態"); + } + + this.ValidateAddState(); + } + + private void ValidateAddState() + { + if (this._instance.EntityState == ChangeState.Added) + { + throw new Exception("Add 狀態不能異動"); + } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/AddressEntity.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/AddressEntity.cs new file mode 100644 index 00000000..0bdc4ad2 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/AddressEntity.cs @@ -0,0 +1,24 @@ +namespace Lab.ChangeTracking.Domain; + +public record AddressEntity +{ + public virtual Guid Id { get; set; } + + public virtual Guid Employee_Id { get; set; } + + public virtual ChangeState EntityState { get; set; } + + public virtual string Country { get; set; } + + public virtual string Street { get; set; } + + public virtual DateTimeOffset CreatedAt { get; set; } + + public virtual string CreatedBy { get; set; } + + public virtual DateTimeOffset? ModifiedAt { get; set; } + + public virtual string ModifiedBy { get; set; } + + public virtual string Remark { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs new file mode 100644 index 00000000..0bafc2b1 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/EmployeeEntity.cs @@ -0,0 +1,30 @@ +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; + +namespace Lab.ChangeTracking.Domain; + +public record EmployeeEntity +{ + public virtual Guid Id { get; set; } + + public virtual ChangeState EntityState { get; set; } + + public virtual string Name { get; set; } + + public virtual int? Age { get; set; } + + public virtual string Remark { get; set; } + + public virtual List Addresses { get; set; } = new(); + + public virtual IdentityEntity Identity { get; set; } + + public virtual string ModifiedBy { get; set; } + + public virtual DateTimeOffset? ModifiedAt { get; set; } + + public virtual DateTimeOffset CreatedAt { get; set; } + + public virtual string CreatedBy { get; set; } + + public virtual int Version { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs new file mode 100644 index 00000000..bcdde865 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Entity/IdentityEntity.cs @@ -0,0 +1,20 @@ +namespace Lab.ChangeTracking.Domain; + +public record IdentityEntity +{ + public Guid Employee_Id { get; set; } + + public string Account { get; set; } + + public string Password { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset? ModifiedAt { get; set; } + + public string? ModifiedBy { get; set; } + + public virtual string Remark { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs new file mode 100644 index 00000000..e251d05f --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/IEmployeeAggregate.cs @@ -0,0 +1 @@ +namespace Lab.ChangeTracking.Domain; diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs new file mode 100644 index 00000000..b0478a4f --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/EmployeeRepository.cs @@ -0,0 +1,41 @@ +using ChangeTracking; +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; + +namespace Lab.ChangeTracking.Domain; + +public class EmployeeRepository +{ + private readonly IDbContextFactory _employeeDbContextFactory; + + public EmployeeRepository(IDbContextFactory memberContextFactory) + { + this._employeeDbContextFactory = memberContextFactory; + } + + public async Task AddAsync(EmployeeEntity source, + CancellationToken cancel = default) + { + var dbContext = await this._employeeDbContextFactory.CreateDbContextAsync(cancel); + var srcEmployee = source.CastToIChangeTrackable(); + + return await dbContext.SaveChangesAsync(cancel); + } + + public async Task SaveChangesAsync(EmployeeEntity source, + CancellationToken cancel = default) + { + var dbContext = await this._employeeDbContextFactory.CreateDbContextAsync(cancel); + var employeeTrackable = source.CastToIChangeTrackable(); + + return await dbContext.SaveChangesAsync(cancel); + } + + public async Task InsertEmployeeAsync(EmployeeEntity srcEmployee, CancellationToken cancel = default) + { + var destEmployee = srcEmployee.To(); + var dbContext = await this._employeeDbContextFactory.CreateDbContextAsync(cancel); + await dbContext.AddAsync(destEmployee, cancel); + return await dbContext.SaveChangesAsync(cancel); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs new file mode 100644 index 00000000..0035d132 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/IEmployeeRepository.cs @@ -0,0 +1,8 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IEmployeeRepository +{ + Task SaveChangeAsync(EmployeeEntity employee, + IEnumerable excludeProperties = null, + CancellationToken cancel = default); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/TypeConverterExtensions.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/TypeConverterExtensions.cs new file mode 100644 index 00000000..0b1e88be --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EmployeeAggregate/Repository/TypeConverterExtensions.cs @@ -0,0 +1,145 @@ +// using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +// +// namespace Lab.ChangeTracking.Domain; +// +// public static class TypeConverterExtensions +// { +// public static Employee To(this EmployeeValueObject srcEmployee) +// { +// if (srcEmployee == null) +// { +// return null; +// } +// +// return new Employee +// { +// Id = srcEmployee.Id, +// Name = srcEmployee.Name, +// Age = srcEmployee.Age, +// Version = srcEmployee.Version, +// Remark = srcEmployee.Remark, +// Addresses = srcEmployee.Addresses != null ? srcEmployee.Addresses.To().ToList() : null, +// Identity = srcEmployee.Identity.To(), +// CreatedAt = srcEmployee.CreatedAt, +// CreatedBy = srcEmployee.CreatedBy, +// ModifiedAt = srcEmployee.ModifiedAt, +// ModifiedBy = srcEmployee.ModifiedBy +// }; +// } +// +// public static EmployeeValueObject To(this Employee srcEmployee) +// { +// if (srcEmployee == null) +// { +// return null; +// } +// +// return new EmployeeValueObject +// { +// Id = srcEmployee.Id, +// Name = srcEmployee.Name, +// Age = srcEmployee.Age, +// Version = srcEmployee.Version, +// Remark = srcEmployee.Remark, +// Addresses = srcEmployee.Addresses.To()?.ToList(), +// Identity = srcEmployee.Identity.To(), +// CreatedAt = srcEmployee.CreatedAt, +// CreatedBy = srcEmployee.CreatedBy, +// ModifiedAt = srcEmployee.ModifiedAt, +// ModifiedBy = srcEmployee.ModifiedBy +// }; +// } +// +// public static Identity To(this IdentityEntity srcIdentity) +// { +// if (srcIdentity == null) +// { +// return null; +// } +// +// return new Identity +// { +// Employee_Id = srcIdentity.Employee_Id, +// Account = srcIdentity.Account, +// Password = srcIdentity.Password, +// Remark = srcIdentity.Remark, +// CreatedAt = srcIdentity.CreatedAt, +// CreatedBy = srcIdentity.CreatedBy, +// ModifiedAt = srcIdentity.ModifiedAt, +// ModifiedBy = srcIdentity.ModifiedBy +// }; +// } +// +// public static IdentityEntity To(this Identity srcIdentity) +// { +// if (srcIdentity == null) +// { +// return null; +// } +// +// return new IdentityEntity +// { +// Employee_Id = srcIdentity.Employee_Id, +// Account = srcIdentity.Account, +// Password = srcIdentity.Password, +// Remark = srcIdentity.Remark, +// CreatedAt = srcIdentity.CreatedAt, +// CreatedBy = srcIdentity.CreatedBy, +// ModifiedAt = srcIdentity.ModifiedAt, +// ModifiedBy = srcIdentity.ModifiedBy +// }; +// } +// +// public static Address To(this AddressEntity srcAddress) +// { +// if (srcAddress == null) +// { +// return null; +// } +// +// return new Address +// { +// Id = srcAddress.Id, +// Employee_Id = srcAddress.Employee_Id, +// Country = srcAddress.Country, +// Street = srcAddress.Street, +// CreatedAt = srcAddress.CreatedAt, +// CreatedBy = srcAddress.CreatedBy, +// ModifiedAt = srcAddress.ModifiedAt, +// ModifiedBy = srcAddress.ModifiedBy, +// Remark = srcAddress.Remark +// }; +// } +// +// public static AddressEntity To(this Address srcAddress) +// { +// if (srcAddress == null) +// { +// return null; +// } +// +// return new AddressEntity +// { +// Id = srcAddress.Id, +// Employee_Id = srcAddress.Employee_Id, +// Country = srcAddress.Country, +// Street = srcAddress.Street, +// CreatedAt = srcAddress.CreatedAt, +// CreatedBy = srcAddress.CreatedBy, +// ModifiedAt = srcAddress.ModifiedAt, +// ModifiedBy = srcAddress.ModifiedBy, +// Remark = srcAddress.Remark +// }; +// } +// +// public static IEnumerable
To(this IEnumerable srcProfiles) +// { +// return srcProfiles?.Select(p => p?.To()); +// } +// +// public static IEnumerable To(this IEnumerable
srcProfiles) +// { +// return srcProfiles?.Select(p => p?.To()); +// } +// } + diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EntityBase.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EntityBase.cs new file mode 100644 index 00000000..96bae77a --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/EntityBase.cs @@ -0,0 +1,140 @@ +using System.Collections; + +namespace Lab.ChangeTracking.Domain; + +public abstract record EntityBase : IChangeTrackable +{ + // public Guid Id + // { + // get => this._id; + // init => this._id = value; + // } + + protected readonly Dictionary _changedProperties = new(); + protected readonly Dictionary _originalValues = new(); + protected CommitState _commitState; + protected ChangeState _entityState; + protected Guid _id; + + public EntityBase AsTrackable() + { + this.Validate(); + + // this._entityState = EntityState.Added; + // this._commitState = CommitState.Unchanged; + // this._version = 1; + var properties = this.GetType().GetProperties(); + foreach (var property in properties) + { + this._originalValues.Add(property.Name, property.GetValue(this)); + } + + return this; + } + + public (Error err, bool changed) AcceptChanges(ISystemClock systemClock, + IAccessContext accessContext, + IUUIdProvider idProvider) + { + this.Validate(); + + this._commitState = CommitState.Accepted; + var (now, accessUserId) = (systemClock.GetNow(), accessContext.GetUserId()); + + + if (this.EntityState == ChangeState.Added) + { + this._id = idProvider.GenerateId(); + this.CreatedAt = now; + this.CreatedBy = accessUserId; + this.Version = 1; + } + else + { + this.Version = this.Version++; + } + + this.ModifiedAt = now; + this.ModifiedBy = accessUserId; + + // this._entityState = EntityState.Submitted; + + return (null, true); + } + + public abstract void RejectChanges(); + + public Dictionary GetChangedProperties() + { + return this._changedProperties; + } + + public ChangeState EntityState + { + get => this._entityState; + init => this._entityState = value; + } + + public CommitState CommitState + { + get => this._commitState; + init => this._commitState = value; + } + + public int Version { get; set; } + + public Dictionary GetOriginalValues() + { + return this._originalValues; + } + + public Guid Id { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string? CreatedBy { get; set; } + + public DateTimeOffset? ModifiedAt { get; set; } + + public string? ModifiedBy { get; set; } + + public void ChangeTrack(string propertyName, object value) + { + this.Validate(); + + var changes = this._changedProperties; + var originals = this._originalValues; + if (originals.Count <= 0) + { + throw new Exception("尚未啟用追蹤"); + } + + if (changes.ContainsKey(propertyName) == false) + { + if (originals[propertyName] != value) + { + changes.Add(propertyName, value); + this._entityState = ChangeState.Modified; + } + } + else + { + if (originals[propertyName].ToString() == value.ToString()) + { + changes.Remove(propertyName); + } + } + + if (changes.Count <= 0) + { + } + } + + private void Validate() + { + if (this.CommitState == CommitState.Accepted) + { + throw new Exception("已經同意,無法再進行修改"); + } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/Error.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/Error.cs new file mode 100644 index 00000000..aa8f1b7a --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/Error.cs @@ -0,0 +1,3 @@ +namespace Lab.ChangeTracking.Domain; + +public record Error(T Code, object Message); \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeContent.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeContent.cs new file mode 100644 index 00000000..6d899ee9 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeContent.cs @@ -0,0 +1,8 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IChangeContent +{ + Dictionary GetChangedProperties(); + + Dictionary GetOriginalValues(); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeState.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeState.cs new file mode 100644 index 00000000..d3223700 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeState.cs @@ -0,0 +1,10 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IChangeState +{ + ChangeState EntityState { get; init; } + + CommitState CommitState { get; init; } + + int Version { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeTime.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeTime.cs new file mode 100644 index 00000000..c987e109 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeTime.cs @@ -0,0 +1,16 @@ +using System.ComponentModel; + +namespace Lab.ChangeTracking.Domain; + +public interface IChangeTime +{ + public Guid Id { get; set; } + + DateTimeOffset CreatedAt { get; set; } + + string CreatedBy { get; set; } + + DateTimeOffset? ModifiedAt { get; set; } + + string? ModifiedBy { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs new file mode 100644 index 00000000..9708f0ea --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IChangeTrackable.cs @@ -0,0 +1,12 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IChangeTrackable : IChangeTime, IChangeState +{ + public (Error err, bool changed) AcceptChanges(ISystemClock systemClock, + IAccessContext accessContext, + IUUIdProvider idProvider); + + void ChangeTrack(string propertyName, object value); + + void RejectChanges(); +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/ISystemClock.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/ISystemClock.cs new file mode 100644 index 00000000..50076e0d --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/ISystemClock.cs @@ -0,0 +1,14 @@ +namespace Lab.ChangeTracking.Domain; + +public interface ISystemClock +{ + DateTimeOffset GetNow(); +} + +public class SystemClock : ISystemClock +{ + public DateTimeOffset GetNow() + { + return DateTimeOffset.Now; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IUUIdProvider.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IUUIdProvider.cs new file mode 100644 index 00000000..a58ce071 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/IUUIdProvider.cs @@ -0,0 +1,14 @@ +namespace Lab.ChangeTracking.Domain; + +public interface IUUIdProvider +{ + Guid GenerateId(); +} + +public class UUIdProvider : IUUIdProvider +{ + public Guid GenerateId() + { + return Guid.NewGuid(); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj new file mode 100644 index 00000000..4249eea0 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/Lab.ChangeTracking.Domain.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/TypeConverterExtension.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/TypeConverterExtension.cs new file mode 100644 index 00000000..04617675 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Domain/TypeConverterExtension.cs @@ -0,0 +1,114 @@ +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; + +namespace Lab.ChangeTracking.Domain; + +public static class TypeConverterExtension +{ + public static EmployeeEntity To(this Employee source) + { + return new EmployeeEntity + { + Id = source.Id, + Version = source.Version, + Name = source.Name, + Age = source.Age, + Addresses = source.Addresses.To().ToList(), + Identity = source.Identity?.To(), + CreatedAt = source.CreatedAt, + CreatedBy = source.CreatedBy, + ModifiedAt = source.ModifiedAt, + ModifiedBy = source.ModifiedBy, + Remark = source.Remark + }; + } + + public static Employee To(this EmployeeEntity source) + { + return new Employee + { + Id = source.Id, + Version = source.Version, + Name = source.Name, + Age = source.Age, + Addresses = source.Addresses.To().ToList(), + Identity = source.Identity?.To(), + CreatedAt = source.CreatedAt, + CreatedBy = source.CreatedBy, + ModifiedAt = source.ModifiedAt, + ModifiedBy = source.ModifiedBy, + Remark = source.Remark + }; + } + + public static IdentityEntity To(this Identity source) + { + return new IdentityEntity + { + Employee_Id = source.Employee_Id, + Account = source.Account, + Password = source.Password, + Remark = source.Remark, + CreatedAt = source.CreatedAt, + CreatedBy = source.CreatedBy, + ModifiedAt = source.ModifiedAt, + ModifiedBy = source.ModifiedBy + }; + } + + public static Identity To(this IdentityEntity source) + { + return new Identity + { + Employee_Id = source.Employee_Id, + Account = source.Account, + Password = source.Password, + Remark = source.Remark, + CreatedAt = source.CreatedAt, + CreatedBy = source.CreatedBy, + ModifiedAt = source.ModifiedAt, + ModifiedBy = source.ModifiedBy + }; + } + + public static Address To(this AddressEntity source) + { + return new Address + { + Id = source.Id, + Employee_Id = source.Employee_Id, + Country = source.Country, + Street = source.Street, + CreatedAt = source.CreatedAt, + CreatedBy = source.CreatedBy, + ModifiedAt = source.ModifiedAt, + ModifiedBy = source.ModifiedBy, + Remark = source.Remark, + }; + } + + public static AddressEntity To(this Address source) + { + return new AddressEntity + { + Id = source.Id, + Employee_Id = source.Employee_Id, + Country = source.Country, + Street = source.Street, + CreatedAt = source.CreatedAt, + CreatedBy = source.CreatedBy, + ModifiedAt = source.ModifiedAt, + ModifiedBy = source.ModifiedBy, + Remark = source.Remark, + }; + } + + public static IEnumerable To(this IEnumerable
sources) + { + return sources.Select(p => p.To()); + } + + public static IEnumerable
To(this IEnumerable sources) + { + return sources.Select(p => p.To()); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs new file mode 100644 index 00000000..332cf598 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/AppDependencyInjectionExtensions.cs @@ -0,0 +1,48 @@ +using Lab.ChangeTracking.Infrastructure.DB.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.ChangeTracking.Infrastructure.DB; + +public static class AppDependencyInjectionExtensions +{ + public static void AddAppEnvironment(this IServiceCollection services) + { + services.AddLogging(builder => { builder.AddConsole(); }); + services.AddSingleton(); + } + + public static void AddEntityFramework(this IServiceCollection services) + { + services.AddPooledDbContextFactory((provider, optionsBuilder) => + { + var option = provider.GetService(); + var connectionString = option.EmployeeDbConnectionString; + var loggerFactory = provider.GetService(); + optionsBuilder.UseSqlServer(connectionString) + .UseLoggerFactory(loggerFactory) + .LogTo(Console.WriteLine) + .EnableSensitiveDataLogging() + ; + }); + + // services.AddPooledDbContextFactory((provider, options) => + // { + // var option = provider.GetService(); + // var loggerFactory = provider.GetService(); + // options.UseNpgsql( + // option.MemberDbConnectionString, //只會呼叫一次 + // builder => + // builder.EnableRetryOnFailure( + // 10, + // TimeSpan.FromSeconds(30), + // new List { "57P01" })) + // + // // .UseLazyLoadingProxies() + // .UseSnakeCaseNamingConvention() + // .UseLoggerFactory(loggerFactory) + // ; + // }); + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs new file mode 100644 index 00000000..d93a85cc --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/AppEnvironmentOption.cs @@ -0,0 +1,32 @@ +namespace Lab.ChangeTracking.Infrastructure.DB; + +public class AppEnvironmentOption +{ + public string EmployeeDbConnectionString + { + get + { + if (string.IsNullOrWhiteSpace(this._employeeDbConnectionString)) + { + this._employeeDbConnectionString = + EnvironmentAssistant.GetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR); + } + + return this._employeeDbConnectionString; + } + set + { + this._employeeDbConnectionString = value; + Environment.SetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR, value); + } + } + + private readonly string EMPLOYEE_DB_CONN_STR = "EMPLOYEE_DB_CONNECTION_STR"; + + private string _employeeDbConnectionString; + + public void Initial() + { + var memberDbConnectionString = this.EmployeeDbConnectionString; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs new file mode 100644 index 00000000..cdddbfa0 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Address.cs @@ -0,0 +1,29 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel; + +public class Address +{ + public Guid Id { get; set; } + + public Guid Employee_Id { get; set; } + + public Employee Employee { get; set; } + + public string Country { get; set; } + + public string Street { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset? ModifiedAt { get; set; } + + public string ModifiedBy { get; set; } + + public string Remark { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int SequenceId { get; set; } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs new file mode 100644 index 00000000..15e8c62b --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Employee.cs @@ -0,0 +1,34 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel +{ + public class Employee + { + public Guid Id { get; set; } + + public int Version { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + public IList
Addresses { get; set; } + + public Identity Identity { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset? ModifiedAt { get; set; } + + public string ModifiedBy { get; set; } + + public string Remark { get; set; } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs new file mode 100644 index 00000000..9ecfbc28 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/EmployeeDbContext.cs @@ -0,0 +1,120 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel +{ + public class EmployeeDbContext : DbContext + { + private static readonly bool[] s_migrated = { false }; + + public virtual DbSet Employees { get; set; } + + public virtual DbSet Identity { get; set; } + + public virtual DbSet
Addresses { get; set; } + + public EmployeeDbContext(DbContextOptions options) + : base(options) + { + // 改用 CI 執行 Migrate + + // if (s_migrated[0]) + // { + // return; + // } + // + // lock (s_migrated) + // { + // if (s_migrated[0] == false) + // { + // var sqlOptions = options.FindExtension(); + // if (sqlOptions != null) + // { + // this.Database.Migrate(); + // } + // + // s_migrated[0] = true; + // } + // } + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new EmployeeConfiguration()); + modelBuilder.ApplyConfiguration(new IdentityConfiguration()); + modelBuilder.ApplyConfiguration(new AddressConfiguration()); + } + + internal class EmployeeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Employee"); + builder.HasKey(p => p.Id) + .IsClustered(false); + + builder.Property(p => p.Name).IsRequired(); + builder.Property(p => p.CreatedBy).IsRequired(); + builder.Property(p => p.CreatedAt).IsRequired(); + builder.Property(p => p.ModifiedAt).IsRequired(false); + builder.Property(p => p.ModifiedBy).IsRequired(false); + builder.Property(p => p.Remark).IsRequired(false); + + builder.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + } + } + + private class IdentityConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Identity"); + builder.HasKey(p => p.Employee_Id).IsClustered(false); + builder.HasOne(e => e.Employee) + .WithOne(p => p.Identity) + .HasForeignKey(p => p.Employee_Id) + .OnDelete(DeleteBehavior.Cascade) + ; + + builder.Property(p => p.Account).IsRequired(); + builder.Property(p => p.Password).IsRequired(); + builder.Property(p => p.CreatedBy).IsRequired(); + builder.Property(p => p.CreatedAt).IsRequired(); + builder.Property(p => p.ModifiedAt).IsRequired(false); + builder.Property(p => p.ModifiedBy).IsRequired(false); + builder.Property(p => p.Remark).IsRequired(false); + + builder.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + } + } + + private class AddressConfiguration : IEntityTypeConfiguration
+ { + public void Configure(EntityTypeBuilder
builder) + { + builder.ToTable("Address"); + builder.HasKey(p => p.Id).IsClustered(false); + builder.HasOne(e => e.Employee) + .WithMany(p => p.Addresses) + .HasForeignKey(p => p.Employee_Id) + .OnDelete(DeleteBehavior.Cascade); + + builder.Property(p => p.Country).IsRequired(); + builder.Property(p => p.Street).IsRequired(); + builder.Property(p => p.CreatedBy).IsRequired(); + builder.Property(p => p.CreatedAt).IsRequired(); + builder.Property(p => p.ModifiedBy).IsRequired(false); + builder.Property(p => p.ModifiedAt).IsRequired(false); + builder.Property(p => p.Remark).IsRequired(false); + + builder.HasIndex(e => e.SequenceId) + .IsUnique() + .IsClustered(); + } + } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs new file mode 100644 index 00000000..e9e2f6d9 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EntityModel/Identity.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.ChangeTracking.Infrastructure.DB.EntityModel +{ + public class Identity + { + public Guid Employee_Id { get; set; } + + // [ForeignKey("Employee_Id")] + public virtual Employee Employee { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset? ModifiedAt { get; set; } + + public string? ModifiedBy { get; set; } + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs new file mode 100644 index 00000000..b115d8a2 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/EnvironmentAssistant.cs @@ -0,0 +1,15 @@ +namespace Lab.ChangeTracking.Infrastructure.DB; + +public class EnvironmentAssistant +{ + public static string GetEnvironmentVariable(string key) + { + var result = Environment.GetEnvironmentVariable(key); + if (string.IsNullOrWhiteSpace(result)) + { + throw new Exception($"the key '{key}' not exist in environment variable"); + } + + return result; + } +} \ No newline at end of file diff --git a/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj new file mode 100644 index 00000000..3a36e2d1 --- /dev/null +++ b/Property Change Tracking/ChangeTrackVersion/src/Lab.ChangeTracking.Infrastructure.DB/Lab.ChangeTracking.Infrastructure.DB.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + + + + + + + + + + From 394e993978e1b3f526cd1add40b88718a94587fb Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 27 Mar 2022 18:57:42 +0800 Subject: [PATCH 189/424] feat: add DictionaryStringObjectJsonConverter project --- Json/Lab.JsonConverter/Lab.JsonConverter.sln | 22 ++ ...ictionaryStringObjectJsonConverterTests.cs | 199 ++++++++++++++++++ .../Lab.JsonConverterLib.UnitTest.csproj | 21 ++ .../DictionaryStringObjectJsonConverter.cs | 114 ++++++++++ .../JsonDocumentExtensions.cs | 41 ++++ .../Lab.JsonConverterLib.csproj | 9 + 6 files changed, 406 insertions(+) create mode 100644 Json/Lab.JsonConverter/Lab.JsonConverter.sln create mode 100644 Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverterTests.cs create mode 100644 Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/Lab.JsonConverterLib.UnitTest.csproj create mode 100644 Json/Lab.JsonConverter/Lab.JsonConverterLib/DictionaryStringObjectJsonConverter.cs create mode 100644 Json/Lab.JsonConverter/Lab.JsonConverterLib/JsonDocumentExtensions.cs create mode 100644 Json/Lab.JsonConverter/Lab.JsonConverterLib/Lab.JsonConverterLib.csproj diff --git a/Json/Lab.JsonConverter/Lab.JsonConverter.sln b/Json/Lab.JsonConverter/Lab.JsonConverter.sln new file mode 100644 index 00000000..07e7f922 --- /dev/null +++ b/Json/Lab.JsonConverter/Lab.JsonConverter.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.JsonConverterLib", "Lab.JsonConverterLib\Lab.JsonConverterLib.csproj", "{5E5A6E58-2175-435A-AC74-617DBF7A70F2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.JsonConverterLib.UnitTest", "Lab.JsonConverterLib.UnitTest\Lab.JsonConverterLib.UnitTest.csproj", "{E36B4A45-46F6-4709-ACF6-2887F4D26B5D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5E5A6E58-2175-435A-AC74-617DBF7A70F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E5A6E58-2175-435A-AC74-617DBF7A70F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E5A6E58-2175-435A-AC74-617DBF7A70F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E5A6E58-2175-435A-AC74-617DBF7A70F2}.Release|Any CPU.Build.0 = Release|Any CPU + {E36B4A45-46F6-4709-ACF6-2887F4D26B5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E36B4A45-46F6-4709-ACF6-2887F4D26B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E36B4A45-46F6-4709-ACF6-2887F4D26B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E36B4A45-46F6-4709-ACF6-2887F4D26B5D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverterTests.cs b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverterTests.cs new file mode 100644 index 00000000..60e751a9 --- /dev/null +++ b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverterTests.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.JsonConverterLib.UnitTest; + +[TestClass] +public class DictionaryStringObjectJsonConverterTests +{ + [TestMethod] + public void JsonDocument轉Dictionary() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + var expected = new Dictionary + { + ["anonymousType"] = new { Prop = 123 }, + ["model"] = new Model { Age = 19, Name = "yao" }, + ["null"] = null!, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["long"] = (long)255, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "String", + ["decimalArray"] = new[] { 1, (decimal)2.1 }, + }; + var json = JsonSerializer.Serialize(expected); + + using var jsonDoc = json.ToJsonDocument(); + var actual = jsonDoc.To>(options); + + Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); + Assert.AreEqual(expected["string"], actual["string"]); + Assert.AreEqual(expected["long"], actual["long"]); + Assert.AreEqual(expected["decimal"], actual["decimal"]); + Assert.AreEqual(expected["null"], actual["null"]); + + AssertAnonymousType(actual["anonymousType"] as Dictionary); + AssertDecimalArray(actual["decimalArray"] as List); + } + + [TestMethod] + public void JsonDocument轉Dictionary1() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + var expected = new Dictionary + { + ["anonymousType"] = new { Prop = 123 }, + ["model"] = new Model { Age = 19, Name = "yao" }, + ["null"] = null!, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["long"] = (long)255, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "String", + ["decimalArray"] = new[] { 1, (decimal)2.1 }, + }; + + using var jsonDoc = expected.ToJsonDocument(); + var actual = jsonDoc.To>(options); + + Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); + Assert.AreEqual(expected["string"], actual["string"]); + Assert.AreEqual(expected["long"], actual["long"]); + Assert.AreEqual(expected["decimal"], actual["decimal"]); + Assert.AreEqual(expected["null"], actual["null"]); + + AssertAnonymousType(actual["anonymousType"] as Dictionary); + AssertDecimalArray(actual["decimalArray"] as List); + } + + [TestMethod] + public void Memory轉Dictionary() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + + var expected = new Dictionary + { + ["null"] = null!, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["long"] = (long)255, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "String", + ["anonymousType"] = new { Prop = 123 }, + ["decimalArray"] = new[] { 1, (decimal)2.1 }, + }; + + var json = JsonSerializer.Serialize(expected, options); + var jsonMemory = new MemoryStream(Encoding.UTF8.GetBytes(json)); + + var actual = JsonSerializer.Deserialize>(jsonMemory, options); + + Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); + Assert.AreEqual(expected["string"], actual["string"]); + Assert.AreEqual(expected["long"], actual["long"]); + Assert.AreEqual(expected["decimal"], actual["decimal"]); + Assert.AreEqual(expected["null"], actual["null"]); + + AssertAnonymousType(actual["anonymousType"] as Dictionary); + AssertDecimalArray(actual["decimalArray"] as List); + } + + [TestMethod] + public void 字串轉Dictionary() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + var expected = new Dictionary + { + ["anonymousType"] = new { Prop = 123 }, + ["model"] = new Model { Age = 19, Name = "yao" }, + ["null"] = null!, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["long"] = (long)255, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "String", + ["decimalArray"] = new[] { 1, (decimal)2.1 }, + }; + + var json = JsonSerializer.Serialize(expected, options); + var actual = JsonSerializer.Deserialize>(json, options); + + Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); + Assert.AreEqual(expected["string"], actual["string"]); + Assert.AreEqual(expected["long"], actual["long"]); + Assert.AreEqual(expected["decimal"], actual["decimal"]); + Assert.AreEqual(expected["null"], actual["null"]); + + AssertAnonymousType(actual["anonymousType"] as Dictionary); + AssertDecimalArray(actual["decimalArray"] as List); + } + + [TestMethod] + public void 字串轉Dictionary_失敗() + { + var expected = new Dictionary + { + ["i"] = 255, + ["s"] = "字串", + ["d"] = new DateTime(1900, 1, 1), + ["a"] = new[] { 1, 2 }, + ["o"] = new { Prop = 123 } + }; + var json = JsonSerializer.Serialize(expected); + + var actual = JsonSerializer.Deserialize>(json); + Assert.AreNotEqual(expected["i"], actual["i"]); + Assert.AreNotEqual(expected["s"], actual["s"]); + + // 反序列化之後得到 JsonElement Type,必須要要透過其他手段才能取得真實的值 + Assert.AreEqual("JsonElement", actual["s"].GetType().Name); + Assert.AreEqual(expected["i"], ((JsonElement)actual["i"]).GetInt32()); + Assert.AreEqual(expected["s"], ((JsonElement)actual["s"]).GetString()); + } + + private static void AssertAnonymousType(Dictionary actual) + { + var expected = new Dictionary + { + { "Prop", (long)123 } + }; + + Assert.AreEqual(expected["Prop"], actual["Prop"]); + } + + private static void AssertDecimalArray(List actual) + { + var expected = new List + { + (long)1, + (decimal)2.1 + }; + + Assert.AreEqual(expected[0], actual[0]); + Assert.AreEqual(expected[1], actual[1]); + } + + private class Model + { + public string Name { get; set; } + + public int Age { get; set; } + } +} \ No newline at end of file diff --git a/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/Lab.JsonConverterLib.UnitTest.csproj b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/Lab.JsonConverterLib.UnitTest.csproj new file mode 100644 index 00000000..d0b60f4b --- /dev/null +++ b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/Lab.JsonConverterLib.UnitTest.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + diff --git a/Json/Lab.JsonConverter/Lab.JsonConverterLib/DictionaryStringObjectJsonConverter.cs b/Json/Lab.JsonConverter/Lab.JsonConverterLib/DictionaryStringObjectJsonConverter.cs new file mode 100644 index 00000000..28ecf719 --- /dev/null +++ b/Json/Lab.JsonConverter/Lab.JsonConverterLib/DictionaryStringObjectJsonConverter.cs @@ -0,0 +1,114 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Lab.JsonConverterLib; + +public class DictionaryStringObjectJsonConverter : JsonConverter> +{ + public override Dictionary Read(ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException($"JsonTokenType was of type {reader.TokenType}, only objects are supported"); + } + + var results = new Dictionary(); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + return results; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("JsonTokenType was not PropertyName"); + } + + var propertyName = reader.GetString(); + + if (string.IsNullOrWhiteSpace(propertyName)) + { + throw new JsonException("Failed to get property name"); + } + + reader.Read(); + + results.Add(propertyName, this.ReadValue(ref reader, options)); + } + + return results; + } + + public override void Write(Utf8JsonWriter writer, + Dictionary value, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + + foreach (var key in value.Keys) + { + WriteValue(writer, key, value[key], options); + } + + writer.WriteEndObject(); + } + + private object ReadValue(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + if (reader.TryGetDateTimeOffset(out var dateOffset)) + { + return dateOffset; + } + + if (reader.TryGetGuid(out var guid)) + { + return guid; + } + + return reader.GetString(); + case JsonTokenType.False: + case JsonTokenType.True: + return reader.GetBoolean(); + case JsonTokenType.Null: + return null; + case JsonTokenType.Number: + if (reader.TryGetInt64(out var result)) + { + return result; + } + + return reader.GetDecimal(); + case JsonTokenType.StartObject: + return this.Read(ref reader, null, options); + case JsonTokenType.StartArray: + var list = new List(); + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + list.Add(this.ReadValue(ref reader, options)); + } + + return list; + default: + throw new JsonException($"'{reader.TokenType}' is not supported"); + } + } + + private static void WriteValue(Utf8JsonWriter writer, + string key, + object value, + JsonSerializerOptions options) + { + if (key != null) + { + writer.WritePropertyName(key); + } + + JsonSerializer.Serialize(writer, value, options); + } +} \ No newline at end of file diff --git a/Json/Lab.JsonConverter/Lab.JsonConverterLib/JsonDocumentExtensions.cs b/Json/Lab.JsonConverter/Lab.JsonConverterLib/JsonDocumentExtensions.cs new file mode 100644 index 00000000..c97c67eb --- /dev/null +++ b/Json/Lab.JsonConverter/Lab.JsonConverterLib/JsonDocumentExtensions.cs @@ -0,0 +1,41 @@ +using System.Text; +using System.Text.Json; + +namespace Lab.JsonConverterLib; + +public static class JsonDocumentExtensions +{ + public static T To(this JsonDocument source, + JsonSerializerOptions options = default) + { + return source.Deserialize(options); + } + + public static JsonDocument ToJsonDocument(this T source, + JsonDocumentOptions options = default) + where T : class + { + return JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes(source), options); + } + + public static JsonDocument ToJsonDocument(this string source, + JsonDocumentOptions options = default) + { + return JsonDocument.Parse(source, options); + } + + public static string ToJsonString(this JsonDocument source, + JsonWriterOptions options = default) + { + if (source == null) + { + return null; + } + + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, options); + source.WriteTo(writer); + writer.Flush(); + return Encoding.UTF8.GetString(stream.ToArray()); + } +} \ No newline at end of file diff --git a/Json/Lab.JsonConverter/Lab.JsonConverterLib/Lab.JsonConverterLib.csproj b/Json/Lab.JsonConverter/Lab.JsonConverterLib/Lab.JsonConverterLib.csproj new file mode 100644 index 00000000..eb2460e9 --- /dev/null +++ b/Json/Lab.JsonConverter/Lab.JsonConverterLib/Lab.JsonConverterLib.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + From 8d6902fd20c24bbc676bdf18b9658beffe4287a3 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 27 Mar 2022 19:21:30 +0800 Subject: [PATCH 190/424] feat: add JsonNodeExtensions --- ...ictionaryStringObjectJsonConverterTests.cs | 51 ++++++++++++++++--- .../JsonNodeExtensions.cs | 42 +++++++++++++++ 2 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 Json/Lab.JsonConverter/Lab.JsonConverterLib/JsonNodeExtensions.cs diff --git a/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverterTests.cs b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverterTests.cs index 60e751a9..48c87558 100644 --- a/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverterTests.cs +++ b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverterTests.cs @@ -20,13 +20,13 @@ public void JsonDocument轉Dictionary() var expected = new Dictionary { ["anonymousType"] = new { Prop = 123 }, - ["model"] = new Model { Age = 19, Name = "yao" }, + ["model"] = new Model { Age = 19, Name = "小章" }, ["null"] = null!, ["dateTimeOffset"] = DateTimeOffset.Now, ["long"] = (long)255, ["decimal"] = (decimal)3.1416, ["guid"] = Guid.NewGuid(), - ["string"] = "String", + ["string"] = "字串", ["decimalArray"] = new[] { 1, (decimal)2.1 }, }; var json = JsonSerializer.Serialize(expected); @@ -54,13 +54,13 @@ public void JsonDocument轉Dictionary1() var expected = new Dictionary { ["anonymousType"] = new { Prop = 123 }, - ["model"] = new Model { Age = 19, Name = "yao" }, + ["model"] = new Model { Age = 19, Name = "小章" }, ["null"] = null!, ["dateTimeOffset"] = DateTimeOffset.Now, ["long"] = (long)255, ["decimal"] = (decimal)3.1416, ["guid"] = Guid.NewGuid(), - ["string"] = "String", + ["string"] = "字串", ["decimalArray"] = new[] { 1, (decimal)2.1 }, }; @@ -77,6 +77,40 @@ public void JsonDocument轉Dictionary1() AssertDecimalArray(actual["decimalArray"] as List); } + [TestMethod] + public void JsonsNode轉Dictionary() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + var expected = new Dictionary + { + ["anonymousType"] = new { Prop = 123 }, + ["model"] = new Model { Age = 19, Name = "小章" }, + ["null"] = null!, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["long"] = (long)255, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "字串", + ["decimalArray"] = new[] { 1, (decimal)2.1 }, + }; + var json = JsonSerializer.Serialize(expected); + + var jsonObject = json.ToJsonNode(); + var actual = jsonObject.To>(options); + + Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); + Assert.AreEqual(expected["string"], actual["string"]); + Assert.AreEqual(expected["long"], actual["long"]); + Assert.AreEqual(expected["decimal"], actual["decimal"]); + Assert.AreEqual(expected["null"], actual["null"]); + + AssertAnonymousType(actual["anonymousType"] as Dictionary); + AssertDecimalArray(actual["decimalArray"] as List); + } + [TestMethod] public void Memory轉Dictionary() { @@ -87,13 +121,14 @@ public void Memory轉Dictionary() var expected = new Dictionary { + ["anonymousType"] = new { Prop = 123 }, + ["model"] = new Model { Age = 19, Name = "小章" }, ["null"] = null!, ["dateTimeOffset"] = DateTimeOffset.Now, ["long"] = (long)255, ["decimal"] = (decimal)3.1416, ["guid"] = Guid.NewGuid(), - ["string"] = "String", - ["anonymousType"] = new { Prop = 123 }, + ["string"] = "字串", ["decimalArray"] = new[] { 1, (decimal)2.1 }, }; @@ -122,13 +157,13 @@ public void 字串轉Dictionary() var expected = new Dictionary { ["anonymousType"] = new { Prop = 123 }, - ["model"] = new Model { Age = 19, Name = "yao" }, + ["model"] = new Model { Age = 19, Name = "小章" }, ["null"] = null!, ["dateTimeOffset"] = DateTimeOffset.Now, ["long"] = (long)255, ["decimal"] = (decimal)3.1416, ["guid"] = Guid.NewGuid(), - ["string"] = "String", + ["string"] = "字串", ["decimalArray"] = new[] { 1, (decimal)2.1 }, }; diff --git a/Json/Lab.JsonConverter/Lab.JsonConverterLib/JsonNodeExtensions.cs b/Json/Lab.JsonConverter/Lab.JsonConverterLib/JsonNodeExtensions.cs new file mode 100644 index 00000000..cb7593d4 --- /dev/null +++ b/Json/Lab.JsonConverter/Lab.JsonConverterLib/JsonNodeExtensions.cs @@ -0,0 +1,42 @@ +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; + +namespace Lab.JsonConverterLib; + +public static class JsonNodeExtensions +{ + public static T To(this JsonNode source, + JsonSerializerOptions options = default) + { + return source.Deserialize(options); + } + + public static JsonNode ToJsonNode(this T source, + JsonNodeOptions options = default) + where T : class + { + return JsonNode.Parse(JsonSerializer.SerializeToUtf8Bytes(source), options); + } + + public static JsonNode ToJsonNode(this string source, + JsonNodeOptions options = default) + { + return JsonNode.Parse(source, options); + } + + public static string ToJsonString(this JsonNode source, + JsonWriterOptions options = default) + { + if (source == null) + { + return null; + } + + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, options); + source.WriteTo(writer); + writer.Flush(); + return Encoding.UTF8.GetString(stream.ToArray()); + } +} \ No newline at end of file From 4452fc49081b40cfb6d3625a094e6041e6bc8de4 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 28 Mar 2022 16:54:10 +0800 Subject: [PATCH 191/424] add project --- .../GlobalSteps.cs | 24 +++++ .../Lab.ORM.DynamicField.UnitTest.csproj | 30 ++++++ .../TestAssistant.cs | 39 ++++++++ .../UnitTest1.cs | 97 +++++++++++++++++++ .../Lab.ORM.DynamicField.sln | 27 ++++++ .../AppDependencyInjectionExtensions.cs | 51 ++++++++++ .../AppEnvironmentOption.cs | 32 ++++++ .../EntityModel/Employee.cs | 35 +++++++ .../EntityModel/EmployeeDbContext.cs | 41 ++++++++ .../EntityModel/Identity.cs | 33 +++++++ .../EntityModel/OrderHistory.cs | 30 ++++++ .../EnvironmentAssistant.cs | 15 +++ .../Lab.ORM.DynamicField.csproj | 18 ++++ .../Lab.ORM.DynamicField/Makefile | 4 + ORM/Lab.ORM.DynamicField/docker-compose.yml | 9 ++ 15 files changed, 485 insertions(+) create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/GlobalSteps.cs create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/Lab.ORM.DynamicField.UnitTest.csproj create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/TestAssistant.cs create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.sln create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/AppDependencyInjectionExtensions.cs create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/AppEnvironmentOption.cs create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/Employee.cs create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/EmployeeDbContext.cs create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/Identity.cs create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/OrderHistory.cs create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EnvironmentAssistant.cs create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Lab.ORM.DynamicField.csproj create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Makefile create mode 100644 ORM/Lab.ORM.DynamicField/docker-compose.yml diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/GlobalSteps.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/GlobalSteps.cs new file mode 100644 index 00000000..5dc33201 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/GlobalSteps.cs @@ -0,0 +1,24 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.ORM.DynamicField.UnitTest; + +[TestClass] +public class GlobalSteps +{ + [AssemblyCleanup] + public static void Cleanup() + { + TestAssistant.SetTestEnvironmentVariable(); + using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + db.Database.EnsureDeleted(); + } + + [AssemblyInitialize] + public static void Setup(TestContext context) + { + TestAssistant.SetTestEnvironmentVariable(); + using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + db.Database.EnsureDeleted(); + db.Database.EnsureCreated(); + } +} \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/Lab.ORM.DynamicField.UnitTest.csproj b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/Lab.ORM.DynamicField.UnitTest.csproj new file mode 100644 index 00000000..116efd17 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/Lab.ORM.DynamicField.UnitTest.csproj @@ -0,0 +1,30 @@ + + + + net6.0 + enable + + false + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/TestAssistant.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/TestAssistant.cs new file mode 100644 index 00000000..be4cb416 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/TestAssistant.cs @@ -0,0 +1,39 @@ +using System; +using Lab.ORM.DynamicField.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Lab.ORM.DynamicField.UnitTest; + +internal class TestAssistant +{ + private static IServiceProvider _serviceProvider; + + public static IDbContextFactory EmployeeDbContextFactory => + _serviceProvider.GetService>(); + + static TestAssistant() + { + var services = new ServiceCollection(); + ConfigureTestServices(services); + } + + public static void ConfigureTestServices(IServiceCollection services) + { + services.AddAppEnvironment(); + services.AddEntityFramework(); + _serviceProvider = services.BuildServiceProvider(); + } + + public static void SetTestEnvironmentVariable() + { + var connectionString = + "Host=localhost;Port=5432;Database=employee;Username=postgres;Password=guest;"; + + // var connectionString = + // "Data Source=localhost;Initial Catalog=EmployeeDb;Integrated Security=false;User ID=sa;Password=pass@w0rd1~;MultipleActiveResultSets=True;TrustServerCertificate=True"; + + var option = _serviceProvider.GetService(); + option.EmployeeDbConnectionString = connectionString; + } +} \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs new file mode 100644 index 00000000..3cbd4716 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using EFCore.BulkExtensions; +using Faker; +using Lab.ORM.DynamicField.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.ORM.DynamicField.UnitTest; + +[TestClass] +public class UnitTest1 +{ + [TestCleanup] + public void TestCleanup() + { + CleanData(); + } + + [TestInitialize] + public void TestInitialize() + { + CleanData(); + } + + [TestMethod] + public void TestMethod1() + { + using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + + // var id = Guid.NewGuid(); + // db.Employees.Add(new Employee + // { + // Id = id, + // Age = RandomNumber.Next(1, 100), + // Name = Name.FullName(), + // CreatedAt = DateTimeOffset.UtcNow, + // CreatedBy = "Sys", + // + // // Identity = new Identity + // // { + // // Account = "yao", + // // CreateAt = DateTimeOffset.UtcNow, + // // CreateBy = "Sys", + // // Password = "123456", + // // }, + // }); + var generateEmployees = GenerateEmployees(1); + db.AddRange(generateEmployees); + db.SaveChanges(); + } + + [TestMethod] + public void TestMethod2() + { + var host = CreateHostBuilder(null).Start(); + host.Services.GetService>(); + } + + private static void CleanData() + { + using var dbContext = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + dbContext.Employees.BatchDelete(); + } + + private static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureServices((hostBuilder, services) => { TestAssistant.ConfigureTestServices(services); }); + } + + private static List GenerateEmployees(int totalCount) + { + var employees = Enumerable.Range(0, totalCount) + .Select((x, i) => + { + var now = DateTimeOffset.UtcNow; + var sysAccount = "sys"; + return new Employee + { + Id = Guid.NewGuid(), + Age = RandomNumber.Next(1, 100), + Name = Name.FullName(), + CreatedBy = sysAccount, + CreatedAt = now, + ModifiedAt = null, + ModifiedBy = null, + + // Name = Name.First(), + }; + }).ToList(); + return employees; + } +} \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.sln b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.sln new file mode 100644 index 00000000..0ff4a8f9 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ORM.DynamicField", "Lab.ORM.DynamicField\Lab.ORM.DynamicField.csproj", "{C1348BD0-4FCA-4DF6-9C94-B2543F4B6E88}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ORM.DynamicField.UnitTest", "Lab.ORM.DynamicField.UnitTest\Lab.ORM.DynamicField.UnitTest.csproj", "{BC21E9D0-E865-499B-8395-4067E5E7DB09}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{B1431E24-8BEB-4481-9E42-8B66BBF99494}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C1348BD0-4FCA-4DF6-9C94-B2543F4B6E88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1348BD0-4FCA-4DF6-9C94-B2543F4B6E88}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1348BD0-4FCA-4DF6-9C94-B2543F4B6E88}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1348BD0-4FCA-4DF6-9C94-B2543F4B6E88}.Release|Any CPU.Build.0 = Release|Any CPU + {BC21E9D0-E865-499B-8395-4067E5E7DB09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC21E9D0-E865-499B-8395-4067E5E7DB09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC21E9D0-E865-499B-8395-4067E5E7DB09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC21E9D0-E865-499B-8395-4067E5E7DB09}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/AppDependencyInjectionExtensions.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/AppDependencyInjectionExtensions.cs new file mode 100644 index 00000000..1edc7fb4 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/AppDependencyInjectionExtensions.cs @@ -0,0 +1,51 @@ +using Lab.ORM.DynamicField.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.ORM.DynamicField; + +public static class AppDependencyInjectionExtensions +{ + public static void AddAppEnvironment(this IServiceCollection services) + { + services.AddLogging(builder => { builder.AddConsole(); }); + services.AddSingleton(); + } + + public static void AddEntityFramework(this IServiceCollection services) + { + services.AddDbContextFactory((provider, options) => + { + var option = provider.GetService(); + var connectionString = option.EmployeeDbConnectionString; + options.UseNpgsql(connectionString, //只會呼叫一次 + builder => builder.EnableRetryOnFailure( + 10, + TimeSpan.FromSeconds(30), + new List { "57P01" }) + ) + + // .UseLazyLoadingProxies() + .EnableSensitiveDataLogging() //这将捕获通过迁移发送的更改。 + .LogTo(Console.WriteLine, LogLevel.Information) //这将捕获所有发送到数据库的SQL。 + // .UseLoggerFactory(loggerFactory) + ; + + //.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + }); + } + + // public static void AddEntityFramework(this IServiceCollection services) + // { + // services.AddPooledDbContextFactory((provider, optionsBuilder) => + // { + // var option = provider.GetService(); + // var connectionString = option.EmployeeDbConnectionString; + // var loggerFactory = provider.GetService(); + // optionsBuilder.UseNpgsql(connectionString) + // .UseLoggerFactory(loggerFactory) + // ; + // }); + // } +} \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/AppEnvironmentOption.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/AppEnvironmentOption.cs new file mode 100644 index 00000000..93d18070 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/AppEnvironmentOption.cs @@ -0,0 +1,32 @@ +namespace Lab.ORM.DynamicField; + +public class AppEnvironmentOption +{ + public string EmployeeDbConnectionString + { + get + { + if (string.IsNullOrWhiteSpace(this._employeeDbConnectionString)) + { + this._employeeDbConnectionString = + EnvironmentAssistant.GetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR); + } + + return this._employeeDbConnectionString; + } + set + { + this._employeeDbConnectionString = value; + Environment.SetEnvironmentVariable(this.EMPLOYEE_DB_CONN_STR, value); + } + } + + private readonly string EMPLOYEE_DB_CONN_STR = "EMPLOYEE_DB_CONNECTION_STR"; + + private string _employeeDbConnectionString; + + public void Initial() + { + var memberDbConnectionString = this.EmployeeDbConnectionString; + } +} \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/Employee.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/Employee.cs new file mode 100644 index 00000000..33c3810f --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/Employee.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.ORM.DynamicField.EntityModel +{ + [Table("Employee")] + public class Employee + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Id { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + // public virtual Identity Identity { get; set; } + + [Required] + public DateTimeOffset CreatedAt { get; set; } + + [Required] + public string CreatedBy { get; set; } + + public DateTimeOffset? ModifiedAt { get; set; } + + public string ModifiedBy { get; set; } + } +} \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/EmployeeDbContext.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/EmployeeDbContext.cs new file mode 100644 index 00000000..3910ea26 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/EmployeeDbContext.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore; + +namespace Lab.ORM.DynamicField.EntityModel +{ + public class EmployeeDbContext : DbContext + { + private static readonly bool[] s_migrated = { false }; + + public virtual DbSet Employees { get; set; } + + // public virtual DbSet Identities { get; set; } + // + // public virtual DbSet OrderHistories { get; set; } + + public EmployeeDbContext(DbContextOptions options) + : base(options) + { + } + + //管理索引 + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(p => + { + p.Property(p => p.Name).IsRequired().HasMaxLength(50); + p.Property(p => p.CreatedAt).IsRequired(); + p.Property(p => p.CreatedBy).IsRequired(); + p.Property(p => p.ModifiedAt).IsRequired(false); + p.Property(p => p.ModifiedBy).IsRequired(false); + p.Property(p => p.Remark).IsRequired(false); + }); + + modelBuilder.Entity(p => + { + p.HasIndex(e => e.SequenceId) + .IsUnique() + ; + }); + } + } +} \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/Identity.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/Identity.cs new file mode 100644 index 00000000..a62f5b10 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/Identity.cs @@ -0,0 +1,33 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.ORM.DynamicField.EntityModel +{ + [Table("Identity")] + public class Identity + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Employee_Id { get; set; } + + [Required] + public string Account { get; set; } + + [Required] + public string Password { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + [Required] + public DateTimeOffset CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + + [ForeignKey("Employee_Id")] + public virtual Employee Employee { get; set; } + } +} \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/OrderHistory.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/OrderHistory.cs new file mode 100644 index 00000000..14415d94 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/OrderHistory.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.ORM.DynamicField.EntityModel +{ + [Table("OrderHistory")] + public class OrderHistory + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + public Guid? Employee_Id { get; set; } + + public string Remark { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Product_Id { get; set; } + + public string Product_Name { get; set; } + + [Required] + public DateTime CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + } +} \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EnvironmentAssistant.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EnvironmentAssistant.cs new file mode 100644 index 00000000..14b3d04c --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EnvironmentAssistant.cs @@ -0,0 +1,15 @@ +namespace Lab.ORM.DynamicField; + +public class EnvironmentAssistant +{ + public static string GetEnvironmentVariable(string key) + { + var result = Environment.GetEnvironmentVariable(key); + if (string.IsNullOrWhiteSpace(result)) + { + throw new Exception($"the key '{key}' not exist in environment variable"); + } + + return result; + } +} \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Lab.ORM.DynamicField.csproj b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Lab.ORM.DynamicField.csproj new file mode 100644 index 00000000..e7d06e72 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Lab.ORM.DynamicField.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + enable + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Makefile b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Makefile new file mode 100644 index 00000000..cf17f07f --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Makefile @@ -0,0 +1,4 @@ +G1: + echo 'Hello World' +G2: + cmd \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/docker-compose.yml b/ORM/Lab.ORM.DynamicField/docker-compose.yml new file mode 100644 index 00000000..0c353024 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/docker-compose.yml @@ -0,0 +1,9 @@ +version: "3.8" + +services: + db: + image: postgres:12-alpine + environment: + - POSTGRES_PASSWORD=guest + ports: + - 5432:5432 \ No newline at end of file From 9ff043bae14d3ba9c03ff7ee415624c8f4fef2ab Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 28 Mar 2022 23:05:17 +0800 Subject: [PATCH 192/424] feat: add DictionaryStringObjectJsonConverter --- .../GlobalSteps.cs | 6 +- .../UnitTest1.cs | 182 +++++++++++++++--- .../AppDependencyInjectionExtensions.cs | 2 +- .../Lab.ORM.DynamicField/Customer.cs | 28 +++ .../DictionaryStringObjectJsonConverter.cs | 114 +++++++++++ .../EntityModel/Employee.cs | 19 +- .../EntityModel/EmployeeDbContext.cs | 3 + .../JsonDocumentExtensions.cs | 41 ++++ .../Lab.ORM.DynamicField.csproj | 6 +- 9 files changed, 367 insertions(+), 34 deletions(-) create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Customer.cs create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/DictionaryStringObjectJsonConverter.cs create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/JsonDocumentExtensions.cs diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/GlobalSteps.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/GlobalSteps.cs index 5dc33201..589504b3 100644 --- a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/GlobalSteps.cs +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/GlobalSteps.cs @@ -8,9 +8,9 @@ public class GlobalSteps [AssemblyCleanup] public static void Cleanup() { - TestAssistant.SetTestEnvironmentVariable(); - using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); - db.Database.EnsureDeleted(); + // TestAssistant.SetTestEnvironmentVariable(); + // using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + // db.Database.EnsureDeleted(); } [AssemblyInitialize] diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs index 3cbd4716..d13799ce 100644 --- a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using EFCore.BulkExtensions; using Faker; using Lab.ORM.DynamicField.EntityModel; @@ -17,7 +18,7 @@ public class UnitTest1 [TestCleanup] public void TestCleanup() { - CleanData(); + // CleanData(); } [TestInitialize] @@ -27,37 +28,111 @@ public void TestInitialize() } [TestMethod] - public void TestMethod1() + public void TestMethod2() + { + var host = CreateHostBuilder(null).Start(); + host.Services.GetService>(); + } + + [TestMethod] + public void 查詢所有資料() { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + var newEmployee = Insert(); using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + var actual = db.Employees + .Where(p => p.Id == newEmployee.Id) + .Select(p => new + { + Profiles = p.Profiles.To>(options), + }) + .FirstOrDefault(); + } - // var id = Guid.NewGuid(); - // db.Employees.Add(new Employee - // { - // Id = id, - // Age = RandomNumber.Next(1, 100), - // Name = Name.FullName(), - // CreatedAt = DateTimeOffset.UtcNow, - // CreatedBy = "Sys", - // - // // Identity = new Identity - // // { - // // Account = "yao", - // // CreateAt = DateTimeOffset.UtcNow, - // // CreateBy = "Sys", - // // Password = "123456", - // // }, - // }); - var generateEmployees = GenerateEmployees(1); - db.AddRange(generateEmployees); - db.SaveChanges(); + [TestMethod] + public void 查詢特定欄位_JsonDoc() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + var newEmployee = Insert(); + using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + var actual = db.Employees + + // .Where(p => p.Profiles.RootElement.GetProperty("long").GetString() == "255") + .Where(p => p.Profiles.RootElement.GetProperty("long").GetInt64() == 255) + .Select(p => new + { + Profiles = p.Profiles.To>(options), + }) + .FirstOrDefault(); } [TestMethod] - public void TestMethod2() + public void 查詢特定欄位_POCO() { - var host = CreateHostBuilder(null).Start(); - host.Services.GetService>(); + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + var newEmployee = Insert(); + using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + var actual = db.Employees + .Where(p => p.Customer.Age > 12) + .Select(p => new + { + p.Customer + + // Order = p.Customer.Orders.Select(p => new { p.Price, p.ShippingAddress }) + // Order = p.Customer + // .Orders + // .Select(p => new Order + // { + // Price = p.Price + // }) + // + + // aa = p.Customer.Orders.ToDictionary(p => p.Price, p => p.ShippingAddress) + }) + + // .AsAsyncEnumerable() + .FirstOrDefault() + ; + } + + [TestMethod] + public void 新增資料() + { + using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + var expected = new Dictionary + { + ["anonymousType"] = new { Prop = 123 }, + ["model"] = new Model { Age = 19, Name = "yao" }, + ["null"] = null!, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["long"] = (long)255, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "String", + ["decimalArray"] = new[] { 1, (decimal)2.1 }, + }; + + var id = Guid.NewGuid(); + db.Employees.Add(new Employee + { + Id = id, + Age = RandomNumber.Next(1, 100), + Name = Name.FullName(), + CreatedAt = DateTimeOffset.UtcNow, + CreatedBy = "Sys", + Profiles = expected.ToJsonDocument(), + }); + + db.SaveChanges(); } private static void CleanData() @@ -94,4 +169,61 @@ private static List GenerateEmployees(int totalCount) }).ToList(); return employees; } + + private static Employee Insert() + { + using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + var expected = new Dictionary + { + ["anonymousType"] = new { Prop = 123 }, + ["model"] = new Model { Age = 19, Name = "yao" }, + ["null"] = null!, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["long"] = (long)255, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "String", + ["decimalArray"] = new[] { 1, (decimal)2.1 }, + }; + + var id = Guid.NewGuid(); + var newEmployee = new Employee + { + Id = id, + Age = RandomNumber.Next(1, 100), + Name = Name.FullName(), + CreatedAt = DateTimeOffset.UtcNow, + CreatedBy = "Sys", + Profiles = expected.ToJsonDocument(), + Customer = new Customer + { + Age = 19, + Name = "小章", + Orders = new[] + { + new Order + { + Price = (decimal)22.1, + ShippingAddress = "台北市" + } + }, + Product = new Product + { + Id = Guid.NewGuid(), + Name = "Mouse" + } + } + }; + db.Employees.Add(newEmployee); + + db.SaveChanges(); + return newEmployee; + } +} + +public record Model +{ + public int Age { get; set; } + + public string Name { get; set; } } \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/AppDependencyInjectionExtensions.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/AppDependencyInjectionExtensions.cs index 1edc7fb4..7b23b667 100644 --- a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/AppDependencyInjectionExtensions.cs +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/AppDependencyInjectionExtensions.cs @@ -27,7 +27,7 @@ public static void AddEntityFramework(this IServiceCollection services) ) // .UseLazyLoadingProxies() - .EnableSensitiveDataLogging() //这将捕获通过迁移发送的更改。 + // .EnableSensitiveDataLogging() //这将捕获通过迁移发送的更改。 .LogTo(Console.WriteLine, LogLevel.Information) //这将捕获所有发送到数据库的SQL。 // .UseLoggerFactory(loggerFactory) ; diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Customer.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Customer.cs new file mode 100644 index 00000000..a0b74e88 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Customer.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; + +namespace Lab.ORM.DynamicField; + +public record Customer +{ + public string Name { get; set; } + + public int Age { get; set; } + + public Order[] Orders { get; set; } + + public Product Product { get; set; } +} + +public record Order +{ + // [JsonPropertyName("OrderPrice")] + public decimal Price { get; set; } + + public string ShippingAddress { get; set; } +} + +public record Product +{ + public Guid Id { get; set; } + public string Name { get; set; } +} \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/DictionaryStringObjectJsonConverter.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/DictionaryStringObjectJsonConverter.cs new file mode 100644 index 00000000..745438fb --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/DictionaryStringObjectJsonConverter.cs @@ -0,0 +1,114 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Lab.ORM.DynamicField; + +public class DictionaryStringObjectJsonConverter : JsonConverter> +{ + public override Dictionary Read(ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException($"JsonTokenType was of type {reader.TokenType}, only objects are supported"); + } + + var results = new Dictionary(); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + return results; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("JsonTokenType was not PropertyName"); + } + + var propertyName = reader.GetString(); + + if (string.IsNullOrWhiteSpace(propertyName)) + { + throw new JsonException("Failed to get property name"); + } + + reader.Read(); + + results.Add(propertyName, this.ReadValue(ref reader, options)); + } + + return results; + } + + public override void Write(Utf8JsonWriter writer, + Dictionary value, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + + foreach (var key in value.Keys) + { + WriteValue(writer, key, value[key], options); + } + + writer.WriteEndObject(); + } + + private object ReadValue(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + if (reader.TryGetDateTimeOffset(out var dateOffset)) + { + return dateOffset; + } + + if (reader.TryGetGuid(out var guid)) + { + return guid; + } + + return reader.GetString(); + case JsonTokenType.False: + case JsonTokenType.True: + return reader.GetBoolean(); + case JsonTokenType.Null: + return null; + case JsonTokenType.Number: + if (reader.TryGetInt64(out var result)) + { + return result; + } + + return reader.GetDecimal(); + case JsonTokenType.StartObject: + return this.Read(ref reader, null, options); + case JsonTokenType.StartArray: + var list = new List(); + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + list.Add(this.ReadValue(ref reader, options)); + } + + return list; + default: + throw new JsonException($"'{reader.TokenType}' is not supported"); + } + } + + private static void WriteValue(Utf8JsonWriter writer, + string key, + object value, + JsonSerializerOptions options) + { + if (key != null) + { + writer.WritePropertyName(key); + } + + JsonSerializer.Serialize(writer, value, options); + } +} \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/Employee.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/Employee.cs index 33c3810f..97b69cfa 100644 --- a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/Employee.cs +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/Employee.cs @@ -1,10 +1,11 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json; namespace Lab.ORM.DynamicField.EntityModel { [Table("Employee")] - public class Employee + public class Employee : IDisposable { [Key] [DatabaseGenerated(DatabaseGeneratedOption.None)] @@ -20,7 +21,19 @@ public class Employee public string Remark { get; set; } - // public virtual Identity Identity { get; set; } + public JsonDocument Profiles { get; set; } + + // [NotMapped] + public Customer Customer + { + get; + set; + + // get => _customer == null ? null : JsonSerializer.Deserialize(_customer); + // set => _customer = JsonSerializer.Serialize(value); + } + + internal string _customer; [Required] public DateTimeOffset CreatedAt { get; set; } @@ -31,5 +44,7 @@ public class Employee public DateTimeOffset? ModifiedAt { get; set; } public string ModifiedBy { get; set; } + + public void Dispose() => this.Profiles?.Dispose(); } } \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/EmployeeDbContext.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/EmployeeDbContext.cs index 3910ea26..c4a011da 100644 --- a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/EmployeeDbContext.cs +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/EmployeeDbContext.cs @@ -22,6 +22,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(p => { + p.Property(p=>p.Profiles).HasColumnType("jsonb"); + p.Property(p=>p.Customer).IsRequired(false).HasColumnType("jsonb"); + // p.Property(p => p._customer).HasColumnName("Customer").HasColumnType("jsonb"); p.Property(p => p.Name).IsRequired().HasMaxLength(50); p.Property(p => p.CreatedAt).IsRequired(); p.Property(p => p.CreatedBy).IsRequired(); diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/JsonDocumentExtensions.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/JsonDocumentExtensions.cs new file mode 100644 index 00000000..7af0b958 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/JsonDocumentExtensions.cs @@ -0,0 +1,41 @@ +using System.Text; +using System.Text.Json; + +namespace Lab.ORM.DynamicField; + +public static class JsonDocumentExtensions +{ + public static T To(this JsonDocument source, + JsonSerializerOptions options = default) + { + return source.Deserialize(options); + } + + public static JsonDocument ToJsonDocument(this T source, + JsonDocumentOptions options = default) + where T : class + { + return JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes(source), options); + } + + public static JsonDocument ToJsonDocument(this string source, + JsonDocumentOptions options = default) + { + return JsonDocument.Parse(source, options); + } + + public static string ToJsonString(this JsonDocument source, + JsonWriterOptions options = default) + { + if (source == null) + { + return null; + } + + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, options); + source.WriteTo(writer); + writer.Flush(); + return Encoding.UTF8.GetString(stream.ToArray()); + } +} \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Lab.ORM.DynamicField.csproj b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Lab.ORM.DynamicField.csproj index e7d06e72..65c79f52 100644 --- a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Lab.ORM.DynamicField.csproj +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/Lab.ORM.DynamicField.csproj @@ -6,13 +6,13 @@ enable - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 092d3569e05a5bcf2c3e14798088e9e7b14019a0 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 29 Mar 2022 16:11:37 +0800 Subject: [PATCH 193/424] refactor --- .../Lab.ORM.DynamicField.UnitTest/UnitTest1.cs | 2 +- .../EntityModel/EmployeeDbContext.cs | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs index d13799ce..74477e56 100644 --- a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs @@ -82,7 +82,7 @@ public void 查詢特定欄位_POCO() var newEmployee = Insert(); using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); var actual = db.Employees - .Where(p => p.Customer.Age > 12) + // .Where(p => p.Customer.Age > 12) .Select(p => new { p.Customer diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/EmployeeDbContext.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/EmployeeDbContext.cs index c4a011da..c094a419 100644 --- a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/EmployeeDbContext.cs +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField/EntityModel/EmployeeDbContext.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; namespace Lab.ORM.DynamicField.EntityModel { @@ -20,10 +21,17 @@ public EmployeeDbContext(DbContextOptions options) //管理索引 protected override void OnModelCreating(ModelBuilder modelBuilder) { + var options = new JsonSerializerOptions(); modelBuilder.Entity(p => { - p.Property(p=>p.Profiles).HasColumnType("jsonb"); - p.Property(p=>p.Customer).IsRequired(false).HasColumnType("jsonb"); + p.Property(p => p.Profiles).HasColumnType("jsonb"); + p.Property(p => p.Customer) + .IsRequired(false) + .HasColumnType("jsonb") + // .HasConversion(p => JsonSerializer.Serialize(p, options), + // p => JsonSerializer.Deserialize(p, options)) + ; + // p.Property(p => p._customer).HasColumnName("Customer").HasColumnType("jsonb"); p.Property(p => p.Name).IsRequired().HasMaxLength(50); p.Property(p => p.CreatedAt).IsRequired(); From 34de8436471112cee37a3e3c998478e23338a426 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 30 Mar 2022 00:16:16 +0800 Subject: [PATCH 194/424] add raw sql --- .../{UnitTest1.cs => EfCoreRawSqlUnitTest.cs} | 25 +- .../EfCoreUnitTest.cs | 241 ++++++++++++++++++ .../Lab.ORM.DynamicField.UnitTest.csproj | 12 +- .../Lab.ORM.DynamicField.UnitTest/Model.cs | 8 + 4 files changed, 264 insertions(+), 22 deletions(-) rename ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/{UnitTest1.cs => EfCoreRawSqlUnitTest.cs} (94%) create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreUnitTest.cs create mode 100644 ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/Model.cs diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreRawSqlUnitTest.cs similarity index 94% rename from ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs rename to ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreRawSqlUnitTest.cs index 74477e56..3f3cb2bf 100644 --- a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/UnitTest1.cs +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreRawSqlUnitTest.cs @@ -13,12 +13,12 @@ namespace Lab.ORM.DynamicField.UnitTest; [TestClass] -public class UnitTest1 +public class EFCoreRawSqlUnitTest { [TestCleanup] public void TestCleanup() { - // CleanData(); + CleanData(); } [TestInitialize] @@ -43,13 +43,12 @@ public void 查詢所有資料() }; var newEmployee = Insert(); using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); - var actual = db.Employees - .Where(p => p.Id == newEmployee.Id) - .Select(p => new - { - Profiles = p.Profiles.To>(options), - }) - .FirstOrDefault(); + + var actual = db.Employees.FromSqlRaw(@" +SELECT * +FROM ""Employee"" AS e +LIMIT 1 +").ToList(); } [TestMethod] @@ -82,6 +81,7 @@ public void 查詢特定欄位_POCO() var newEmployee = Insert(); using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); var actual = db.Employees + // .Where(p => p.Customer.Age > 12) .Select(p => new { @@ -219,11 +219,4 @@ private static Employee Insert() db.SaveChanges(); return newEmployee; } -} - -public record Model -{ - public int Age { get; set; } - - public string Name { get; set; } } \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreUnitTest.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreUnitTest.cs new file mode 100644 index 00000000..ffa2d421 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreUnitTest.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using EFCore.BulkExtensions; +using Faker; +using Lab.ORM.DynamicField.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.ORM.DynamicField.UnitTest; + +[TestClass] +public class EfCoreUnitTest +{ + [TestCleanup] + public void TestCleanup() + { + CleanData(); + } + + [TestInitialize] + public void TestInitialize() + { + CleanData(); + } + + [TestMethod] + public void TestMethod2() + { + var host = CreateHostBuilder(null).Start(); + host.Services.GetService>(); + } + + [TestMethod] + public void 查詢所有資料() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + var newEmployee = Insert(); + using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + var actual = db.Employees + .Where(p => p.Id == newEmployee.Id) + .Select(p => new + { + Profiles = p.Profiles.To>(options), + }) + .FirstOrDefault(); + } + + [TestMethod] + public void 查詢所有資料_Raw_SQL() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + var newEmployee = Insert(); + using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + var queryCommand = @" +SELECT e.""Profiles"" +FROM ""Employee"" AS e +LIMIT 1 +"; + + var actual = db.Employees.FromSqlRaw(queryCommand); + } + + [TestMethod] + public void 查詢特定欄位_JsonDoc() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + var newEmployee = Insert(); + using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + var actual = db.Employees + + // .Where(p => p.Profiles.RootElement.GetProperty("long").GetString() == "255") + .Where(p => p.Profiles.RootElement.GetProperty("long").GetInt64() == 255) + .Select(p => new + { + Profiles = p.Profiles.To>(options), + }) + .FirstOrDefault(); + } + + [TestMethod] + public void 查詢特定欄位_POCO() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + var newEmployee = Insert(); + using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + var actual = db.Employees + + // .Where(p => p.Customer.Age > 12) + .Select(p => new + { + p.Customer + + // Order = p.Customer.Orders.Select(p => new { p.Price, p.ShippingAddress }) + // Order = p.Customer + // .Orders + // .Select(p => new Order + // { + // Price = p.Price + // }) + // + + // aa = p.Customer.Orders.ToDictionary(p => p.Price, p => p.ShippingAddress) + }) + + // .AsAsyncEnumerable() + .FirstOrDefault() + ; + } + + [TestMethod] + public void 新增資料() + { + using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + var expected = new Dictionary + { + ["anonymousType"] = new { Prop = 123 }, + ["model"] = new Model { Age = 19, Name = "yao" }, + ["null"] = null!, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["long"] = (long)255, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "String", + ["decimalArray"] = new[] { 1, (decimal)2.1 }, + }; + + var id = Guid.NewGuid(); + db.Employees.Add(new Employee + { + Id = id, + Age = RandomNumber.Next(1, 100), + Name = Name.FullName(), + CreatedAt = DateTimeOffset.UtcNow, + CreatedBy = "Sys", + Profiles = expected.ToJsonDocument(), + }); + + db.SaveChanges(); + } + + private static void CleanData() + { + using var dbContext = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + dbContext.Employees.BatchDelete(); + } + + private static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureServices((hostBuilder, services) => { TestAssistant.ConfigureTestServices(services); }); + } + + private static List GenerateEmployees(int totalCount) + { + var employees = Enumerable.Range(0, totalCount) + .Select((x, i) => + { + var now = DateTimeOffset.UtcNow; + var sysAccount = "sys"; + return new Employee + { + Id = Guid.NewGuid(), + Age = RandomNumber.Next(1, 100), + Name = Name.FullName(), + CreatedBy = sysAccount, + CreatedAt = now, + ModifiedAt = null, + ModifiedBy = null, + + // Name = Name.First(), + }; + }).ToList(); + return employees; + } + + private static Employee Insert() + { + using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + var expected = new Dictionary + { + ["anonymousType"] = new { Prop = 123 }, + ["model"] = new Model { Age = 19, Name = "yao" }, + ["null"] = null!, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["long"] = (long)255, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "String", + ["decimalArray"] = new[] { 1, (decimal)2.1 }, + }; + + var id = Guid.NewGuid(); + var newEmployee = new Employee + { + Id = id, + Age = RandomNumber.Next(1, 100), + Name = Name.FullName(), + CreatedAt = DateTimeOffset.UtcNow, + CreatedBy = "Sys", + Profiles = expected.ToJsonDocument(), + Customer = new Customer + { + Age = 19, + Name = "小章", + Orders = new[] + { + new Order + { + Price = (decimal)22.1, + ShippingAddress = "台北市" + } + }, + Product = new Product + { + Id = Guid.NewGuid(), + Name = "Mouse" + } + } + }; + db.Employees.Add(newEmployee); + + db.SaveChanges(); + return newEmployee; + } +} \ No newline at end of file diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/Lab.ORM.DynamicField.UnitTest.csproj b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/Lab.ORM.DynamicField.UnitTest.csproj index 116efd17..9512e0fb 100644 --- a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/Lab.ORM.DynamicField.UnitTest.csproj +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/Lab.ORM.DynamicField.UnitTest.csproj @@ -8,11 +8,11 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -24,7 +24,7 @@ - + diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/Model.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/Model.cs new file mode 100644 index 00000000..766e0dc7 --- /dev/null +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/Model.cs @@ -0,0 +1,8 @@ +namespace Lab.ORM.DynamicField.UnitTest; + +public record Model +{ + public int Age { get; set; } + + public string Name { get; set; } +} \ No newline at end of file From 9d6e437de5f18e61122202548ad104e41f1f5d46 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 30 Mar 2022 11:41:41 +0800 Subject: [PATCH 195/424] feat: raw sql query --- .../EfCoreRawSqlUnitTest.cs | 1 + .../EfCoreUnitTest.cs | 40 +++++++++++-------- .../GlobalSteps.cs | 6 +-- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreRawSqlUnitTest.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreRawSqlUnitTest.cs index 3f3cb2bf..b4e89ee2 100644 --- a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreRawSqlUnitTest.cs +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreRawSqlUnitTest.cs @@ -33,6 +33,7 @@ public void TestMethod2() var host = CreateHostBuilder(null).Start(); host.Services.GetService>(); } + [TestMethod] public void 查詢所有資料() diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreUnitTest.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreUnitTest.cs index ffa2d421..7b0b89a3 100644 --- a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreUnitTest.cs +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/EfCoreUnitTest.cs @@ -35,7 +35,7 @@ public void TestMethod2() } [TestMethod] - public void 查詢所有資料() + public void 更新部分欄位() { var options = new JsonSerializerOptions { @@ -43,17 +43,23 @@ public void 查詢所有資料() }; var newEmployee = Insert(); using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); - var actual = db.Employees - .Where(p => p.Id == newEmployee.Id) - .Select(p => new - { - Profiles = p.Profiles.To>(options), - }) - .FirstOrDefault(); + var destEmployee = new Employee + { + Id = newEmployee.Id, + Name = "yao", + ModifiedAt = DateTimeOffset.UtcNow, + ModifiedBy = "sys" + }; + var employeeEntry = db.Entry(destEmployee); + db.Attach(destEmployee); + employeeEntry.Property(p => p.Name).IsModified = true; + employeeEntry.Property(p => p.ModifiedAt).IsModified = true; + employeeEntry.Property(p => p.ModifiedBy).IsModified = true; + var count = db.SaveChanges(); } [TestMethod] - public void 查詢所有資料_Raw_SQL() + public void 查詢所有資料() { var options = new JsonSerializerOptions { @@ -61,15 +67,15 @@ public void 查詢所有資料_Raw_SQL() }; var newEmployee = Insert(); using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); - var queryCommand = @" -SELECT e.""Profiles"" -FROM ""Employee"" AS e -LIMIT 1 -"; - - var actual = db.Employees.FromSqlRaw(queryCommand); + var actual = db.Employees + .Where(p => p.Id == newEmployee.Id) + .Select(p => new + { + Profiles = p.Profiles.To>(options), + }) + .FirstOrDefault(); } - + [TestMethod] public void 查詢特定欄位_JsonDoc() { diff --git a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/GlobalSteps.cs b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/GlobalSteps.cs index 589504b3..5dc33201 100644 --- a/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/GlobalSteps.cs +++ b/ORM/Lab.ORM.DynamicField/Lab.ORM.DynamicField.UnitTest/GlobalSteps.cs @@ -8,9 +8,9 @@ public class GlobalSteps [AssemblyCleanup] public static void Cleanup() { - // TestAssistant.SetTestEnvironmentVariable(); - // using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); - // db.Database.EnsureDeleted(); + TestAssistant.SetTestEnvironmentVariable(); + using var db = TestAssistant.EmployeeDbContextFactory.CreateDbContext(); + db.Database.EnsureDeleted(); } [AssemblyInitialize] From 10aeb3d41f894a620b83d452387191b1712f1573 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 9 Apr 2022 15:38:25 +0800 Subject: [PATCH 196/424] add project --- .../DictionaryStringObjectJsonConverter.cs | 116 ++++++++++++++++++ ...DictionaryFluentValidation.UnitTest.csproj | 21 ++++ .../ProfileValidatorTests.cs | 30 +++++ .../Assistants/ProfileAssistants.cs | 40 ++++++ .../Fields/AddressFieldNames.cs | 30 +++++ .../Fields/BirthdayFieldNames.cs | 26 ++++ .../Fields/GenderFieldNames.cs | 26 ++++ .../Fields/NameFieldNames.cs | 26 ++++ .../Fields/PersonalFieldNames.cs | 52 ++++++++ .../Fields/ProfileFieldNames.cs | 35 ++++++ .../Lab.DictionaryFluentValidation.csproj | 13 ++ .../Validators/EmailFieldValidator.cs | 11 ++ .../Validators/ProfileValidator.cs | 97 +++++++++++++++ .../Validators/RequireFieldValidator.cs | 11 ++ .../Lab.DictionaryValidation.sln | 22 ++++ 15 files changed, 556 insertions(+) create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/DictionaryStringObjectJsonConverter.cs create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/Lab.DictionaryFluentValidation.UnitTest.csproj create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Assistants/ProfileAssistants.cs create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/AddressFieldNames.cs create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldNames.cs create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/PersonalFieldNames.cs create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Lab.DictionaryFluentValidation.csproj create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/RequireFieldValidator.cs create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryValidation.sln diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/DictionaryStringObjectJsonConverter.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/DictionaryStringObjectJsonConverter.cs new file mode 100644 index 00000000..85f37e52 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/DictionaryStringObjectJsonConverter.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Lab.DictionaryFluentValidation.UnitTest; + +public class DictionaryStringObjectJsonConverter : JsonConverter> +{ + public override Dictionary Read(ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException($"JsonTokenType was of type {reader.TokenType}, only objects are supported"); + } + + var results = new Dictionary(); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + return results; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("JsonTokenType was not PropertyName"); + } + + var propertyName = reader.GetString(); + + if (string.IsNullOrWhiteSpace(propertyName)) + { + throw new JsonException("Failed to get property name"); + } + + reader.Read(); + + results.Add(propertyName, this.ReadValue(ref reader, options)); + } + + return results; + } + + public override void Write(Utf8JsonWriter writer, + Dictionary value, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + + foreach (var key in value.Keys) + { + WriteValue(writer, key, value[key], options); + } + + writer.WriteEndObject(); + } + + private object ReadValue(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + if (reader.TryGetDateTimeOffset(out var dateOffset)) + { + return dateOffset; + } + + if (reader.TryGetGuid(out var guid)) + { + return guid; + } + + return reader.GetString(); + case JsonTokenType.False: + case JsonTokenType.True: + return reader.GetBoolean(); + case JsonTokenType.Null: + return null; + case JsonTokenType.Number: + if (reader.TryGetInt64(out var result)) + { + return result; + } + + return reader.GetDecimal(); + case JsonTokenType.StartObject: + return this.Read(ref reader, null, options); + case JsonTokenType.StartArray: + var list = new List(); + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + list.Add(this.ReadValue(ref reader, options)); + } + + return list; + default: + throw new JsonException($"'{reader.TokenType}' is not supported"); + } + } + + private static void WriteValue(Utf8JsonWriter writer, + string key, + object value, + JsonSerializerOptions options) + { + if (key != null) + { + writer.WritePropertyName(key); + } + + JsonSerializer.Serialize(writer, value, options); + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/Lab.DictionaryFluentValidation.UnitTest.csproj b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/Lab.DictionaryFluentValidation.UnitTest.csproj new file mode 100644 index 00000000..3487bd02 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/Lab.DictionaryFluentValidation.UnitTest.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs new file mode 100644 index 00000000..6e0dbfba --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs @@ -0,0 +1,30 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.Json; +using Lab.DictionaryFluentValidation.Fields; +using Lab.DictionaryFluentValidation.Validators; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.DictionaryFluentValidation.UnitTest; + +[TestClass] +public class ProfileValidatorTests +{ + [TestMethod] + public void TestMethod1() + { + var data = new Dictionary() + { + { "contactEmail", "yao" }, + { "Name", new { firstName = "yao", lastName = "yu", fullName = "yao-chang.yu" } }, + }; + var options = new JsonSerializerOptions() + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + + + var profileValidator = new ProfileValidator(); + var validationResult = profileValidator.Validate(data); + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Assistants/ProfileAssistants.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Assistants/ProfileAssistants.cs new file mode 100644 index 00000000..e645a267 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Assistants/ProfileAssistants.cs @@ -0,0 +1,40 @@ +using System.Reflection; + +namespace Lab.DictionaryFluentValidation.Assistants; + +public class ProfileAssistants +{ + public static IEnumerable GetFieldNames() + { + var type = typeof(T); + + var bindingFlags = BindingFlags.Public + | BindingFlags.Static + ; + var results = new List(); + var fieldInfosInfos = type.GetFields(bindingFlags); + foreach (var fieldInfo in fieldInfosInfos) + { + results.Add(fieldInfo.GetValue(null).ToString()); + } + + return results; + } + + public static Dictionary GetFields() + { + var type = typeof(T); + + var bindingFlags = BindingFlags.Public + | BindingFlags.Static + ; + var results = new Dictionary(); + var fieldInfosInfos = type.GetFields(bindingFlags); + foreach (var fieldInfo in fieldInfosInfos) + { + results.Add(fieldInfo.GetValue(null).ToString(), null); + } + + return results; + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/AddressFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/AddressFieldNames.cs new file mode 100644 index 00000000..f7b314c2 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/AddressFieldNames.cs @@ -0,0 +1,30 @@ +using Lab.DictionaryFluentValidation.Assistants; + +namespace Lab.DictionaryFluentValidation.Fields; + +public class AddressFieldNames +{ + public static readonly string Address1 = "address1"; + public static readonly string Address2 = "address2"; + public static readonly string District = "district"; + public static readonly string CityTown = "cityTown"; + public static readonly string Province = "province"; + public static readonly string PostalCode = "postalCode"; + public static readonly string Country = "country"; + + private static readonly Lazy> s_fieldNameLazy = + new(ProfileAssistants.GetFieldNames); + + private static readonly Lazy> s_fieldLazy = + new(ProfileAssistants.GetFields); + + public static IEnumerable GetFieldNames() + { + return s_fieldNameLazy.Value; + } + + public static Dictionary GetFields() + { + return s_fieldLazy.Value; + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs new file mode 100644 index 00000000..f5bc2df9 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs @@ -0,0 +1,26 @@ +using Lab.DictionaryFluentValidation.Assistants; + +namespace Lab.DictionaryFluentValidation.Fields; + +public class BirthdayFieldNames +{ + public static readonly string Year = "year"; + public static readonly string Month = "month"; + public static readonly string Day = "day"; + + private static readonly Lazy> s_fieldNameLazy = + new(ProfileAssistants.GetFieldNames); + + private static readonly Lazy> s_fieldLazy = + new(ProfileAssistants.GetFields); + + public static IEnumerable GetFieldNames() + { + return s_fieldNameLazy.Value; + } + + public static Dictionary GetFields() + { + return s_fieldLazy.Value; + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldNames.cs new file mode 100644 index 00000000..41139a4c --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldNames.cs @@ -0,0 +1,26 @@ +using Lab.DictionaryFluentValidation.Assistants; + +namespace Lab.DictionaryFluentValidation.Fields; + +public class GenderFieldNames +{ + public static string Male = "male"; + public static string Female = "female"; + public static string NotAvailable = "notAvailable"; + + private static readonly Lazy> s_fieldNameLazy = + new(ProfileAssistants.GetFieldNames); + + private static readonly Lazy> s_fieldLazy = + new(ProfileAssistants.GetFields); + + public static IEnumerable GetFieldNames() + { + return s_fieldNameLazy.Value; + } + + public static Dictionary GetFields() + { + return s_fieldLazy.Value; + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs new file mode 100644 index 00000000..3bdba673 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs @@ -0,0 +1,26 @@ +using Lab.DictionaryFluentValidation.Assistants; + +namespace Lab.DictionaryFluentValidation.Fields; + +public class NameFieldNames +{ + public static readonly string FirstName = "firstName"; + public static readonly string LastName = "lastName"; + public static readonly string FullName = "fullName"; + + private static readonly Lazy> s_fieldNameLazy = + new(ProfileAssistants.GetFieldNames); + + private static readonly Lazy> s_fieldLazy = + new(ProfileAssistants.GetFields); + + public static IEnumerable GetFieldNames() + { + return s_fieldNameLazy.Value; + } + + public static Dictionary GetFields() + { + return s_fieldLazy.Value; + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/PersonalFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/PersonalFieldNames.cs new file mode 100644 index 00000000..69cd8501 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/PersonalFieldNames.cs @@ -0,0 +1,52 @@ +using Lab.DictionaryFluentValidation.Assistants; + +namespace Lab.DictionaryFluentValidation.Fields; + +public class PersonalFieldNames +{ + /// + /// 身分證字號 + /// + public static string IdentityCardId = "identityCardId"; + + /// + /// 教育程度 + /// + public static string Education = "education"; + + /// + /// 職業 + /// + public static string Profession = "profession"; + + /// + /// 婚姻狀態 + /// + public static string MaritalStatus = "maritalStatus"; + + /// + /// 家屬 + /// + public static string Dependents = "dependents"; + + /// + /// 年收入 + /// + public static string AnnualIncome = "annualIncome"; + + private static readonly Lazy> s_fieldNameLazy = + new(ProfileAssistants.GetFieldNames); + + private static readonly Lazy> s_fieldLazy = + new(ProfileAssistants.GetFields); + + public static IEnumerable GetFieldNames() + { + return s_fieldNameLazy.Value; + } + + public static Dictionary GetFields() + { + return s_fieldLazy.Value; + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs new file mode 100644 index 00000000..21bcb040 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs @@ -0,0 +1,35 @@ +using Lab.DictionaryFluentValidation.Assistants; + +namespace Lab.DictionaryFluentValidation.Fields; + +public class ProfileFieldNames +{ + public const string Name = "name"; + public const string Gender = "gender"; + public const string Birthday = "birthday"; + public const string EComJoinDateTime = "eComJoinDateTime"; + public const string MigrateJoinDateTime = "migrateJoinDateTime"; + public const string ContactEmail = "contactEmail"; + public const string Local = "locale"; + public const string BrandMemberId = "brandMemberId"; + public const string BarcodeValue = "barcodeValue"; + public const string Address = "address"; + public const string Personal = "personal"; + public const string Custom = "custom"; + + private static readonly Lazy> s_fieldNames = + new(ProfileAssistants.GetFieldNames); + + private static Lazy> s_fields = + new(ProfileAssistants.GetFields); + + public static IEnumerable GetFieldNames() + { + return s_fieldNames.Value; + } + + public static Dictionary GetFields() + { + return s_fields.Value; + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Lab.DictionaryFluentValidation.csproj b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Lab.DictionaryFluentValidation.csproj new file mode 100644 index 00000000..f03eb636 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Lab.DictionaryFluentValidation.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs new file mode 100644 index 00000000..d329fcd5 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs @@ -0,0 +1,11 @@ +using FluentValidation; + +namespace Lab.DictionaryFluentValidation.Validators; + +public class EmailFieldValidator : AbstractValidator +{ + public EmailFieldValidator() + { + this.RuleFor(customer => customer).EmailAddress(); + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs new file mode 100644 index 00000000..63d98237 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs @@ -0,0 +1,97 @@ +using FluentValidation; +using FluentValidation.Results; +using Lab.DictionaryFluentValidation.Fields; + +namespace Lab.DictionaryFluentValidation.Validators; + +public class ProfileValidator : AbstractValidator> +{ + private readonly Lazy _emailFieldValidatorLazy = new(new EmailFieldValidator()); + + public ProfileValidator() + { + this._emailFieldValidatorLazy = new(new EmailFieldValidator()); + } + + protected override bool PreValidate(ValidationContext> context, ValidationResult result) + { + if (ValidateSupportFields(context) == false) + { + return false; + } + + var instances = context.InstanceToValidate; + foreach (var item in instances) + { + switch (item.Key) + { + case ProfileFieldNames.ContactEmail: + { + var validationResult = this._emailFieldValidatorLazy.Value.Validate(item.Value.ToString()); + if (validationResult.IsValid == false) + { + foreach (var error in validationResult.Errors) + { + var failure = new ValidationFailure(ProfileFieldNames.ContactEmail, + error.ErrorMessage, + error.AttemptedValue) + { + ErrorCode = error.ErrorCode, + }; + context.AddFailure(failure); + } + } + + break; + } + } + } + + return true; + } + + private static bool ValidateSupportFields(ValidationContext> context) + { + var instances = context.InstanceToValidate; + var isValid = true; + foreach (var item in instances) + { + var fieldName = item.Key; + var fieldValue = item.Value; + switch (fieldName) + { + case ProfileFieldNames.Name: + { + var propertyInfos = fieldValue.GetType().GetProperties(); + foreach (var propertyInfo in propertyInfos) + { + isValid = IsSupportFields(context, NameFieldNames.GetFields(), propertyInfo.Name); + } + + break; + } + default: + { + isValid = IsSupportFields(context, ProfileFieldNames.GetFields(), fieldName); + break; + } + } + } + + return isValid; + } + + private static bool IsSupportFields(ValidationContext> context, + Dictionary sourceFields, + string destFieldName) + { + var notExistKey = sourceFields.ContainsKey(destFieldName) == false; + if (notExistKey) + { + context.AddFailure(destFieldName, $"not support column '{destFieldName}'"); + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/RequireFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/RequireFieldValidator.cs new file mode 100644 index 00000000..6f81132b --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/RequireFieldValidator.cs @@ -0,0 +1,11 @@ +using FluentValidation; + +namespace Lab.DictionaryFluentValidation.Validators; + +public class RequireFieldValidator : AbstractValidator +{ + public RequireFieldValidator() + { + this.RuleFor(customer => customer).NotEmpty(); + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryValidation.sln b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryValidation.sln new file mode 100644 index 00000000..ab18b294 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryValidation.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.DictionaryFluentValidation", "Lab.DictionaryFluentValidation\Lab.DictionaryFluentValidation.csproj", "{F351E4A0-8F8E-4CEF-BE2A-D158E420DFB6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.DictionaryFluentValidation.UnitTest", "Lab.DictionaryFluentValidation.UnitTest\Lab.DictionaryFluentValidation.UnitTest.csproj", "{3C5718DF-EA0D-472B-8067-B5C736923125}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F351E4A0-8F8E-4CEF-BE2A-D158E420DFB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F351E4A0-8F8E-4CEF-BE2A-D158E420DFB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F351E4A0-8F8E-4CEF-BE2A-D158E420DFB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F351E4A0-8F8E-4CEF-BE2A-D158E420DFB6}.Release|Any CPU.Build.0 = Release|Any CPU + {3C5718DF-EA0D-472B-8067-B5C736923125}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C5718DF-EA0D-472B-8067-B5C736923125}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C5718DF-EA0D-472B-8067-B5C736923125}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C5718DF-EA0D-472B-8067-B5C736923125}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From 5280afc4985e5283b9194be38e2676a86dee3033 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 10 Apr 2022 00:12:02 +0800 Subject: [PATCH 197/424] add RequireFieldValidator --- .../ProfileValidatorTests.cs | 72 +++++++- .../Fields/NameFieldNames.cs | 6 +- .../Validators/EmailFieldValidator.cs | 2 +- .../Validators/ProfileValidator.cs | 166 ++++++++++++------ .../Validators/RequireFieldValidator.cs | 4 +- 5 files changed, 189 insertions(+), 61 deletions(-) diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs index 6e0dbfba..dae5514d 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Text.Json; using Lab.DictionaryFluentValidation.Fields; using Lab.DictionaryFluentValidation.Validators; @@ -11,20 +12,81 @@ namespace Lab.DictionaryFluentValidation.UnitTest; public class ProfileValidatorTests { [TestMethod] - public void TestMethod1() + public void 郵件格式錯誤() { var data = new Dictionary() { { "contactEmail", "yao" }, - { "Name", new { firstName = "yao", lastName = "yu", fullName = "yao-chang.yu" } }, }; - var options = new JsonSerializerOptions() + var profileValidator = new ProfileValidator(); + var validationResult = profileValidator.Validate(data); + Assert.AreEqual(false, validationResult.IsValid); + var actualError = validationResult.Errors.First(); + Assert.AreEqual("contactEmail", actualError.PropertyName); + Assert.AreEqual("EmailValidator", actualError.ErrorCode); + Assert.AreEqual("'' is not a valid email address.", actualError.ErrorMessage); + } + + [TestMethod] + public void 必填欄位為空() + { + var data = new Dictionary() + { + { "name", new { firstName = "yao", lastName = "yu", fullName = "" } }, + }; + var profileValidator = new ProfileValidator(); + var validationResult = profileValidator.Validate(data); + Assert.AreEqual(false, validationResult.IsValid); + var actualError = validationResult.Errors.First(); + Assert.AreEqual("name.fullName", actualError.PropertyName); + Assert.AreEqual("NotEmptyValidator", actualError.ErrorCode); + Assert.AreEqual("'' must not be empty.", actualError.ErrorMessage); + } + + [TestMethod] + public void 使用不支援的Key() + { + var data = new Dictionary() + { + { "Hi", null }, + }; + + var profileValidator = new ProfileValidator(); + var validationResult = profileValidator.Validate(data); + Assert.AreEqual(false, validationResult.IsValid); + var actualError = validationResult.Errors.First(); + Assert.AreEqual("Hi", actualError.PropertyName); + Assert.AreEqual("NotSupportValidator", actualError.ErrorCode); + Assert.AreEqual("not support column 'Hi'", actualError.ErrorMessage); + } + + [TestMethod] + public void Key區分大小寫() + { + var data = new Dictionary() + { + { "Name", null }, + }; + + var profileValidator = new ProfileValidator(); + var validationResult = profileValidator.Validate(data); + Assert.AreEqual(false, validationResult.IsValid); + var actualError = validationResult.Errors.First(); + Assert.AreEqual("Name", actualError.PropertyName); + Assert.AreEqual("NotSupportValidator", actualError.ErrorCode); + Assert.AreEqual("not support column 'Name'", actualError.ErrorMessage); + } + + [TestMethod] + public void 使用支援的Key() + { + var data = new Dictionary() { - Converters = { new DictionaryStringObjectJsonConverter() } + { "name", null }, }; - var profileValidator = new ProfileValidator(); var validationResult = profileValidator.Validate(data); + Assert.AreEqual(true, validationResult.IsValid); } } \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs index 3bdba673..cd6c8dd1 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs @@ -4,9 +4,9 @@ namespace Lab.DictionaryFluentValidation.Fields; public class NameFieldNames { - public static readonly string FirstName = "firstName"; - public static readonly string LastName = "lastName"; - public static readonly string FullName = "fullName"; + public const string FirstName = "firstName"; + public const string LastName = "lastName"; + public const string FullName = "fullName"; private static readonly Lazy> s_fieldNameLazy = new(ProfileAssistants.GetFieldNames); diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs index d329fcd5..d9b46bc4 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs @@ -6,6 +6,6 @@ public class EmailFieldValidator : AbstractValidator { public EmailFieldValidator() { - this.RuleFor(customer => customer).EmailAddress(); + this.RuleFor(p => p).EmailAddress(); } } \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs index 63d98237..c477c552 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs @@ -6,92 +6,158 @@ namespace Lab.DictionaryFluentValidation.Validators; public class ProfileValidator : AbstractValidator> { - private readonly Lazy _emailFieldValidatorLazy = new(new EmailFieldValidator()); + private static readonly Lazy s_emailFieldValidatorLazy = new(() => new EmailFieldValidator()); - public ProfileValidator() + private static readonly Lazy s_requireFieldValidatorLazy = + new(() => new RequireFieldValidator()); + + private static bool IsNotSupportFields(ValidationContext> context) { - this._emailFieldValidatorLazy = new(new EmailFieldValidator()); + var instances = context.InstanceToValidate; + var isNotSupports = new List(); + foreach (var item in instances) + { + var fieldName = item.Key; + var fieldValue = item.Value; + + switch (fieldName) + { + case ProfileFieldNames.Name: + isNotSupports.Add(IsNotSupportNestFields(NameFieldNames.GetFields(), fieldValue, context)); + break; + case ProfileFieldNames.Birthday: + isNotSupports.Add(IsNotSupportNestFields(BirthdayFieldNames.GetFields(), fieldValue, context)); + break; + default: + isNotSupports.Add(IsNotSupportFields(ProfileFieldNames.GetFields(), fieldName, context)); + break; + } + } + + return isNotSupports.Any(p => p); } - protected override bool PreValidate(ValidationContext> context, ValidationResult result) + private static bool IsNotSupportFields(Dictionary sourceFields, + string destFieldName, + ValidationContext> context) { - if (ValidateSupportFields(context) == false) + var isSNotSupport = sourceFields.ContainsKey(destFieldName) == false; + if (isSNotSupport) { - return false; + var failure = new ValidationFailure(destFieldName, + $"not support column '{destFieldName}'") + { + ErrorCode = "NotSupportValidator", + }; + context.AddFailure(failure); } - var instances = context.InstanceToValidate; - foreach (var item in instances) + return isSNotSupport; + } + + private static bool IsNotSupportNestFields(Dictionary sourceFields, + object destValue, + ValidationContext> context) + { + if (destValue == null) { - switch (item.Key) - { - case ProfileFieldNames.ContactEmail: - { - var validationResult = this._emailFieldValidatorLazy.Value.Validate(item.Value.ToString()); - if (validationResult.IsValid == false) - { - foreach (var error in validationResult.Errors) - { - var failure = new ValidationFailure(ProfileFieldNames.ContactEmail, - error.ErrorMessage, - error.AttemptedValue) - { - ErrorCode = error.ErrorCode, - }; - context.AddFailure(failure); - } - } + return false; + } - break; - } - } + var isNotSupports = new List(); + + var propertyInfos = destValue.GetType().GetProperties(); + foreach (var propertyInfo in propertyInfos) + { + isNotSupports.Add(IsNotSupportFields(sourceFields, propertyInfo.Name, context)); } - return true; + return isNotSupports.Any(p => p); } - private static bool ValidateSupportFields(ValidationContext> context) + protected override bool PreValidate(ValidationContext> context, ValidationResult result) { + if (IsNotSupportFields(context)) + { + return false; + } + var instances = context.InstanceToValidate; - var isValid = true; foreach (var item in instances) { var fieldName = item.Key; var fieldValue = item.Value; + if (fieldValue == null) + { + continue; + } + switch (fieldName) { - case ProfileFieldNames.Name: + case ProfileFieldNames.ContactEmail: { - var propertyInfos = fieldValue.GetType().GetProperties(); - foreach (var propertyInfo in propertyInfos) - { - isValid = IsSupportFields(context, NameFieldNames.GetFields(), propertyInfo.Name); - } - + ValidateEmail(fieldValue.ToString(), context); break; } - default: + case ProfileFieldNames.Name: { - isValid = IsSupportFields(context, ProfileFieldNames.GetFields(), fieldName); + this.ValidateName(fieldName, fieldValue, context); break; } } } - return isValid; + return true; + } + + private static void ValidateEmail(string value, ValidationContext> context) + { + var validationResult = s_emailFieldValidatorLazy.Value.Validate(value); + ValidationContextAddFailure(ProfileFieldNames.ContactEmail, validationResult, context); + } + + private void ValidateName(string fieldName, + object fieldValue, ValidationContext> context) + { + var propertyInfos = fieldValue.GetType().GetProperties(); + foreach (var propertyInfo in propertyInfos) + { + var value = propertyInfo.GetValue(fieldValue); + switch (propertyInfo.Name) + { + case NameFieldNames.FullName: + ValidateRequireField(context, $"{fieldName}.{NameFieldNames.FullName}", value); + break; + } + } + } + + private static void ValidateRequireField(ValidationContext> context, + string fieldName, + object fieldValue) + { + var validationResult = s_requireFieldValidatorLazy.Value.Validate(fieldValue); + ValidationContextAddFailure(fieldName, validationResult, context); } - private static bool IsSupportFields(ValidationContext> context, - Dictionary sourceFields, - string destFieldName) + private static void ValidationContextAddFailure(string fieldName, + ValidationResult validationResult, + ValidationContext> context) { - var notExistKey = sourceFields.ContainsKey(destFieldName) == false; - if (notExistKey) + if (validationResult.IsValid) { - context.AddFailure(destFieldName, $"not support column '{destFieldName}'"); - return false; + return; } - return true; + foreach (var error in validationResult.Errors) + { + var failure = new ValidationFailure(fieldName, + error.ErrorMessage, + error.AttemptedValue) + { + ErrorCode = error.ErrorCode, + }; + context.AddFailure(failure); + } } } \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/RequireFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/RequireFieldValidator.cs index 6f81132b..58013712 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/RequireFieldValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/RequireFieldValidator.cs @@ -2,10 +2,10 @@ namespace Lab.DictionaryFluentValidation.Validators; -public class RequireFieldValidator : AbstractValidator +public class RequireFieldValidator : AbstractValidator { public RequireFieldValidator() { - this.RuleFor(customer => customer).NotEmpty(); + this.RuleFor(p => p).NotEmpty().NotNull(); } } \ No newline at end of file From c869ca9b96c4c1ff486f87ca4072702610fdffae Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 10 Apr 2022 11:54:37 +0800 Subject: [PATCH 198/424] feat: NameFieldValidator --- .../ProfileValidatorTests.cs | 8 +-- .../Validators/EmailFieldValidator.cs | 27 +++++++- .../Validators/NameFieldValidator.cs | 54 +++++++++++++++ .../Validators/ProfileValidator.cs | 69 +++---------------- 4 files changed, 93 insertions(+), 65 deletions(-) create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/NameFieldValidator.cs diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs index dae5514d..e577a3f6 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs @@ -24,7 +24,7 @@ public void 郵件格式錯誤() var actualError = validationResult.Errors.First(); Assert.AreEqual("contactEmail", actualError.PropertyName); Assert.AreEqual("EmailValidator", actualError.ErrorCode); - Assert.AreEqual("'' is not a valid email address.", actualError.ErrorMessage); + Assert.AreEqual("'contactEmail' is not a valid email address.", actualError.ErrorMessage); } [TestMethod] @@ -32,15 +32,15 @@ public void 必填欄位為空() { var data = new Dictionary() { - { "name", new { firstName = "yao", lastName = "yu", fullName = "" } }, + { "name", new { firstName = "yao", lastName = "", fullName = "" } }, }; var profileValidator = new ProfileValidator(); var validationResult = profileValidator.Validate(data); - Assert.AreEqual(false, validationResult.IsValid); + // Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); Assert.AreEqual("name.fullName", actualError.PropertyName); Assert.AreEqual("NotEmptyValidator", actualError.ErrorCode); - Assert.AreEqual("'' must not be empty.", actualError.ErrorMessage); + Assert.AreEqual("'name.fullName' must not be empty.", actualError.ErrorMessage); } [TestMethod] diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs index d9b46bc4..33c02738 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs @@ -1,11 +1,32 @@ using FluentValidation; +using FluentValidation.Results; +using Lab.DictionaryFluentValidation.Fields; namespace Lab.DictionaryFluentValidation.Validators; -public class EmailFieldValidator : AbstractValidator +public class EmailFieldValidator : AbstractValidator { - public EmailFieldValidator() + /// + /// return true 繼續往下驗證 + /// https://docs.fluentvalidation.net/en/latest/advanced.html?highlight=PreValidate#prevalidate + /// + /// + /// + /// + protected override bool PreValidate(ValidationContext context, ValidationResult result) { - this.RuleFor(p => p).EmailAddress(); + var isValid = true; + var instance = context.InstanceToValidate; + if (instance == null) + { + return isValid; + } + + this.RuleFor(p => p.ToString()) + .NotEmpty() + .WithName(ProfileFieldNames.ContactEmail) + .EmailAddress(); + ; + return isValid; } } \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/NameFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/NameFieldValidator.cs new file mode 100644 index 00000000..bda13d1e --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/NameFieldValidator.cs @@ -0,0 +1,54 @@ +using FluentValidation; +using FluentValidation.Results; +using Lab.DictionaryFluentValidation.Fields; + +namespace Lab.DictionaryFluentValidation.Validators; + +public class NameFieldValidator : AbstractValidator +{ + /// + /// return true 繼續往下驗證 + /// https://docs.fluentvalidation.net/en/latest/advanced.html?highlight=PreValidate#prevalidate + /// + /// + /// + /// + protected override bool PreValidate(ValidationContext context, ValidationResult result) + { + var isValid = true; + var instance = context.InstanceToValidate; + if (instance == null) + { + return isValid; + } + + var propertyInfos = instance.GetType().GetProperties(); + foreach (var propertyInfo in propertyInfos) + { + var value = propertyInfo.GetValue(instance); + if (value == null) + { + return isValid; + } + + var propertyName = $"name.{propertyInfo.Name}"; + switch (propertyInfo.Name) + { + case NameFieldNames.FirstName: + break; + case NameFieldNames.LastName: + break; + case NameFieldNames.FullName: + this.RuleFor(p => value) + .NotEmpty() + .WithName(propertyName) + .OverridePropertyName(propertyName) + ; + + break; + } + } + + return isValid; + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs index c477c552..6f37f469 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs @@ -6,11 +6,10 @@ namespace Lab.DictionaryFluentValidation.Validators; public class ProfileValidator : AbstractValidator> { - private static readonly Lazy s_emailFieldValidatorLazy = new(() => new EmailFieldValidator()); - - private static readonly Lazy s_requireFieldValidatorLazy = - new(() => new RequireFieldValidator()); - + private static readonly Lazy s_emailFieldValidatorLazy + = new(() => new EmailFieldValidator()); + private static readonly Lazy s_nameFieldValidatorLazy + = new(() => new NameFieldValidator()); private static bool IsNotSupportFields(ValidationContext> context) { var instances = context.InstanceToValidate; @@ -96,12 +95,17 @@ protected override bool PreValidate(ValidationContext { case ProfileFieldNames.ContactEmail: { - ValidateEmail(fieldValue.ToString(), context); + RuleFor(p => p[fieldName]) + .SetValidator(p => s_emailFieldValidatorLazy.Value) + ; + break; } case ProfileFieldNames.Name: { - this.ValidateName(fieldName, fieldValue, context); + RuleFor(p => p[fieldName]) + .SetValidator(p => s_nameFieldValidatorLazy.Value) + ; break; } } @@ -109,55 +113,4 @@ protected override bool PreValidate(ValidationContext return true; } - - private static void ValidateEmail(string value, ValidationContext> context) - { - var validationResult = s_emailFieldValidatorLazy.Value.Validate(value); - ValidationContextAddFailure(ProfileFieldNames.ContactEmail, validationResult, context); - } - - private void ValidateName(string fieldName, - object fieldValue, ValidationContext> context) - { - var propertyInfos = fieldValue.GetType().GetProperties(); - foreach (var propertyInfo in propertyInfos) - { - var value = propertyInfo.GetValue(fieldValue); - switch (propertyInfo.Name) - { - case NameFieldNames.FullName: - ValidateRequireField(context, $"{fieldName}.{NameFieldNames.FullName}", value); - break; - } - } - } - - private static void ValidateRequireField(ValidationContext> context, - string fieldName, - object fieldValue) - { - var validationResult = s_requireFieldValidatorLazy.Value.Validate(fieldValue); - ValidationContextAddFailure(fieldName, validationResult, context); - } - - private static void ValidationContextAddFailure(string fieldName, - ValidationResult validationResult, - ValidationContext> context) - { - if (validationResult.IsValid) - { - return; - } - - foreach (var error in validationResult.Errors) - { - var failure = new ValidationFailure(fieldName, - error.ErrorMessage, - error.AttemptedValue) - { - ErrorCode = error.ErrorCode, - }; - context.AddFailure(failure); - } - } } \ No newline at end of file From 507b4074beaee1be65b855a7ce658cf411667367 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 10 Apr 2022 12:50:26 +0800 Subject: [PATCH 199/424] feat: BirthdayFieldValidator --- .../ProfileValidatorTests.cs | 85 +++++++++++++---- .../Fields/BirthdayFieldNames.cs | 6 +- .../Validators/BirthdayFieldValidator.cs | 91 +++++++++++++++++++ .../Validators/NameFieldValidator.cs | 2 +- .../Validators/ProfileValidator.cs | 16 +++- .../Validators/RequireFieldValidator.cs | 11 --- 6 files changed, 174 insertions(+), 37 deletions(-) create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/BirthdayFieldValidator.cs delete mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/RequireFieldValidator.cs diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs index e577a3f6..f3a0831a 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs @@ -1,8 +1,5 @@ -using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Text.Json; -using Lab.DictionaryFluentValidation.Fields; using Lab.DictionaryFluentValidation.Validators; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -12,30 +9,32 @@ namespace Lab.DictionaryFluentValidation.UnitTest; public class ProfileValidatorTests { [TestMethod] - public void 郵件格式錯誤() + public void Key區分大小寫() { - var data = new Dictionary() + var data = new Dictionary { - { "contactEmail", "yao" }, + { "Name", null }, }; + var profileValidator = new ProfileValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); - Assert.AreEqual("contactEmail", actualError.PropertyName); - Assert.AreEqual("EmailValidator", actualError.ErrorCode); - Assert.AreEqual("'contactEmail' is not a valid email address.", actualError.ErrorMessage); + Assert.AreEqual("Name", actualError.PropertyName); + Assert.AreEqual("NotSupportValidator", actualError.ErrorCode); + Assert.AreEqual("not support column 'Name'", actualError.ErrorMessage); } [TestMethod] public void 必填欄位為空() { - var data = new Dictionary() + var data = new Dictionary { { "name", new { firstName = "yao", lastName = "", fullName = "" } }, }; var profileValidator = new ProfileValidator(); var validationResult = profileValidator.Validate(data); + // Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); Assert.AreEqual("name.fullName", actualError.PropertyName); @@ -46,7 +45,7 @@ public void 必填欄位為空() [TestMethod] public void 使用不支援的Key() { - var data = new Dictionary() + var data = new Dictionary { { "Hi", null }, }; @@ -61,32 +60,78 @@ public void 使用不支援的Key() } [TestMethod] - public void Key區分大小寫() + public void 使用支援的Key() { - var data = new Dictionary() + var data = new Dictionary { - { "Name", null }, + { "name", null }, + }; + + var profileValidator = new ProfileValidator(); + var validationResult = profileValidator.Validate(data); + Assert.AreEqual(true, validationResult.IsValid); + } + + [TestMethod] + public void 二月三十是非法日期() + { + var data = new Dictionary + { + { "birthday", new { year = 2000, month = 2, day = 30 } }, }; var profileValidator = new ProfileValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); - Assert.AreEqual("Name", actualError.PropertyName); - Assert.AreEqual("NotSupportValidator", actualError.ErrorCode); - Assert.AreEqual("not support column 'Name'", actualError.ErrorMessage); + Assert.AreEqual("birthday", actualError.PropertyName); + Assert.AreEqual("BirthdayFieldValidator", actualError.ErrorCode); + Assert.AreEqual("year:2000,month:2,day:31 is invalid date format", actualError.ErrorMessage); } [TestMethod] - public void 使用支援的Key() + public void 沒有年是非法日期() { - var data = new Dictionary() + var data = new Dictionary { - { "name", null }, + { "birthday", new { month = 2, day = 30 } }, + }; + + var profileValidator = new ProfileValidator(); + var validationResult = profileValidator.Validate(data); + Assert.AreEqual(false, validationResult.IsValid); + var actualError = validationResult.Errors.First(); + Assert.AreEqual("birthday.year", actualError.PropertyName); + Assert.AreEqual("BirthdayFieldValidator", actualError.ErrorCode); + Assert.AreEqual("'birthday.year' must not be empty.", actualError.ErrorMessage); + } + + [TestMethod] + public void 日期內容為非法值() + { + var data = new Dictionary + { + { "birthday", null }, }; var profileValidator = new ProfileValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(true, validationResult.IsValid); } + + [TestMethod] + public void 郵件格式錯誤() + { + var data = new Dictionary + { + { "contactEmail", "yao" }, + }; + var profileValidator = new ProfileValidator(); + var validationResult = profileValidator.Validate(data); + Assert.AreEqual(false, validationResult.IsValid); + var actualError = validationResult.Errors.First(); + Assert.AreEqual("contactEmail", actualError.PropertyName); + Assert.AreEqual("EmailValidator", actualError.ErrorCode); + Assert.AreEqual("'contactEmail' is not a valid email address.", actualError.ErrorMessage); + } } \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs index f5bc2df9..4f0471af 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs @@ -4,9 +4,9 @@ namespace Lab.DictionaryFluentValidation.Fields; public class BirthdayFieldNames { - public static readonly string Year = "year"; - public static readonly string Month = "month"; - public static readonly string Day = "day"; + public const string Year = "year"; + public const string Month = "month"; + public const string Day = "day"; private static readonly Lazy> s_fieldNameLazy = new(ProfileAssistants.GetFieldNames); diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/BirthdayFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/BirthdayFieldValidator.cs new file mode 100644 index 00000000..c544d5b6 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/BirthdayFieldValidator.cs @@ -0,0 +1,91 @@ +using FluentValidation; +using FluentValidation.Results; +using Lab.DictionaryFluentValidation.Fields; + +namespace Lab.DictionaryFluentValidation.Validators; + +public class BirthdayFieldValidator : AbstractValidator +{ + private const string ErrorCode = nameof(BirthdayFieldValidator); + + /// + /// return true 繼續往下驗證 + /// https://docs.fluentvalidation.net/en/latest/advanced.html?highlight=PreValidate#prevalidate + /// + /// + /// + /// + protected override bool PreValidate(ValidationContext context, ValidationResult result) + { + var isValid = true; + var instance = context.InstanceToValidate; + if (instance == null) + { + return isValid; + } + + var propertyInfos = instance.GetType().GetProperties(); + var birthday = new Dictionary(); + foreach (var propertyInfo in propertyInfos) + { + var value = propertyInfo.GetValue(instance); + if (value == null) + { + continue; + } + + birthday.Add(propertyInfo.Name, Convert.ToInt32(value)); + } + + var srcBirthdayFields = BirthdayFieldNames.GetFields(); + isValid = HasRequireField(context, srcBirthdayFields, birthday); + if (isValid == false) + { + return isValid; + } + + var year = birthday[BirthdayFieldNames.Year]; + var month = birthday[BirthdayFieldNames.Month]; + var day = birthday[BirthdayFieldNames.Day]; + try + { + var birthday2 = new DateTime(year, month, day); + } + catch (Exception e) + { + var errorMsg = $"{BirthdayFieldNames.Year}:{year}," + + $"{BirthdayFieldNames.Month}:{month}," + + $"{BirthdayFieldNames.Day}:{day} is invalid date format"; + + var validationFailure = new ValidationFailure(ProfileFieldNames.Birthday, errorMsg) + { + ErrorCode = ErrorCode + }; + context.AddFailure(validationFailure); + } + + return isValid; + } + + private static bool HasRequireField(ValidationContext context, Dictionary srcBirthdayFields, + Dictionary destBirthdayFields) + { + var isValid = true; + foreach (var srcField in srcBirthdayFields) + { + var srcKey = srcField.Key; + if (destBirthdayFields.ContainsKey(srcKey) == false) + { + var propertyName = $"birthday.{srcKey}"; + var validationFailure = new ValidationFailure(propertyName, $"'{propertyName}' must not be empty.") + { + ErrorCode = ErrorCode + }; + context.AddFailure(validationFailure); + isValid = false; + } + } + + return isValid; + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/NameFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/NameFieldValidator.cs index bda13d1e..b99a212e 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/NameFieldValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/NameFieldValidator.cs @@ -28,7 +28,7 @@ protected override bool PreValidate(ValidationContext context, Validatio var value = propertyInfo.GetValue(instance); if (value == null) { - return isValid; + continue; } var propertyName = $"name.{propertyInfo.Name}"; diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs index 6f37f469..07e261fb 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs @@ -6,10 +6,15 @@ namespace Lab.DictionaryFluentValidation.Validators; public class ProfileValidator : AbstractValidator> { - private static readonly Lazy s_emailFieldValidatorLazy + private static readonly Lazy s_emailFieldValidatorLazy = new(() => new EmailFieldValidator()); - private static readonly Lazy s_nameFieldValidatorLazy + + private static readonly Lazy s_nameFieldValidatorLazy = new(() => new NameFieldValidator()); + + private static readonly Lazy s_birthdayFieldValidatorLazy + = new(() => new BirthdayFieldValidator()); + private static bool IsNotSupportFields(ValidationContext> context) { var instances = context.InstanceToValidate; @@ -108,6 +113,13 @@ protected override bool PreValidate(ValidationContext ; break; } + case ProfileFieldNames.Birthday: + { + RuleFor(p => p[fieldName]) + .SetValidator(p => s_birthdayFieldValidatorLazy.Value) + ; + break; + } } } diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/RequireFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/RequireFieldValidator.cs deleted file mode 100644 index 58013712..00000000 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/RequireFieldValidator.cs +++ /dev/null @@ -1,11 +0,0 @@ -using FluentValidation; - -namespace Lab.DictionaryFluentValidation.Validators; - -public class RequireFieldValidator : AbstractValidator -{ - public RequireFieldValidator() - { - this.RuleFor(p => p).NotEmpty().NotNull(); - } -} \ No newline at end of file From 9187478a9ecf3a05676c00603c239b77115afb48 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 10 Apr 2022 13:15:47 +0800 Subject: [PATCH 200/424] feat GenderFieldValidator --- .../ProfileValidatorTests.cs | 16 ++++++++ .../Fields/GenderFieldNames.cs | 26 ------------ .../Fields/GenderFieldValues.cs | 26 ++++++++++++ .../Fields/ProfileFieldNames.cs | 7 ---- .../Validators/GenderFieldValidator.cs | 41 +++++++++++++++++++ .../Validators/ProfileValidator.cs | 10 +++++ 6 files changed, 93 insertions(+), 33 deletions(-) delete mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldNames.cs create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldValues.cs create mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/GenderFieldValidator.cs diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs index f3a0831a..cb00e25e 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs @@ -134,4 +134,20 @@ public void 郵件格式錯誤() Assert.AreEqual("EmailValidator", actualError.ErrorCode); Assert.AreEqual("'contactEmail' is not a valid email address.", actualError.ErrorMessage); } + [TestMethod] + public void 性別格式錯誤() + { + var data = new Dictionary + { + { "gender", "公的" }, + }; + var profileValidator = new ProfileValidator(); + var validationResult = profileValidator.Validate(data); + Assert.AreEqual(false, validationResult.IsValid); + var actualError = validationResult.Errors.First(); + Assert.AreEqual("gender", actualError.PropertyName); + Assert.AreEqual("GenderFieldValidator", actualError.ErrorCode); + Assert.AreEqual("'公的' is invalid value.", actualError.ErrorMessage); + } + } \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldNames.cs deleted file mode 100644 index 41139a4c..00000000 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldNames.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Lab.DictionaryFluentValidation.Assistants; - -namespace Lab.DictionaryFluentValidation.Fields; - -public class GenderFieldNames -{ - public static string Male = "male"; - public static string Female = "female"; - public static string NotAvailable = "notAvailable"; - - private static readonly Lazy> s_fieldNameLazy = - new(ProfileAssistants.GetFieldNames); - - private static readonly Lazy> s_fieldLazy = - new(ProfileAssistants.GetFields); - - public static IEnumerable GetFieldNames() - { - return s_fieldNameLazy.Value; - } - - public static Dictionary GetFields() - { - return s_fieldLazy.Value; - } -} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldValues.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldValues.cs new file mode 100644 index 00000000..75be1ca6 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldValues.cs @@ -0,0 +1,26 @@ +using Lab.DictionaryFluentValidation.Assistants; + +namespace Lab.DictionaryFluentValidation.Fields; + +public class GenderFieldValues +{ + public const string Male = "male"; + public const string Female = "female"; + public const string NotAvailable = "notAvailable"; + + private static readonly Lazy> s_fieldNameLazy = + new(ProfileAssistants.GetFieldNames); + + private static readonly Lazy> s_fieldLazy = + new(ProfileAssistants.GetFields); + + public static IEnumerable GetFieldValues() + { + return s_fieldNameLazy.Value; + } + + public static Dictionary GetValues() + { + return s_fieldLazy.Value; + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs index 21bcb040..581581d2 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs @@ -7,15 +7,8 @@ public class ProfileFieldNames public const string Name = "name"; public const string Gender = "gender"; public const string Birthday = "birthday"; - public const string EComJoinDateTime = "eComJoinDateTime"; - public const string MigrateJoinDateTime = "migrateJoinDateTime"; public const string ContactEmail = "contactEmail"; - public const string Local = "locale"; - public const string BrandMemberId = "brandMemberId"; - public const string BarcodeValue = "barcodeValue"; public const string Address = "address"; - public const string Personal = "personal"; - public const string Custom = "custom"; private static readonly Lazy> s_fieldNames = new(ProfileAssistants.GetFieldNames); diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/GenderFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/GenderFieldValidator.cs new file mode 100644 index 00000000..a43dc732 --- /dev/null +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/GenderFieldValidator.cs @@ -0,0 +1,41 @@ +using FluentValidation; +using FluentValidation.Results; +using Lab.DictionaryFluentValidation.Fields; + +namespace Lab.DictionaryFluentValidation.Validators; + +public class GenderFieldValidator : AbstractValidator +{ + private const string ErrorCode = nameof(GenderFieldValidator); + + /// + /// return true 繼續往下驗證 + /// https://docs.fluentvalidation.net/en/latest/advanced.html?highlight=PreValidate#prevalidate + /// + /// + /// + /// + protected override bool PreValidate(ValidationContext context, ValidationResult result) + { + var isValid = true; + var instance = context.InstanceToValidate; + if (instance == null) + { + return isValid; + } + + var srcValues = GenderFieldValues.GetValues(); + var destValue = instance.ToString(); + if (srcValues.ContainsKey(destValue) == false) + { + var validationFailure = new ValidationFailure("gender", + $"'{destValue}' is invalid value.") + { + ErrorCode = ErrorCode + }; + context.AddFailure(validationFailure); + } + + return isValid; + } +} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs index 07e261fb..131eaf2b 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs @@ -15,6 +15,9 @@ private static readonly Lazy s_nameFieldValidatorLazy private static readonly Lazy s_birthdayFieldValidatorLazy = new(() => new BirthdayFieldValidator()); + private static readonly Lazy s_genderFieldValidatorLazy + = new(() => new GenderFieldValidator()); + private static bool IsNotSupportFields(ValidationContext> context) { var instances = context.InstanceToValidate; @@ -120,6 +123,13 @@ protected override bool PreValidate(ValidationContext ; break; } + case ProfileFieldNames.Gender: + { + RuleFor(p => p[fieldName]) + .SetValidator(p => s_genderFieldValidatorLazy.Value) + ; + break; + } } } From 60093acdc9c19ad6a30bac864aada29a4b69eff6 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 10 Apr 2022 13:41:21 +0800 Subject: [PATCH 201/424] refactor --- .../ProfileValidatorTests.cs | 2 +- .../Fields/AddressFieldNames.cs | 30 ------------------- .../Fields/ProfileFieldNames.cs | 1 - .../Validators/ProfileValidator.cs | 25 +++++++++------- 4 files changed, 16 insertions(+), 42 deletions(-) delete mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/AddressFieldNames.cs diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs index cb00e25e..69d96626 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs @@ -86,7 +86,7 @@ public void 二月三十是非法日期() var actualError = validationResult.Errors.First(); Assert.AreEqual("birthday", actualError.PropertyName); Assert.AreEqual("BirthdayFieldValidator", actualError.ErrorCode); - Assert.AreEqual("year:2000,month:2,day:31 is invalid date format", actualError.ErrorMessage); + Assert.AreEqual("year:2000,month:2,day:30 is invalid date format", actualError.ErrorMessage); } [TestMethod] diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/AddressFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/AddressFieldNames.cs deleted file mode 100644 index f7b314c2..00000000 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/AddressFieldNames.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Lab.DictionaryFluentValidation.Assistants; - -namespace Lab.DictionaryFluentValidation.Fields; - -public class AddressFieldNames -{ - public static readonly string Address1 = "address1"; - public static readonly string Address2 = "address2"; - public static readonly string District = "district"; - public static readonly string CityTown = "cityTown"; - public static readonly string Province = "province"; - public static readonly string PostalCode = "postalCode"; - public static readonly string Country = "country"; - - private static readonly Lazy> s_fieldNameLazy = - new(ProfileAssistants.GetFieldNames); - - private static readonly Lazy> s_fieldLazy = - new(ProfileAssistants.GetFields); - - public static IEnumerable GetFieldNames() - { - return s_fieldNameLazy.Value; - } - - public static Dictionary GetFields() - { - return s_fieldLazy.Value; - } -} \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs index 581581d2..13878179 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs @@ -8,7 +8,6 @@ public class ProfileFieldNames public const string Gender = "gender"; public const string Birthday = "birthday"; public const string ContactEmail = "contactEmail"; - public const string Address = "address"; private static readonly Lazy> s_fieldNames = new(ProfileAssistants.GetFieldNames); diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs index 131eaf2b..263bd1a9 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs @@ -1,3 +1,4 @@ +using System.Linq.Expressions; using FluentValidation; using FluentValidation.Results; using Lab.DictionaryFluentValidation.Fields; @@ -90,6 +91,13 @@ protected override bool PreValidate(ValidationContext } var instances = context.InstanceToValidate; + this.SetValidateRule(instances); + + return true; + } + + private void SetValidateRule(Dictionary instances) + { foreach (var item in instances) { var fieldName = item.Key; @@ -98,41 +106,38 @@ protected override bool PreValidate(ValidationContext { continue; } - + switch (fieldName) { case ProfileFieldNames.ContactEmail: { - RuleFor(p => p[fieldName]) - .SetValidator(p => s_emailFieldValidatorLazy.Value) - ; - + this.RuleFor(p => p[fieldName]) + .SetValidator(p => s_emailFieldValidatorLazy.Value); + break; } case ProfileFieldNames.Name: { - RuleFor(p => p[fieldName]) + this.RuleFor(p => p[fieldName]) .SetValidator(p => s_nameFieldValidatorLazy.Value) ; break; } case ProfileFieldNames.Birthday: { - RuleFor(p => p[fieldName]) + this.RuleFor(p => p[fieldName]) .SetValidator(p => s_birthdayFieldValidatorLazy.Value) ; break; } case ProfileFieldNames.Gender: { - RuleFor(p => p[fieldName]) + this.RuleFor(p => p[fieldName]) .SetValidator(p => s_genderFieldValidatorLazy.Value) ; break; } } } - - return true; } } \ No newline at end of file From 1716a51d17f040cdbfe737a8199df421130678e9 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 10 Apr 2022 13:58:28 +0800 Subject: [PATCH 202/424] remove file --- .../DictionaryStringObjectJsonConverter.cs | 116 ------------------ 1 file changed, 116 deletions(-) delete mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/DictionaryStringObjectJsonConverter.cs diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/DictionaryStringObjectJsonConverter.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/DictionaryStringObjectJsonConverter.cs deleted file mode 100644 index 85f37e52..00000000 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/DictionaryStringObjectJsonConverter.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Lab.DictionaryFluentValidation.UnitTest; - -public class DictionaryStringObjectJsonConverter : JsonConverter> -{ - public override Dictionary Read(ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) - { - if (reader.TokenType != JsonTokenType.StartObject) - { - throw new JsonException($"JsonTokenType was of type {reader.TokenType}, only objects are supported"); - } - - var results = new Dictionary(); - while (reader.Read()) - { - if (reader.TokenType == JsonTokenType.EndObject) - { - return results; - } - - if (reader.TokenType != JsonTokenType.PropertyName) - { - throw new JsonException("JsonTokenType was not PropertyName"); - } - - var propertyName = reader.GetString(); - - if (string.IsNullOrWhiteSpace(propertyName)) - { - throw new JsonException("Failed to get property name"); - } - - reader.Read(); - - results.Add(propertyName, this.ReadValue(ref reader, options)); - } - - return results; - } - - public override void Write(Utf8JsonWriter writer, - Dictionary value, - JsonSerializerOptions options) - { - writer.WriteStartObject(); - - foreach (var key in value.Keys) - { - WriteValue(writer, key, value[key], options); - } - - writer.WriteEndObject(); - } - - private object ReadValue(ref Utf8JsonReader reader, JsonSerializerOptions options) - { - switch (reader.TokenType) - { - case JsonTokenType.String: - if (reader.TryGetDateTimeOffset(out var dateOffset)) - { - return dateOffset; - } - - if (reader.TryGetGuid(out var guid)) - { - return guid; - } - - return reader.GetString(); - case JsonTokenType.False: - case JsonTokenType.True: - return reader.GetBoolean(); - case JsonTokenType.Null: - return null; - case JsonTokenType.Number: - if (reader.TryGetInt64(out var result)) - { - return result; - } - - return reader.GetDecimal(); - case JsonTokenType.StartObject: - return this.Read(ref reader, null, options); - case JsonTokenType.StartArray: - var list = new List(); - while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) - { - list.Add(this.ReadValue(ref reader, options)); - } - - return list; - default: - throw new JsonException($"'{reader.TokenType}' is not supported"); - } - } - - private static void WriteValue(Utf8JsonWriter writer, - string key, - object value, - JsonSerializerOptions options) - { - if (key != null) - { - writer.WritePropertyName(key); - } - - JsonSerializer.Serialize(writer, value, options); - } -} \ No newline at end of file From dfc5d6e7b93b9c6a305b1e1c498db1786203408b Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 10 Apr 2022 14:11:05 +0800 Subject: [PATCH 203/424] remove file --- .../Fields/PersonalFieldNames.cs | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/PersonalFieldNames.cs diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/PersonalFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/PersonalFieldNames.cs deleted file mode 100644 index 69cd8501..00000000 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/PersonalFieldNames.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Lab.DictionaryFluentValidation.Assistants; - -namespace Lab.DictionaryFluentValidation.Fields; - -public class PersonalFieldNames -{ - /// - /// 身分證字號 - /// - public static string IdentityCardId = "identityCardId"; - - /// - /// 教育程度 - /// - public static string Education = "education"; - - /// - /// 職業 - /// - public static string Profession = "profession"; - - /// - /// 婚姻狀態 - /// - public static string MaritalStatus = "maritalStatus"; - - /// - /// 家屬 - /// - public static string Dependents = "dependents"; - - /// - /// 年收入 - /// - public static string AnnualIncome = "annualIncome"; - - private static readonly Lazy> s_fieldNameLazy = - new(ProfileAssistants.GetFieldNames); - - private static readonly Lazy> s_fieldLazy = - new(ProfileAssistants.GetFields); - - public static IEnumerable GetFieldNames() - { - return s_fieldNameLazy.Value; - } - - public static Dictionary GetFields() - { - return s_fieldLazy.Value; - } -} \ No newline at end of file From 875783ffc6ba8f3817232be63f88b6e69c19e4ca Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 10 Apr 2022 23:10:18 +0800 Subject: [PATCH 204/424] refactor --- .../ProfileValidatorTests.cs | 20 ++++++++++++++++++- .../Assistants/ProfileAssistants.cs | 19 +----------------- .../Fields/BirthdayFieldNames.cs | 16 +++++---------- .../Fields/GenderFieldValues.cs | 16 +++++---------- .../Fields/NameFieldNames.cs | 18 ++++++----------- .../Fields/ProfileFieldNames.cs | 16 +++++---------- .../Validators/BirthdayFieldValidator.cs | 2 +- .../Validators/GenderFieldValidator.cs | 2 +- .../Validators/ProfileValidator.cs | 6 +++--- 9 files changed, 46 insertions(+), 69 deletions(-) diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs index 69d96626..68628dde 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Lab.DictionaryFluentValidation.Fields; using Lab.DictionaryFluentValidation.Validators; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -8,6 +9,23 @@ namespace Lab.DictionaryFluentValidation.UnitTest; [TestClass] public class ProfileValidatorTests { + [TestMethod] + public void 通過驗證() + { + BirthdayFieldNames.GetFieldNames(); + BirthdayFieldNames.GetFieldNames(); + var data = new Dictionary + { + { "name", new { firstName = "yao", lastName = "yu", fullName = "yao-chang.yu" } }, + { "birthday", new { year = 2000, month = 2, day = 28 } }, + { "contactEmail", "yao@aa.bb" }, + }; + + var profileValidator = new ProfileValidator(); + var validationResult = profileValidator.Validate(data); + Assert.AreEqual(true, validationResult.IsValid); + } + [TestMethod] public void Key區分大小寫() { @@ -134,6 +152,7 @@ public void 郵件格式錯誤() Assert.AreEqual("EmailValidator", actualError.ErrorCode); Assert.AreEqual("'contactEmail' is not a valid email address.", actualError.ErrorMessage); } + [TestMethod] public void 性別格式錯誤() { @@ -149,5 +168,4 @@ public void 性別格式錯誤() Assert.AreEqual("GenderFieldValidator", actualError.ErrorCode); Assert.AreEqual("'公的' is invalid value.", actualError.ErrorMessage); } - } \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Assistants/ProfileAssistants.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Assistants/ProfileAssistants.cs index e645a267..f2ce8fb0 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Assistants/ProfileAssistants.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Assistants/ProfileAssistants.cs @@ -4,24 +4,7 @@ namespace Lab.DictionaryFluentValidation.Assistants; public class ProfileAssistants { - public static IEnumerable GetFieldNames() - { - var type = typeof(T); - - var bindingFlags = BindingFlags.Public - | BindingFlags.Static - ; - var results = new List(); - var fieldInfosInfos = type.GetFields(bindingFlags); - foreach (var fieldInfo in fieldInfosInfos) - { - results.Add(fieldInfo.GetValue(null).ToString()); - } - - return results; - } - - public static Dictionary GetFields() + public static Dictionary GetFieldNames() { var type = typeof(T); diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs index 4f0471af..8b44733d 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs @@ -8,19 +8,13 @@ public class BirthdayFieldNames public const string Month = "month"; public const string Day = "day"; - private static readonly Lazy> s_fieldNameLazy = - new(ProfileAssistants.GetFieldNames); + private static readonly Lazy> s_fieldNamesLazy = + new(() => ProfileAssistants.GetFieldNames()); - private static readonly Lazy> s_fieldLazy = - new(ProfileAssistants.GetFields); + private static Dictionary FieldNames => s_fieldNamesLazy.Value; - public static IEnumerable GetFieldNames() + public static Dictionary GetFieldNames() { - return s_fieldNameLazy.Value; - } - - public static Dictionary GetFields() - { - return s_fieldLazy.Value; + return FieldNames; } } \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldValues.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldValues.cs index 75be1ca6..fdeaea12 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldValues.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldValues.cs @@ -8,19 +8,13 @@ public class GenderFieldValues public const string Female = "female"; public const string NotAvailable = "notAvailable"; - private static readonly Lazy> s_fieldNameLazy = - new(ProfileAssistants.GetFieldNames); + private static readonly Lazy> s_fieldValuesLazy = + new(() => ProfileAssistants.GetFieldNames()); - private static readonly Lazy> s_fieldLazy = - new(ProfileAssistants.GetFields); + private static Dictionary FieldValues => s_fieldValuesLazy.Value; - public static IEnumerable GetFieldValues() + public static Dictionary GetFieldValues() { - return s_fieldNameLazy.Value; - } - - public static Dictionary GetValues() - { - return s_fieldLazy.Value; + return FieldValues; } } \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs index cd6c8dd1..e42c485f 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs @@ -7,20 +7,14 @@ public class NameFieldNames public const string FirstName = "firstName"; public const string LastName = "lastName"; public const string FullName = "fullName"; + + private static readonly Lazy> s_fieldNamesLazy = + new(() => ProfileAssistants.GetFieldNames()); - private static readonly Lazy> s_fieldNameLazy = - new(ProfileAssistants.GetFieldNames); + private static Dictionary FieldNames => s_fieldNamesLazy.Value; - private static readonly Lazy> s_fieldLazy = - new(ProfileAssistants.GetFields); - - public static IEnumerable GetFieldNames() - { - return s_fieldNameLazy.Value; - } - - public static Dictionary GetFields() + public static Dictionary GetFieldNames() { - return s_fieldLazy.Value; + return FieldNames; } } \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs index 13878179..9d49c233 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs @@ -9,19 +9,13 @@ public class ProfileFieldNames public const string Birthday = "birthday"; public const string ContactEmail = "contactEmail"; - private static readonly Lazy> s_fieldNames = - new(ProfileAssistants.GetFieldNames); + private static readonly Lazy> s_fieldNamesLazy = + new(() => ProfileAssistants.GetFieldNames()); - private static Lazy> s_fields = - new(ProfileAssistants.GetFields); + private static Dictionary FieldNames => s_fieldNamesLazy.Value; - public static IEnumerable GetFieldNames() + public static Dictionary GetFieldNames() { - return s_fieldNames.Value; - } - - public static Dictionary GetFields() - { - return s_fields.Value; + return FieldNames; } } \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/BirthdayFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/BirthdayFieldValidator.cs index c544d5b6..64e53747 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/BirthdayFieldValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/BirthdayFieldValidator.cs @@ -37,7 +37,7 @@ protected override bool PreValidate(ValidationContext context, Validatio birthday.Add(propertyInfo.Name, Convert.ToInt32(value)); } - var srcBirthdayFields = BirthdayFieldNames.GetFields(); + var srcBirthdayFields = BirthdayFieldNames.GetFieldNames(); isValid = HasRequireField(context, srcBirthdayFields, birthday); if (isValid == false) { diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/GenderFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/GenderFieldValidator.cs index a43dc732..0cab502d 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/GenderFieldValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/GenderFieldValidator.cs @@ -24,7 +24,7 @@ protected override bool PreValidate(ValidationContext context, Validatio return isValid; } - var srcValues = GenderFieldValues.GetValues(); + var srcValues = GenderFieldValues.GetFieldValues(); var destValue = instance.ToString(); if (srcValues.ContainsKey(destValue) == false) { diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs index 263bd1a9..492de546 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs @@ -31,13 +31,13 @@ private static bool IsNotSupportFields(ValidationContext Date: Tue, 12 Apr 2022 00:10:20 +0800 Subject: [PATCH 205/424] refactor --- .../ProfileValidatorTests.cs | 32 ++++----- ...hdayFieldNames.cs => BirthdayTypeNames.cs} | 4 +- ...nderFieldValues.cs => GenderTypeValues.cs} | 4 +- .../{NameFieldNames.cs => NameTypeNames.cs} | 4 +- ...ofileFieldNames.cs => ProfileTypeNames.cs} | 4 +- ...dValidator.cs => BirthdayTypeValidator.cs} | 70 ++++++++++--------- ...ieldValidator.cs => EmailTypeValidator.cs} | 20 ++++-- ...eldValidator.cs => GenderTypeValidator.cs} | 16 +++-- ...FieldValidator.cs => NameTypeValidator.cs} | 17 +++-- ...leValidator.cs => ProfileTypeValidator.cs} | 49 ++++++------- 10 files changed, 121 insertions(+), 99 deletions(-) rename ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/{BirthdayFieldNames.cs => BirthdayTypeNames.cs} (82%) rename ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/{GenderFieldValues.cs => GenderTypeValues.cs} (83%) rename ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/{NameFieldNames.cs => NameTypeNames.cs} (84%) rename ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/{ProfileFieldNames.cs => ProfileTypeNames.cs} (84%) rename ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/{BirthdayFieldValidator.cs => BirthdayTypeValidator.cs} (66%) rename ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/{EmailFieldValidator.cs => EmailTypeValidator.cs} (55%) rename ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/{GenderFieldValidator.cs => GenderTypeValidator.cs} (65%) rename ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/{NameFieldValidator.cs => NameTypeValidator.cs} (76%) rename ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/{ProfileValidator.cs => ProfileTypeValidator.cs} (70%) diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs index 68628dde..51eb5067 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs @@ -12,8 +12,8 @@ public class ProfileValidatorTests [TestMethod] public void 通過驗證() { - BirthdayFieldNames.GetFieldNames(); - BirthdayFieldNames.GetFieldNames(); + BirthdayTypeNames.GetFieldNames(); + BirthdayTypeNames.GetFieldNames(); var data = new Dictionary { { "name", new { firstName = "yao", lastName = "yu", fullName = "yao-chang.yu" } }, @@ -21,7 +21,7 @@ public void 通過驗證() { "contactEmail", "yao@aa.bb" }, }; - var profileValidator = new ProfileValidator(); + var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(true, validationResult.IsValid); } @@ -34,7 +34,7 @@ public void Key區分大小寫() { "Name", null }, }; - var profileValidator = new ProfileValidator(); + var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); @@ -50,7 +50,7 @@ public void 必填欄位為空() { { "name", new { firstName = "yao", lastName = "", fullName = "" } }, }; - var profileValidator = new ProfileValidator(); + var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); // Assert.AreEqual(false, validationResult.IsValid); @@ -68,7 +68,7 @@ public void 使用不支援的Key() { "Hi", null }, }; - var profileValidator = new ProfileValidator(); + var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); @@ -85,7 +85,7 @@ public void 使用支援的Key() { "name", null }, }; - var profileValidator = new ProfileValidator(); + var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(true, validationResult.IsValid); } @@ -98,12 +98,12 @@ public void 二月三十是非法日期() { "birthday", new { year = 2000, month = 2, day = 30 } }, }; - var profileValidator = new ProfileValidator(); + var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); Assert.AreEqual("birthday", actualError.PropertyName); - Assert.AreEqual("BirthdayFieldValidator", actualError.ErrorCode); + Assert.AreEqual(nameof(BirthdayTypeValidator), actualError.ErrorCode); Assert.AreEqual("year:2000,month:2,day:30 is invalid date format", actualError.ErrorMessage); } @@ -115,12 +115,12 @@ public void 沒有年是非法日期() { "birthday", new { month = 2, day = 30 } }, }; - var profileValidator = new ProfileValidator(); + var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); Assert.AreEqual("birthday.year", actualError.PropertyName); - Assert.AreEqual("BirthdayFieldValidator", actualError.ErrorCode); + Assert.AreEqual(nameof(BirthdayTypeValidator), actualError.ErrorCode); Assert.AreEqual("'birthday.year' must not be empty.", actualError.ErrorMessage); } @@ -132,7 +132,7 @@ public void 日期內容為非法值() { "birthday", null }, }; - var profileValidator = new ProfileValidator(); + var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(true, validationResult.IsValid); } @@ -144,12 +144,12 @@ public void 郵件格式錯誤() { { "contactEmail", "yao" }, }; - var profileValidator = new ProfileValidator(); + var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); Assert.AreEqual("contactEmail", actualError.PropertyName); - Assert.AreEqual("EmailValidator", actualError.ErrorCode); + Assert.AreEqual(nameof(EmailTypeValidator), actualError.ErrorCode); Assert.AreEqual("'contactEmail' is not a valid email address.", actualError.ErrorMessage); } @@ -160,12 +160,12 @@ public void 性別格式錯誤() { { "gender", "公的" }, }; - var profileValidator = new ProfileValidator(); + var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); Assert.AreEqual("gender", actualError.PropertyName); - Assert.AreEqual("GenderFieldValidator", actualError.ErrorCode); + Assert.AreEqual(nameof(GenderTypeValidator), actualError.ErrorCode); Assert.AreEqual("'公的' is invalid value.", actualError.ErrorMessage); } } \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayTypeNames.cs similarity index 82% rename from ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs rename to ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayTypeNames.cs index 8b44733d..0a5a89ba 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayFieldNames.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/BirthdayTypeNames.cs @@ -2,14 +2,14 @@ namespace Lab.DictionaryFluentValidation.Fields; -public class BirthdayFieldNames +public class BirthdayTypeNames { public const string Year = "year"; public const string Month = "month"; public const string Day = "day"; private static readonly Lazy> s_fieldNamesLazy = - new(() => ProfileAssistants.GetFieldNames()); + new(() => ProfileAssistants.GetFieldNames()); private static Dictionary FieldNames => s_fieldNamesLazy.Value; diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldValues.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderTypeValues.cs similarity index 83% rename from ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldValues.cs rename to ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderTypeValues.cs index fdeaea12..670504e1 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderFieldValues.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/GenderTypeValues.cs @@ -2,14 +2,14 @@ namespace Lab.DictionaryFluentValidation.Fields; -public class GenderFieldValues +public class GenderTypeValues { public const string Male = "male"; public const string Female = "female"; public const string NotAvailable = "notAvailable"; private static readonly Lazy> s_fieldValuesLazy = - new(() => ProfileAssistants.GetFieldNames()); + new(() => ProfileAssistants.GetFieldNames()); private static Dictionary FieldValues => s_fieldValuesLazy.Value; diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameTypeNames.cs similarity index 84% rename from ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs rename to ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameTypeNames.cs index e42c485f..0034bbe6 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameFieldNames.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/NameTypeNames.cs @@ -2,14 +2,14 @@ namespace Lab.DictionaryFluentValidation.Fields; -public class NameFieldNames +public class NameTypeNames { public const string FirstName = "firstName"; public const string LastName = "lastName"; public const string FullName = "fullName"; private static readonly Lazy> s_fieldNamesLazy = - new(() => ProfileAssistants.GetFieldNames()); + new(() => ProfileAssistants.GetFieldNames()); private static Dictionary FieldNames => s_fieldNamesLazy.Value; diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileTypeNames.cs similarity index 84% rename from ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs rename to ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileTypeNames.cs index 9d49c233..e00d1b53 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileFieldNames.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Fields/ProfileTypeNames.cs @@ -2,7 +2,7 @@ namespace Lab.DictionaryFluentValidation.Fields; -public class ProfileFieldNames +public class ProfileTypeNames { public const string Name = "name"; public const string Gender = "gender"; @@ -10,7 +10,7 @@ public class ProfileFieldNames public const string ContactEmail = "contactEmail"; private static readonly Lazy> s_fieldNamesLazy = - new(() => ProfileAssistants.GetFieldNames()); + new(() => ProfileAssistants.GetFieldNames()); private static Dictionary FieldNames => s_fieldNamesLazy.Value; diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/BirthdayFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/BirthdayTypeValidator.cs similarity index 66% rename from ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/BirthdayFieldValidator.cs rename to ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/BirthdayTypeValidator.cs index 64e53747..e88c1704 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/BirthdayFieldValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/BirthdayTypeValidator.cs @@ -4,9 +4,37 @@ namespace Lab.DictionaryFluentValidation.Validators; -public class BirthdayFieldValidator : AbstractValidator +public class BirthdayTypeValidator : AbstractValidator { - private const string ErrorCode = nameof(BirthdayFieldValidator); + private const string ErrorCode = nameof(BirthdayTypeValidator); + private readonly string _propertyName; + + public BirthdayTypeValidator(string propertyName) + { + this._propertyName = propertyName; + } + + private bool HasRequireField(ValidationContext context, Dictionary srcBirthdayFields, + Dictionary destBirthdayFields) + { + var isValid = true; + foreach (var srcField in srcBirthdayFields) + { + var srcKey = srcField.Key; + if (destBirthdayFields.ContainsKey(srcKey) == false) + { + var propertyName = $"{_propertyName}.{srcKey}"; + var validationFailure = new ValidationFailure(propertyName, $"'{propertyName}' must not be empty.") + { + ErrorCode = ErrorCode + }; + context.AddFailure(validationFailure); + isValid = false; + } + } + + return isValid; + } /// /// return true 繼續往下驗證 @@ -37,27 +65,27 @@ protected override bool PreValidate(ValidationContext context, Validatio birthday.Add(propertyInfo.Name, Convert.ToInt32(value)); } - var srcBirthdayFields = BirthdayFieldNames.GetFieldNames(); + var srcBirthdayFields = BirthdayTypeNames.GetFieldNames(); isValid = HasRequireField(context, srcBirthdayFields, birthday); if (isValid == false) { return isValid; } - var year = birthday[BirthdayFieldNames.Year]; - var month = birthday[BirthdayFieldNames.Month]; - var day = birthday[BirthdayFieldNames.Day]; + var year = birthday[BirthdayTypeNames.Year]; + var month = birthday[BirthdayTypeNames.Month]; + var day = birthday[BirthdayTypeNames.Day]; try { var birthday2 = new DateTime(year, month, day); } catch (Exception e) { - var errorMsg = $"{BirthdayFieldNames.Year}:{year}," + - $"{BirthdayFieldNames.Month}:{month}," + - $"{BirthdayFieldNames.Day}:{day} is invalid date format"; + var errorMsg = $"{BirthdayTypeNames.Year}:{year}," + + $"{BirthdayTypeNames.Month}:{month}," + + $"{BirthdayTypeNames.Day}:{day} is invalid date format"; - var validationFailure = new ValidationFailure(ProfileFieldNames.Birthday, errorMsg) + var validationFailure = new ValidationFailure(this._propertyName, errorMsg) { ErrorCode = ErrorCode }; @@ -66,26 +94,4 @@ protected override bool PreValidate(ValidationContext context, Validatio return isValid; } - - private static bool HasRequireField(ValidationContext context, Dictionary srcBirthdayFields, - Dictionary destBirthdayFields) - { - var isValid = true; - foreach (var srcField in srcBirthdayFields) - { - var srcKey = srcField.Key; - if (destBirthdayFields.ContainsKey(srcKey) == false) - { - var propertyName = $"birthday.{srcKey}"; - var validationFailure = new ValidationFailure(propertyName, $"'{propertyName}' must not be empty.") - { - ErrorCode = ErrorCode - }; - context.AddFailure(validationFailure); - isValid = false; - } - } - - return isValid; - } } \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailTypeValidator.cs similarity index 55% rename from ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs rename to ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailTypeValidator.cs index 33c02738..9d3bcae3 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailFieldValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/EmailTypeValidator.cs @@ -1,14 +1,20 @@ using FluentValidation; using FluentValidation.Results; -using Lab.DictionaryFluentValidation.Fields; namespace Lab.DictionaryFluentValidation.Validators; -public class EmailFieldValidator : AbstractValidator +public class EmailTypeValidator : AbstractValidator { + private readonly string _propertyName; + + public EmailTypeValidator(string propertyName) + { + this._propertyName = propertyName; + } + /// - /// return true 繼續往下驗證 - /// https://docs.fluentvalidation.net/en/latest/advanced.html?highlight=PreValidate#prevalidate + /// return true 繼續往下驗證 + /// https://docs.fluentvalidation.net/en/latest/advanced.html?highlight=PreValidate#prevalidate /// /// /// @@ -23,9 +29,9 @@ protected override bool PreValidate(ValidationContext context, Validatio } this.RuleFor(p => p.ToString()) - .NotEmpty() - .WithName(ProfileFieldNames.ContactEmail) - .EmailAddress(); + .EmailAddress() + .WithName(this._propertyName) + .WithErrorCode(nameof(EmailTypeValidator)) ; return isValid; } diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/GenderFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/GenderTypeValidator.cs similarity index 65% rename from ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/GenderFieldValidator.cs rename to ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/GenderTypeValidator.cs index 0cab502d..998dbfab 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/GenderFieldValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/GenderTypeValidator.cs @@ -4,9 +4,15 @@ namespace Lab.DictionaryFluentValidation.Validators; -public class GenderFieldValidator : AbstractValidator +public class GenderTypeValidator : AbstractValidator { - private const string ErrorCode = nameof(GenderFieldValidator); + private const string ErrorCode = nameof(GenderTypeValidator); + private readonly string _propertyName; + + public GenderTypeValidator(string propertyName) + { + this._propertyName = propertyName; + } /// /// return true 繼續往下驗證 @@ -24,12 +30,12 @@ protected override bool PreValidate(ValidationContext context, Validatio return isValid; } - var srcValues = GenderFieldValues.GetFieldValues(); + var srcValues = GenderTypeValues.GetFieldValues(); var destValue = instance.ToString(); if (srcValues.ContainsKey(destValue) == false) { - var validationFailure = new ValidationFailure("gender", - $"'{destValue}' is invalid value.") + var validationFailure = new ValidationFailure(this._propertyName, + $"'{destValue}' is invalid value.") { ErrorCode = ErrorCode }; diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/NameFieldValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/NameTypeValidator.cs similarity index 76% rename from ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/NameFieldValidator.cs rename to ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/NameTypeValidator.cs index b99a212e..bd3914d3 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/NameFieldValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/NameTypeValidator.cs @@ -4,8 +4,15 @@ namespace Lab.DictionaryFluentValidation.Validators; -public class NameFieldValidator : AbstractValidator +public class NameTypeValidator : AbstractValidator { + private readonly string _propertyName; + + public NameTypeValidator(string propertyName) + { + this._propertyName = propertyName; + } + /// /// return true 繼續往下驗證 /// https://docs.fluentvalidation.net/en/latest/advanced.html?highlight=PreValidate#prevalidate @@ -31,14 +38,14 @@ protected override bool PreValidate(ValidationContext context, Validatio continue; } - var propertyName = $"name.{propertyInfo.Name}"; + var propertyName = $"{this._propertyName}.{propertyInfo.Name}"; switch (propertyInfo.Name) { - case NameFieldNames.FirstName: + case NameTypeNames.FirstName: break; - case NameFieldNames.LastName: + case NameTypeNames.LastName: break; - case NameFieldNames.FullName: + case NameTypeNames.FullName: this.RuleFor(p => value) .NotEmpty() .WithName(propertyName) diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileTypeValidator.cs similarity index 70% rename from ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs rename to ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileTypeValidator.cs index 492de546..6c857fd2 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileTypeValidator.cs @@ -1,23 +1,18 @@ -using System.Linq.Expressions; using FluentValidation; using FluentValidation.Results; using Lab.DictionaryFluentValidation.Fields; namespace Lab.DictionaryFluentValidation.Validators; -public class ProfileValidator : AbstractValidator> +public class ProfileTypeValidator : AbstractValidator> { - private static readonly Lazy s_emailFieldValidatorLazy - = new(() => new EmailFieldValidator()); + private static EmailTypeValidator EmailTypeValidator => new(ProfileTypeNames.ContactEmail); - private static readonly Lazy s_nameFieldValidatorLazy - = new(() => new NameFieldValidator()); + private static NameTypeValidator NameTypeValidator => new(ProfileTypeNames.Name); - private static readonly Lazy s_birthdayFieldValidatorLazy - = new(() => new BirthdayFieldValidator()); + private static BirthdayTypeValidator BirthdayTypeValidator => new(ProfileTypeNames.Birthday); - private static readonly Lazy s_genderFieldValidatorLazy - = new(() => new GenderFieldValidator()); + private static GenderTypeValidator GenderTypeValidator => new(ProfileTypeNames.Gender); private static bool IsNotSupportFields(ValidationContext> context) { @@ -30,14 +25,14 @@ private static bool IsNotSupportFields(ValidationContext instances) { continue; } - + switch (fieldName) { - case ProfileFieldNames.ContactEmail: + case ProfileTypeNames.ContactEmail: { - this.RuleFor(p => p[fieldName]) - .SetValidator(p => s_emailFieldValidatorLazy.Value); - + var PropertyName = fieldName; + this.RuleFor(p => p[fieldName]) + .SetValidator(p => EmailTypeValidator) + ; + break; } - case ProfileFieldNames.Name: + case ProfileTypeNames.Name: { this.RuleFor(p => p[fieldName]) - .SetValidator(p => s_nameFieldValidatorLazy.Value) + .SetValidator(p => NameTypeValidator) ; break; } - case ProfileFieldNames.Birthday: + case ProfileTypeNames.Birthday: { this.RuleFor(p => p[fieldName]) - .SetValidator(p => s_birthdayFieldValidatorLazy.Value) + .SetValidator(p => BirthdayTypeValidator) ; break; } - case ProfileFieldNames.Gender: + case ProfileTypeNames.Gender: { this.RuleFor(p => p[fieldName]) - .SetValidator(p => s_genderFieldValidatorLazy.Value) + .SetValidator(p => GenderTypeValidator) ; break; } From a26eb70536d0055174ba37cbdd8f14e3eafbf8d0 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 12 Apr 2022 02:22:36 +0800 Subject: [PATCH 206/424] refactor --- .../ProfileValidatorTests.cs | 127 +++++++++++------- .../Validators/ProfileTypeValidator.cs | 29 ++-- 2 files changed, 100 insertions(+), 56 deletions(-) diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs index 51eb5067..af85a89c 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation.UnitTest/ProfileValidatorTests.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using Lab.DictionaryFluentValidation.Fields; using Lab.DictionaryFluentValidation.Validators; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -10,20 +9,39 @@ namespace Lab.DictionaryFluentValidation.UnitTest; public class ProfileValidatorTests { [TestMethod] - public void 通過驗證() + public void aaaa() { - BirthdayTypeNames.GetFieldNames(); - BirthdayTypeNames.GetFieldNames(); + var profileValidator = new ProfileTypeValidator(); var data = new Dictionary { { "name", new { firstName = "yao", lastName = "yu", fullName = "yao-chang.yu" } }, { "birthday", new { year = 2000, month = 2, day = 28 } }, { "contactEmail", "yao@aa.bb" }, }; - - var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); - Assert.AreEqual(true, validationResult.IsValid); + + data = new Dictionary + { + { "gender", "公的" }, + }; + validationResult = profileValidator.Validate(data); + + data = new Dictionary + { + { "Name", null }, + }; + + validationResult = profileValidator.Validate(data); + data = new Dictionary + { + { "name", new { firstName = "yao", lastName = "", fullName = "" } }, + }; + validationResult = profileValidator.Validate(data); + data = new Dictionary + { + { "Hi", null }, + }; + validationResult = profileValidator.Validate(data); } [TestMethod] @@ -40,96 +58,96 @@ public void Key區分大小寫() var actualError = validationResult.Errors.First(); Assert.AreEqual("Name", actualError.PropertyName); Assert.AreEqual("NotSupportValidator", actualError.ErrorCode); - Assert.AreEqual("not support column 'Name'", actualError.ErrorMessage); + Assert.AreEqual("'Name' column not support", actualError.ErrorMessage); } [TestMethod] - public void 必填欄位為空() + public void 二月三十是非法日期() { var data = new Dictionary { - { "name", new { firstName = "yao", lastName = "", fullName = "" } }, + { "birthday", new { year = 2000, month = 2, day = 30 } }, }; + var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); - - // Assert.AreEqual(false, validationResult.IsValid); + Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); - Assert.AreEqual("name.fullName", actualError.PropertyName); - Assert.AreEqual("NotEmptyValidator", actualError.ErrorCode); - Assert.AreEqual("'name.fullName' must not be empty.", actualError.ErrorMessage); + Assert.AreEqual("birthday", actualError.PropertyName); + Assert.AreEqual(nameof(BirthdayTypeValidator), actualError.ErrorCode); + Assert.AreEqual("year:2000,month:2,day:30 is invalid date format", actualError.ErrorMessage); } [TestMethod] - public void 使用不支援的Key() + public void 日期內容為非法值() { var data = new Dictionary { - { "Hi", null }, + { "birthday", null }, }; var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); - Assert.AreEqual(false, validationResult.IsValid); - var actualError = validationResult.Errors.First(); - Assert.AreEqual("Hi", actualError.PropertyName); - Assert.AreEqual("NotSupportValidator", actualError.ErrorCode); - Assert.AreEqual("not support column 'Hi'", actualError.ErrorMessage); + Assert.AreEqual(true, validationResult.IsValid); } [TestMethod] - public void 使用支援的Key() + public void 必填欄位為空() { var data = new Dictionary { - { "name", null }, + { "name", new { firstName = "yao", lastName = "", fullName = "" } }, }; - var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); - Assert.AreEqual(true, validationResult.IsValid); + + // Assert.AreEqual(false, validationResult.IsValid); + var actualError = validationResult.Errors.First(); + Assert.AreEqual("name.fullName", actualError.PropertyName); + Assert.AreEqual("NotEmptyValidator", actualError.ErrorCode); + Assert.AreEqual("'name.fullName' must not be empty.", actualError.ErrorMessage); } [TestMethod] - public void 二月三十是非法日期() + public void 沒有年是非法日期() { var data = new Dictionary { - { "birthday", new { year = 2000, month = 2, day = 30 } }, + { "birthday", new { month = 2, day = 30 } }, }; var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); - Assert.AreEqual("birthday", actualError.PropertyName); + Assert.AreEqual("birthday.year", actualError.PropertyName); Assert.AreEqual(nameof(BirthdayTypeValidator), actualError.ErrorCode); - Assert.AreEqual("year:2000,month:2,day:30 is invalid date format", actualError.ErrorMessage); + Assert.AreEqual("'birthday.year' must not be empty.", actualError.ErrorMessage); } [TestMethod] - public void 沒有年是非法日期() + public void 使用不支援的Key() { var data = new Dictionary { - { "birthday", new { month = 2, day = 30 } }, + { "Hi", null }, }; var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); - Assert.AreEqual("birthday.year", actualError.PropertyName); - Assert.AreEqual(nameof(BirthdayTypeValidator), actualError.ErrorCode); - Assert.AreEqual("'birthday.year' must not be empty.", actualError.ErrorMessage); + Assert.AreEqual("Hi", actualError.PropertyName); + Assert.AreEqual("NotSupportValidator", actualError.ErrorCode); + Assert.AreEqual("'Hi' column not support", actualError.ErrorMessage); } [TestMethod] - public void 日期內容為非法值() + public void 使用支援的Key() { var data = new Dictionary { - { "birthday", null }, + { "name", null }, }; var profileValidator = new ProfileTypeValidator(); @@ -138,34 +156,49 @@ public void 日期內容為非法值() } [TestMethod] - public void 郵件格式錯誤() + public void 性別格式錯誤() { var data = new Dictionary { - { "contactEmail", "yao" }, + { "gender", "公的" }, }; var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); - Assert.AreEqual("contactEmail", actualError.PropertyName); - Assert.AreEqual(nameof(EmailTypeValidator), actualError.ErrorCode); - Assert.AreEqual("'contactEmail' is not a valid email address.", actualError.ErrorMessage); + Assert.AreEqual("gender", actualError.PropertyName); + Assert.AreEqual(nameof(GenderTypeValidator), actualError.ErrorCode); + Assert.AreEqual("'公的' is invalid value.", actualError.ErrorMessage); } [TestMethod] - public void 性別格式錯誤() + public void 通過驗證() { var data = new Dictionary { - { "gender", "公的" }, + { "name", new { firstName = "yao", lastName = "yu", fullName = "yao-chang.yu" } }, + { "birthday", new { year = 2000, month = 2, day = 28 } }, + { "contactEmail", "yao@aa.bb" }, + }; + + var profileValidator = new ProfileTypeValidator(); + var validationResult = profileValidator.Validate(data); + Assert.AreEqual(true, validationResult.IsValid); + } + + [TestMethod] + public void 郵件格式錯誤() + { + var data = new Dictionary + { + { "contactEmail", "yao" }, }; var profileValidator = new ProfileTypeValidator(); var validationResult = profileValidator.Validate(data); Assert.AreEqual(false, validationResult.IsValid); var actualError = validationResult.Errors.First(); - Assert.AreEqual("gender", actualError.PropertyName); - Assert.AreEqual(nameof(GenderTypeValidator), actualError.ErrorCode); - Assert.AreEqual("'公的' is invalid value.", actualError.ErrorMessage); + Assert.AreEqual("contactEmail", actualError.PropertyName); + Assert.AreEqual(nameof(EmailTypeValidator), actualError.ErrorCode); + Assert.AreEqual("'contactEmail' is not a valid email address.", actualError.ErrorMessage); } } \ No newline at end of file diff --git a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileTypeValidator.cs b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileTypeValidator.cs index 6c857fd2..7336f78c 100644 --- a/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileTypeValidator.cs +++ b/ModelValidation/Lab.DictionaryValidation/Lab.DictionaryFluentValidation/Validators/ProfileTypeValidator.cs @@ -6,13 +6,25 @@ namespace Lab.DictionaryFluentValidation.Validators; public class ProfileTypeValidator : AbstractValidator> { - private static EmailTypeValidator EmailTypeValidator => new(ProfileTypeNames.ContactEmail); + private static readonly Lazy s_emailTypeValidatorLazy = + new(() => new EmailTypeValidator(ProfileTypeNames.ContactEmail)); - private static NameTypeValidator NameTypeValidator => new(ProfileTypeNames.Name); + private static readonly Lazy s_nameTypeValidator = + new Lazy(() => new NameTypeValidator(ProfileTypeNames.Name)); - private static BirthdayTypeValidator BirthdayTypeValidator => new(ProfileTypeNames.Birthday); + private static readonly Lazy s_birthdayTypeValidatorLazy = + new(() => new BirthdayTypeValidator(ProfileTypeNames.Birthday)); - private static GenderTypeValidator GenderTypeValidator => new(ProfileTypeNames.Gender); + private static readonly Lazy s_genderTypeValidatorLazy = + new(() => new GenderTypeValidator(ProfileTypeNames.Gender)); + + private static EmailTypeValidator EmailTypeValidator => s_emailTypeValidatorLazy.Value; + + private static NameTypeValidator NameTypeValidator => s_nameTypeValidator.Value; + + private static BirthdayTypeValidator BirthdayTypeValidator => s_birthdayTypeValidatorLazy.Value; + + private static GenderTypeValidator GenderTypeValidator => s_genderTypeValidatorLazy.Value; private static bool IsNotSupportFields(ValidationContext> context) { @@ -44,18 +56,18 @@ private static bool IsNotSupportFields(Dictionary sourceFields, string destFieldName, ValidationContext> context) { - var isSNotSupport = sourceFields.ContainsKey(destFieldName) == false; - if (isSNotSupport) + var isNotSupport = sourceFields.ContainsKey(destFieldName) == false; + if (isNotSupport) { var failure = new ValidationFailure(destFieldName, - $"not support column '{destFieldName}'") + $"'{destFieldName}' column not support") { ErrorCode = "NotSupportValidator", }; context.AddFailure(failure); } - return isSNotSupport; + return isNotSupport; } private static bool IsNotSupportNestFields(Dictionary sourceFields, @@ -106,7 +118,6 @@ private void SetValidateRule(Dictionary instances) { case ProfileTypeNames.ContactEmail: { - var PropertyName = fieldName; this.RuleFor(p => p[fieldName]) .SetValidator(p => EmailTypeValidator) ; From 94322d1977f286557249ce695807f1a8b5f6116d Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 15 Apr 2022 23:47:12 +0800 Subject: [PATCH 207/424] feat: add DictionaryStringObjectJsonConverter2 --- ...ctionaryStringObjectJsonConverter2Tests.cs | 243 ++++++++++++++++++ .../Lab.JsonConverterLib.UnitTest.csproj | 10 +- .../DictionaryStringObjectJsonConverter2.cs | 145 +++++++++++ 3 files changed, 393 insertions(+), 5 deletions(-) create mode 100644 Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverter2Tests.cs create mode 100644 Json/Lab.JsonConverter/Lab.JsonConverterLib/DictionaryStringObjectJsonConverter2.cs diff --git a/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverter2Tests.cs b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverter2Tests.cs new file mode 100644 index 00000000..8e882f2a --- /dev/null +++ b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverter2Tests.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.JsonConverterLib.UnitTest; + +[TestClass] +public class DictionaryStringObjectJsonConverter2Tests +{ + [TestMethod] + public void JsonDocument轉Dictionary() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter2() } + }; + var expected = new Dictionary + { + // ["anonymousType"] = new Dictionary() + // { + // { "Prop", 123 } + // }, + // + // ["null"] = null!, + // ["model"] = new Dictionary() + // { + // { "Age", 19 }, + // { "Name", "小章" } + // }, + // ["dateTimeOffset"] = DateTimeOffset.Now, + // ["long"] = (long)255, + // ["decimal"] = (decimal)3.1416, + // ["guid"] = Guid.NewGuid(), + // ["string"] = "字串", + ["decimalArray"] = new List() { 1, (decimal)2.1 } + }; + var json = JsonSerializer.Serialize(expected); + + using var jsonDoc = json.ToJsonDocument(); + var actual = jsonDoc.To>(options); + + Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); + Assert.AreEqual(expected["string"], actual["string"]); + Assert.AreEqual(expected["long"], actual["long"]); + Assert.AreEqual(expected["decimal"], actual["decimal"]); + + // Assert.AreEqual(expected["null"], actual["null"]); + + AssertAnonymousType(actual["anonymousType"] as Dictionary); + AssertDecimalArray(actual["decimalArray"] as List); + } + + [TestMethod] + public void JsonDocument轉Dictionary1() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + var expected = new Dictionary + { + ["anonymousType"] = new { Prop = 123 }, + ["model"] = new Model { Age = 19, Name = "小章" }, + ["null"] = null!, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["long"] = (long)255, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "字串", + ["decimalArray"] = new[] { 1, (decimal)2.1 }, + }; + + using var jsonDoc = expected.ToJsonDocument(); + var actual = jsonDoc.To>(options); + + Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); + Assert.AreEqual(expected["string"], actual["string"]); + Assert.AreEqual(expected["long"], actual["long"]); + Assert.AreEqual(expected["decimal"], actual["decimal"]); + Assert.AreEqual(expected["null"], actual["null"]); + + AssertAnonymousType(actual["anonymousType"] as Dictionary); + AssertDecimalArray(actual["decimalArray"] as List); + } + + [TestMethod] + public void JsonsNode轉Dictionary() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + var expected = new Dictionary + { + ["anonymousType"] = new { Prop = 123 }, + ["model"] = new Model { Age = 19, Name = "小章" }, + ["null"] = null!, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["long"] = (long)255, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "字串", + ["decimalArray"] = new[] { 1, (decimal)2.1 }, + }; + var json = JsonSerializer.Serialize(expected); + + var jsonObject = json.ToJsonNode(); + var actual = jsonObject.To>(options); + + Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); + Assert.AreEqual(expected["string"], actual["string"]); + Assert.AreEqual(expected["long"], actual["long"]); + Assert.AreEqual(expected["decimal"], actual["decimal"]); + Assert.AreEqual(expected["null"], actual["null"]); + + AssertAnonymousType(actual["anonymousType"] as Dictionary); + AssertDecimalArray(actual["decimalArray"] as List); + } + + [TestMethod] + public void Memory轉Dictionary() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + + var expected = new Dictionary + { + ["anonymousType"] = new { Prop = 123 }, + ["model"] = new Model { Age = 19, Name = "小章" }, + ["null"] = null!, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["long"] = (long)255, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "字串", + ["decimalArray"] = new[] { 1, (decimal)2.1 }, + }; + + var json = JsonSerializer.Serialize(expected, options); + var jsonMemory = new MemoryStream(Encoding.UTF8.GetBytes(json)); + + var actual = JsonSerializer.Deserialize>(jsonMemory, options); + + Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); + Assert.AreEqual(expected["string"], actual["string"]); + Assert.AreEqual(expected["long"], actual["long"]); + Assert.AreEqual(expected["decimal"], actual["decimal"]); + Assert.AreEqual(expected["null"], actual["null"]); + + AssertAnonymousType(actual["anonymousType"] as Dictionary); + AssertDecimalArray(actual["decimalArray"] as List); + } + + [TestMethod] + public void 字串轉Dictionary() + { + var options = new JsonSerializerOptions + { + Converters = { new DictionaryStringObjectJsonConverter() } + }; + var expected = new Dictionary + { + ["anonymousType"] = new { Prop = 123 }, + ["model"] = new Model { Age = 19, Name = "小章" }, + ["null"] = null!, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["long"] = (long)255, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "字串", + ["decimalArray"] = new[] { 1, (decimal)2.1 }, + }; + + var json = JsonSerializer.Serialize(expected, options); + var actual = JsonSerializer.Deserialize>(json, options); + + Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); + Assert.AreEqual(expected["string"], actual["string"]); + Assert.AreEqual(expected["long"], actual["long"]); + Assert.AreEqual(expected["decimal"], actual["decimal"]); + Assert.AreEqual(expected["null"], actual["null"]); + + AssertAnonymousType(actual["anonymousType"] as Dictionary); + AssertDecimalArray(actual["decimalArray"] as List); + } + + [TestMethod] + public void 字串轉Dictionary_失敗() + { + var expected = new Dictionary + { + ["i"] = 255, + ["s"] = "字串", + ["d"] = new DateTime(1900, 1, 1), + ["a"] = new[] { 1, 2 }, + ["o"] = new { Prop = 123 } + }; + var json = JsonSerializer.Serialize(expected); + + var actual = JsonSerializer.Deserialize>(json); + Assert.AreNotEqual(expected["i"], actual["i"]); + Assert.AreNotEqual(expected["s"], actual["s"]); + + // 反序列化之後得到 JsonElement Type,必須要要透過其他手段才能取得真實的值 + Assert.AreEqual("JsonElement", actual["s"].GetType().Name); + Assert.AreEqual(expected["i"], ((JsonElement)actual["i"]).GetInt32()); + Assert.AreEqual(expected["s"], ((JsonElement)actual["s"]).GetString()); + } + + private static void AssertAnonymousType(Dictionary actual) + { + var expected = new Dictionary + { + { "Prop", (long)123 } + }; + + Assert.AreEqual(expected["Prop"], actual["Prop"]); + } + + private static void AssertDecimalArray(List actual) + { + var expected = new List + { + (long)1, + (decimal)2.1 + }; + + Assert.AreEqual(expected[0], actual[0]); + Assert.AreEqual(expected[1], actual[1]); + } + + private class Model + { + public string Name { get; set; } + + public int Age { get; set; } + } +} \ No newline at end of file diff --git a/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/Lab.JsonConverterLib.UnitTest.csproj b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/Lab.JsonConverterLib.UnitTest.csproj index d0b60f4b..3eb035e8 100644 --- a/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/Lab.JsonConverterLib.UnitTest.csproj +++ b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/Lab.JsonConverterLib.UnitTest.csproj @@ -8,14 +8,14 @@ - - - - + + + + - + diff --git a/Json/Lab.JsonConverter/Lab.JsonConverterLib/DictionaryStringObjectJsonConverter2.cs b/Json/Lab.JsonConverter/Lab.JsonConverterLib/DictionaryStringObjectJsonConverter2.cs new file mode 100644 index 00000000..9de6b135 --- /dev/null +++ b/Json/Lab.JsonConverter/Lab.JsonConverterLib/DictionaryStringObjectJsonConverter2.cs @@ -0,0 +1,145 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Lab.JsonConverterLib; + +public class DictionaryStringObjectJsonConverter2 : JsonConverter> +{ + public override Dictionary Read(ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException($"JsonTokenType was of type {reader.TokenType}, only objects are supported"); + } + + var results = new Dictionary(); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + return results; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("JsonTokenType was not PropertyName"); + } + + var propertyName = reader.GetString(); + + if (string.IsNullOrWhiteSpace(propertyName)) + { + throw new JsonException("Failed to get property name"); + } + + reader.Read(); + + results.Add(propertyName, this.ReadValue(ref reader, options)); + } + + return results; + } + + public override void Write(Utf8JsonWriter writer, + Dictionary value, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + + foreach (var key in value.Keys) + { + WriteValue(writer, key, value[key], options); + } + + writer.WriteEndObject(); + } + + private Dictionary ReadObjectValue(ref Utf8JsonReader reader, + JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException($"JsonTokenType was of type {reader.TokenType}, only objects are supported"); + } + + var results = new Dictionary(); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + return results; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("JsonTokenType was not PropertyName"); + } + + var propertyName = reader.GetString(); + + if (string.IsNullOrWhiteSpace(propertyName)) + { + throw new JsonException("Failed to get property name"); + } + + reader.Read(); + + results.Add(propertyName, this.ReadValue(ref reader, options)); + } + + return results; + } + + private object ReadValue(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + if (reader.TryGetDateTimeOffset(out var dateOffset)) + { + return dateOffset; + } + + if (reader.TryGetGuid(out var guid)) + { + return guid; + } + + return reader.GetString(); + case JsonTokenType.False: + case JsonTokenType.True: + return reader.GetBoolean(); + case JsonTokenType.Null: + return null; + case JsonTokenType.Number: + return reader.GetDecimal(); + case JsonTokenType.StartObject: + return this.ReadObjectValue(ref reader, options); + case JsonTokenType.StartArray: + var list = new List(); + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + list.Add(this.ReadValue(ref reader, options)); + } + + return list; + default: + throw new JsonException($"'{reader.TokenType}' is not supported"); + } + } + + private static void WriteValue(Utf8JsonWriter writer, + string key, + object value, + JsonSerializerOptions options) + { + if (key != null) + { + writer.WritePropertyName(key); + } + + JsonSerializer.Serialize(writer, value, options); + } +} \ No newline at end of file From e93914040acc0f1fb4b42f077d9a1b0b11a615a8 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 16 Apr 2022 20:56:28 +0800 Subject: [PATCH 208/424] feat: add test case --- ...ctionaryStringObjectJsonConverter2Tests.cs | 169 ++++-------------- ...ictionaryStringObjectJsonConverterTests.cs | 142 ++++----------- .../Lab.JsonConverterLib.UnitTest.csproj | 11 +- 3 files changed, 75 insertions(+), 247 deletions(-) diff --git a/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverter2Tests.cs b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverter2Tests.cs index 8e882f2a..12b8ebaf 100644 --- a/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverter2Tests.cs +++ b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverter2Tests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Text; using System.Text.Json; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Lab.JsonConverterLib.UnitTest; @@ -17,73 +18,12 @@ public void JsonDocument轉Dictionary() { Converters = { new DictionaryStringObjectJsonConverter2() } }; - var expected = new Dictionary - { - // ["anonymousType"] = new Dictionary() - // { - // { "Prop", 123 } - // }, - // - // ["null"] = null!, - // ["model"] = new Dictionary() - // { - // { "Age", 19 }, - // { "Name", "小章" } - // }, - // ["dateTimeOffset"] = DateTimeOffset.Now, - // ["long"] = (long)255, - // ["decimal"] = (decimal)3.1416, - // ["guid"] = Guid.NewGuid(), - // ["string"] = "字串", - ["decimalArray"] = new List() { 1, (decimal)2.1 } - }; + var expected = CreateDictionary(); var json = JsonSerializer.Serialize(expected); using var jsonDoc = json.ToJsonDocument(); var actual = jsonDoc.To>(options); - - Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); - Assert.AreEqual(expected["string"], actual["string"]); - Assert.AreEqual(expected["long"], actual["long"]); - Assert.AreEqual(expected["decimal"], actual["decimal"]); - - // Assert.AreEqual(expected["null"], actual["null"]); - - AssertAnonymousType(actual["anonymousType"] as Dictionary); - AssertDecimalArray(actual["decimalArray"] as List); - } - - [TestMethod] - public void JsonDocument轉Dictionary1() - { - var options = new JsonSerializerOptions - { - Converters = { new DictionaryStringObjectJsonConverter() } - }; - var expected = new Dictionary - { - ["anonymousType"] = new { Prop = 123 }, - ["model"] = new Model { Age = 19, Name = "小章" }, - ["null"] = null!, - ["dateTimeOffset"] = DateTimeOffset.Now, - ["long"] = (long)255, - ["decimal"] = (decimal)3.1416, - ["guid"] = Guid.NewGuid(), - ["string"] = "字串", - ["decimalArray"] = new[] { 1, (decimal)2.1 }, - }; - - using var jsonDoc = expected.ToJsonDocument(); - var actual = jsonDoc.To>(options); - - Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); - Assert.AreEqual(expected["string"], actual["string"]); - Assert.AreEqual(expected["long"], actual["long"]); - Assert.AreEqual(expected["decimal"], actual["decimal"]); - Assert.AreEqual(expected["null"], actual["null"]); - - AssertAnonymousType(actual["anonymousType"] as Dictionary); - AssertDecimalArray(actual["decimalArray"] as List); + AssertThat(actual, expected); } [TestMethod] @@ -91,33 +31,25 @@ public void JsonsNode轉Dictionary() { var options = new JsonSerializerOptions { - Converters = { new DictionaryStringObjectJsonConverter() } - }; - var expected = new Dictionary - { - ["anonymousType"] = new { Prop = 123 }, - ["model"] = new Model { Age = 19, Name = "小章" }, - ["null"] = null!, - ["dateTimeOffset"] = DateTimeOffset.Now, - ["long"] = (long)255, - ["decimal"] = (decimal)3.1416, - ["guid"] = Guid.NewGuid(), - ["string"] = "字串", - ["decimalArray"] = new[] { 1, (decimal)2.1 }, + Converters = { new DictionaryStringObjectJsonConverter2() } }; + var expected = CreateDictionary(); var json = JsonSerializer.Serialize(expected); var jsonObject = json.ToJsonNode(); var actual = jsonObject.To>(options); + AssertThat(actual, expected); + } + + private static void AssertThat(Dictionary actual, Dictionary expected) + { + actual["model"].Should().BeEquivalentTo(expected["model"]); + actual["decimalArray"].Should().BeEquivalentTo(expected["decimalArray"]); Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); - Assert.AreEqual(expected["string"], actual["string"]); - Assert.AreEqual(expected["long"], actual["long"]); Assert.AreEqual(expected["decimal"], actual["decimal"]); - Assert.AreEqual(expected["null"], actual["null"]); - - AssertAnonymousType(actual["anonymousType"] as Dictionary); - AssertDecimalArray(actual["decimalArray"] as List); + Assert.AreEqual(expected["guid"], actual["guid"]); + Assert.AreEqual(expected["string"], actual["string"]); } [TestMethod] @@ -127,33 +59,14 @@ public void Memory轉Dictionary() { Converters = { new DictionaryStringObjectJsonConverter() } }; - - var expected = new Dictionary - { - ["anonymousType"] = new { Prop = 123 }, - ["model"] = new Model { Age = 19, Name = "小章" }, - ["null"] = null!, - ["dateTimeOffset"] = DateTimeOffset.Now, - ["long"] = (long)255, - ["decimal"] = (decimal)3.1416, - ["guid"] = Guid.NewGuid(), - ["string"] = "字串", - ["decimalArray"] = new[] { 1, (decimal)2.1 }, - }; + var expected = CreateDictionary(); var json = JsonSerializer.Serialize(expected, options); var jsonMemory = new MemoryStream(Encoding.UTF8.GetBytes(json)); var actual = JsonSerializer.Deserialize>(jsonMemory, options); - Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); - Assert.AreEqual(expected["string"], actual["string"]); - Assert.AreEqual(expected["long"], actual["long"]); - Assert.AreEqual(expected["decimal"], actual["decimal"]); - Assert.AreEqual(expected["null"], actual["null"]); - - AssertAnonymousType(actual["anonymousType"] as Dictionary); - AssertDecimalArray(actual["decimalArray"] as List); + AssertThat(actual, expected); } [TestMethod] @@ -163,30 +76,12 @@ public void 字串轉Dictionary() { Converters = { new DictionaryStringObjectJsonConverter() } }; - var expected = new Dictionary - { - ["anonymousType"] = new { Prop = 123 }, - ["model"] = new Model { Age = 19, Name = "小章" }, - ["null"] = null!, - ["dateTimeOffset"] = DateTimeOffset.Now, - ["long"] = (long)255, - ["decimal"] = (decimal)3.1416, - ["guid"] = Guid.NewGuid(), - ["string"] = "字串", - ["decimalArray"] = new[] { 1, (decimal)2.1 }, - }; + var expected = CreateDictionary(); var json = JsonSerializer.Serialize(expected, options); var actual = JsonSerializer.Deserialize>(json, options); - Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); - Assert.AreEqual(expected["string"], actual["string"]); - Assert.AreEqual(expected["long"], actual["long"]); - Assert.AreEqual(expected["decimal"], actual["decimal"]); - Assert.AreEqual(expected["null"], actual["null"]); - - AssertAnonymousType(actual["anonymousType"] as Dictionary); - AssertDecimalArray(actual["decimalArray"] as List); + AssertThat(actual, expected); } [TestMethod] @@ -212,26 +107,22 @@ public void 字串轉Dictionary_失敗() Assert.AreEqual(expected["s"], ((JsonElement)actual["s"]).GetString()); } - private static void AssertAnonymousType(Dictionary actual) + private static Dictionary CreateDictionary() { var expected = new Dictionary { - { "Prop", (long)123 } - }; - - Assert.AreEqual(expected["Prop"], actual["Prop"]); - } - - private static void AssertDecimalArray(List actual) - { - var expected = new List - { - (long)1, - (decimal)2.1 + ["model"] = new Dictionary + { + { "Age", 19 }, + { "Name", "小章" } + }, + ["decimalArray"] = new List { 1, (decimal)2.1 }, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "字串", }; - - Assert.AreEqual(expected[0], actual[0]); - Assert.AreEqual(expected[1], actual[1]); + return expected; } private class Model diff --git a/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverterTests.cs b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverterTests.cs index 48c87558..4cd6e579 100644 --- a/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverterTests.cs +++ b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/DictionaryStringObjectJsonConverterTests.cs @@ -17,31 +17,13 @@ public void JsonDocument轉Dictionary() { Converters = { new DictionaryStringObjectJsonConverter() } }; - var expected = new Dictionary - { - ["anonymousType"] = new { Prop = 123 }, - ["model"] = new Model { Age = 19, Name = "小章" }, - ["null"] = null!, - ["dateTimeOffset"] = DateTimeOffset.Now, - ["long"] = (long)255, - ["decimal"] = (decimal)3.1416, - ["guid"] = Guid.NewGuid(), - ["string"] = "字串", - ["decimalArray"] = new[] { 1, (decimal)2.1 }, - }; + var expected = CreateDictionary(); var json = JsonSerializer.Serialize(expected); using var jsonDoc = json.ToJsonDocument(); var actual = jsonDoc.To>(options); - Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); - Assert.AreEqual(expected["string"], actual["string"]); - Assert.AreEqual(expected["long"], actual["long"]); - Assert.AreEqual(expected["decimal"], actual["decimal"]); - Assert.AreEqual(expected["null"], actual["null"]); - - AssertAnonymousType(actual["anonymousType"] as Dictionary); - AssertDecimalArray(actual["decimalArray"] as List); + AssertThat(expected, actual); } [TestMethod] @@ -51,30 +33,11 @@ public void JsonDocument轉Dictionary1() { Converters = { new DictionaryStringObjectJsonConverter() } }; - var expected = new Dictionary - { - ["anonymousType"] = new { Prop = 123 }, - ["model"] = new Model { Age = 19, Name = "小章" }, - ["null"] = null!, - ["dateTimeOffset"] = DateTimeOffset.Now, - ["long"] = (long)255, - ["decimal"] = (decimal)3.1416, - ["guid"] = Guid.NewGuid(), - ["string"] = "字串", - ["decimalArray"] = new[] { 1, (decimal)2.1 }, - }; + var expected = CreateDictionary(); using var jsonDoc = expected.ToJsonDocument(); var actual = jsonDoc.To>(options); - - Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); - Assert.AreEqual(expected["string"], actual["string"]); - Assert.AreEqual(expected["long"], actual["long"]); - Assert.AreEqual(expected["decimal"], actual["decimal"]); - Assert.AreEqual(expected["null"], actual["null"]); - - AssertAnonymousType(actual["anonymousType"] as Dictionary); - AssertDecimalArray(actual["decimalArray"] as List); + AssertThat(expected, actual); } [TestMethod] @@ -84,31 +47,13 @@ public void JsonsNode轉Dictionary() { Converters = { new DictionaryStringObjectJsonConverter() } }; - var expected = new Dictionary - { - ["anonymousType"] = new { Prop = 123 }, - ["model"] = new Model { Age = 19, Name = "小章" }, - ["null"] = null!, - ["dateTimeOffset"] = DateTimeOffset.Now, - ["long"] = (long)255, - ["decimal"] = (decimal)3.1416, - ["guid"] = Guid.NewGuid(), - ["string"] = "字串", - ["decimalArray"] = new[] { 1, (decimal)2.1 }, - }; + var expected = CreateDictionary(); var json = JsonSerializer.Serialize(expected); var jsonObject = json.ToJsonNode(); var actual = jsonObject.To>(options); - Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); - Assert.AreEqual(expected["string"], actual["string"]); - Assert.AreEqual(expected["long"], actual["long"]); - Assert.AreEqual(expected["decimal"], actual["decimal"]); - Assert.AreEqual(expected["null"], actual["null"]); - - AssertAnonymousType(actual["anonymousType"] as Dictionary); - AssertDecimalArray(actual["decimalArray"] as List); + AssertThat(expected, actual); } [TestMethod] @@ -119,32 +64,13 @@ public void Memory轉Dictionary() Converters = { new DictionaryStringObjectJsonConverter() } }; - var expected = new Dictionary - { - ["anonymousType"] = new { Prop = 123 }, - ["model"] = new Model { Age = 19, Name = "小章" }, - ["null"] = null!, - ["dateTimeOffset"] = DateTimeOffset.Now, - ["long"] = (long)255, - ["decimal"] = (decimal)3.1416, - ["guid"] = Guid.NewGuid(), - ["string"] = "字串", - ["decimalArray"] = new[] { 1, (decimal)2.1 }, - }; + var expected = CreateDictionary(); var json = JsonSerializer.Serialize(expected, options); var jsonMemory = new MemoryStream(Encoding.UTF8.GetBytes(json)); var actual = JsonSerializer.Deserialize>(jsonMemory, options); - - Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); - Assert.AreEqual(expected["string"], actual["string"]); - Assert.AreEqual(expected["long"], actual["long"]); - Assert.AreEqual(expected["decimal"], actual["decimal"]); - Assert.AreEqual(expected["null"], actual["null"]); - - AssertAnonymousType(actual["anonymousType"] as Dictionary); - AssertDecimalArray(actual["decimalArray"] as List); + AssertThat(expected, actual); } [TestMethod] @@ -154,30 +80,11 @@ public void 字串轉Dictionary() { Converters = { new DictionaryStringObjectJsonConverter() } }; - var expected = new Dictionary - { - ["anonymousType"] = new { Prop = 123 }, - ["model"] = new Model { Age = 19, Name = "小章" }, - ["null"] = null!, - ["dateTimeOffset"] = DateTimeOffset.Now, - ["long"] = (long)255, - ["decimal"] = (decimal)3.1416, - ["guid"] = Guid.NewGuid(), - ["string"] = "字串", - ["decimalArray"] = new[] { 1, (decimal)2.1 }, - }; + var expected = CreateDictionary(); var json = JsonSerializer.Serialize(expected, options); var actual = JsonSerializer.Deserialize>(json, options); - - Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); - Assert.AreEqual(expected["string"], actual["string"]); - Assert.AreEqual(expected["long"], actual["long"]); - Assert.AreEqual(expected["decimal"], actual["decimal"]); - Assert.AreEqual(expected["null"], actual["null"]); - - AssertAnonymousType(actual["anonymousType"] as Dictionary); - AssertDecimalArray(actual["decimalArray"] as List); + AssertThat(expected, actual); } [TestMethod] @@ -225,6 +132,35 @@ private static void AssertDecimalArray(List actual) Assert.AreEqual(expected[1], actual[1]); } + private static void AssertThat(Dictionary expected, Dictionary actual) + { + Assert.AreEqual(expected["dateTimeOffset"], actual["dateTimeOffset"]); + Assert.AreEqual(expected["string"], actual["string"]); + Assert.AreEqual(expected["long"], actual["long"]); + Assert.AreEqual(expected["decimal"], actual["decimal"]); + Assert.AreEqual(expected["null"], actual["null"]); + + AssertAnonymousType(actual["anonymousType"] as Dictionary); + AssertDecimalArray(actual["decimalArray"] as List); + } + + private static Dictionary CreateDictionary() + { + var expected = new Dictionary + { + ["anonymousType"] = new { Prop = 123 }, + ["model"] = new Model { Age = 19, Name = "小章" }, + ["null"] = null!, + ["dateTimeOffset"] = DateTimeOffset.Now, + ["long"] = (long)255, + ["decimal"] = (decimal)3.1416, + ["guid"] = Guid.NewGuid(), + ["string"] = "字串", + ["decimalArray"] = new[] { 1, (decimal)2.1 }, + }; + return expected; + } + private class Model { public string Name { get; set; } diff --git a/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/Lab.JsonConverterLib.UnitTest.csproj b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/Lab.JsonConverterLib.UnitTest.csproj index 3eb035e8..ec415e09 100644 --- a/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/Lab.JsonConverterLib.UnitTest.csproj +++ b/Json/Lab.JsonConverter/Lab.JsonConverterLib.UnitTest/Lab.JsonConverterLib.UnitTest.csproj @@ -8,14 +8,15 @@ - - - - + + + + + - + From 6082108d8425a2cda16700e55df8d39d72588963 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 14 May 2022 17:36:40 +0800 Subject: [PATCH 209/424] =?UTF-8?q?fea:=20=E6=96=B0=E5=A2=9E=20System.Text?= =?UTF-8?q?.Json.JsonDiffPatch=20=E6=B8=AC=E8=A9=A6=E6=A1=88=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Lab.Json.UnitTest.csproj | 17 +++ .../Lab.Json.UnitTest/UnitTest1.cs | 12 ++ .../Lab.JsonCompare.UnitTest.csproj | 18 +++ .../SystemTextJsonTests.cs | 137 ++++++++++++++++++ Json/Lab.JsonCompare/Lab.JsonCompare.sln | 16 ++ 5 files changed, 200 insertions(+) create mode 100644 Json/Lab.JsonCompare/Lab.Json.UnitTest/Lab.Json.UnitTest.csproj create mode 100644 Json/Lab.JsonCompare/Lab.Json.UnitTest/UnitTest1.cs create mode 100644 Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/Lab.JsonCompare.UnitTest.csproj create mode 100644 Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonTests.cs create mode 100644 Json/Lab.JsonCompare/Lab.JsonCompare.sln diff --git a/Json/Lab.JsonCompare/Lab.Json.UnitTest/Lab.Json.UnitTest.csproj b/Json/Lab.JsonCompare/Lab.Json.UnitTest/Lab.Json.UnitTest.csproj new file mode 100644 index 00000000..c83c50db --- /dev/null +++ b/Json/Lab.JsonCompare/Lab.Json.UnitTest/Lab.Json.UnitTest.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + + false + + + + + + + + + + diff --git a/Json/Lab.JsonCompare/Lab.Json.UnitTest/UnitTest1.cs b/Json/Lab.JsonCompare/Lab.Json.UnitTest/UnitTest1.cs new file mode 100644 index 00000000..9d646e0a --- /dev/null +++ b/Json/Lab.JsonCompare/Lab.Json.UnitTest/UnitTest1.cs @@ -0,0 +1,12 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.Json.UnitTest; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestMethod1() + { + } +} \ No newline at end of file diff --git a/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/Lab.JsonCompare.UnitTest.csproj b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/Lab.JsonCompare.UnitTest.csproj new file mode 100644 index 00000000..74a9fc61 --- /dev/null +++ b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/Lab.JsonCompare.UnitTest.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + diff --git a/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonTests.cs b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonTests.cs new file mode 100644 index 00000000..5a15b6d0 --- /dev/null +++ b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonTests.cs @@ -0,0 +1,137 @@ +using System; +using System.Text.Json; +using System.Text.Json.JsonDiffPatch; +using System.Text.Json.JsonDiffPatch.MsTest; +using System.Text.Json.Nodes; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.JsonCompare.UnitTest; + +[TestClass] +public class SystemTextJsonTests +{ + [TestMethod] + public void 比對兩個一樣的Json字串_via_JsonDiffPatcher() + { + var o1 = new JsonObject + { + { "Integer", 12345 }, + { "String", "A string" }, + { "Items", new JsonArray(1, 2) } + }; + + var o2 = new JsonObject + { + { "Integer", 12345 }, + { "String", "A string" }, + { "Items", new JsonArray(1, 2) } + }; + + var left = o1.ToJsonString(); + var right = o2.ToJsonString(); + var diff = JsonDiffPatcher.Diff(left, right); + if (diff != null) + { + Console.WriteLine(JsonSerializer.Serialize(diff)); + } + + Assert.IsNull(diff); + } + + [TestMethod] + public void 比對兩個不一樣的JsonObject() + { + var o1 = new JsonObject + { + { "Integer", 12345 }, + { "String", JsonValue.Create("A string") }, + { "Items", new JsonArray(1, 2) } + }; + + var o2 = new JsonObject + { + { "integer", 12345 }, + { "String", "A string" }, + { "Items", new JsonArray(1, 2, new JsonArray { "a", "b" }) } + }; + + var diff = o1.Diff(o2); + if (diff != null) + { + Console.WriteLine(JsonSerializer.Serialize(diff)); + } + + Assert.IsNotNull(diff); + } + + [TestMethod] + public void 比對兩個不一樣的JsonNode() + { + var o1 = new JsonObject + { + { "Integer", 12345 }, + { "String", JsonValue.Create("A string") }, + { "Items", new JsonArray(1, 2) } + }; + + var o2 = new JsonObject + { + { "integer", 12345 }, + { "String", "A string" }, + { "Items", new JsonArray(1, 2, new JsonArray { "a", "b" }) } + }; + + var left = JsonNode.Parse(o1.ToJsonString()); + var right = JsonNode.Parse(o2.ToJsonString()); + var diff = left.Diff(right); + if (diff != null) + { + Console.WriteLine(JsonSerializer.Serialize(diff)); + } + + Assert.IsNotNull(diff); + } + + [TestMethod] + public void 比對兩個不一樣的JsonObject_via_JsonAssert() + { + var o1 = new JsonObject + { + { "Integer", 12345 }, + { "String", JsonValue.Create("A string") }, + { "Items", new JsonArray(1, 2) } + }; + + var o2 = new JsonObject + { + { "integer", 12345 }, + { "String", "A string" }, + { "Items", new JsonArray(1, 2, new JsonArray { "a", "b" }) } + }; + Assert.That.JsonAreEqual(o1, o2, true); + } + + [TestMethod] + public void 比對兩個不一樣的JsonDocument() + { + var o1 = new JsonObject + { + { "Integer", 12345 }, + { "String", JsonValue.Create("A string") }, + { "Items", new JsonArray(1, 2) } + }; + + var o2 = new JsonObject + { + { "integer", 12345 }, + { "String", "A string" }, + { "Items", new JsonArray(1, 2, new JsonArray { "a", "b" }) } + }; + + var left = JsonDocument.Parse(o1.ToJsonString()); + var right = JsonDocument.Parse(o2.ToJsonString()); + + var isEquals = left.DeepEquals(right); + Assert.IsFalse(isEquals); + } +} \ No newline at end of file diff --git a/Json/Lab.JsonCompare/Lab.JsonCompare.sln b/Json/Lab.JsonCompare/Lab.JsonCompare.sln new file mode 100644 index 00000000..77f11d88 --- /dev/null +++ b/Json/Lab.JsonCompare/Lab.JsonCompare.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.JsonCompare.UnitTest", "Lab.JsonCompare.UnitTest\Lab.JsonCompare.UnitTest.csproj", "{D79E4743-D20C-4712-8E8E-F0323D05A339}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D79E4743-D20C-4712-8E8E-F0323D05A339}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D79E4743-D20C-4712-8E8E-F0323D05A339}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D79E4743-D20C-4712-8E8E-F0323D05A339}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D79E4743-D20C-4712-8E8E-F0323D05A339}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From 2c9f5f2791861161194aabc56ab7938d6e71a16c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 May 2022 09:37:19 +0000 Subject: [PATCH 210/424] build(deps): bump CefSharp.WinForms in /CEF/Lab.Startup/WinFormNet48 Bumps [CefSharp.WinForms](https://github.com/cefsharp/cefsharp) from 79.1.360 to 98.1.210. - [Release notes](https://github.com/cefsharp/cefsharp/releases) - [Commits](https://github.com/cefsharp/cefsharp/compare/v79.1.360...v98.1.210) --- updated-dependencies: - dependency-name: CefSharp.WinForms dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- CEF/Lab.Startup/WinFormNet48/packages.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEF/Lab.Startup/WinFormNet48/packages.config b/CEF/Lab.Startup/WinFormNet48/packages.config index 7ad8ca9f..db8da869 100644 --- a/CEF/Lab.Startup/WinFormNet48/packages.config +++ b/CEF/Lab.Startup/WinFormNet48/packages.config @@ -4,7 +4,7 @@ - + \ No newline at end of file From 7f7396ab630b8c72efacd6c6612845ebf94e884e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 May 2022 09:37:21 +0000 Subject: [PATCH 211/424] build(deps): bump CefSharp.WinForms in /CEF/Lab.Startup/WinFormCore30 Bumps [CefSharp.WinForms](https://github.com/cefsharp/cefsharp) from 79.1.360 to 98.1.210. - [Release notes](https://github.com/cefsharp/cefsharp/releases) - [Commits](https://github.com/cefsharp/cefsharp/compare/v79.1.360...v98.1.210) --- updated-dependencies: - dependency-name: CefSharp.WinForms dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- CEF/Lab.Startup/WinFormCore30/WinFormCore30.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CEF/Lab.Startup/WinFormCore30/WinFormCore30.csproj b/CEF/Lab.Startup/WinFormCore30/WinFormCore30.csproj index ae5a740d..755aa470 100644 --- a/CEF/Lab.Startup/WinFormCore30/WinFormCore30.csproj +++ b/CEF/Lab.Startup/WinFormCore30/WinFormCore30.csproj @@ -18,7 +18,7 @@ - + From 585697a13917514485e853e9aa8fec1cbc7b3f34 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 14 May 2022 20:02:43 +0800 Subject: [PATCH 212/424] feat: add NewtonsoftJsonDiffPathTests --- .../Lab.JsonCompare.UnitTest.csproj | 11 ++-- .../NewtonsoftJsonDiffPathTests.cs | 57 +++++++++++++++++++ ...ests.cs => SystemTextJsonDiffPathTests.cs} | 2 +- 3 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/NewtonsoftJsonDiffPathTests.cs rename Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/{SystemTextJsonTests.cs => SystemTextJsonDiffPathTests.cs} (98%) diff --git a/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/Lab.JsonCompare.UnitTest.csproj b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/Lab.JsonCompare.UnitTest.csproj index 74a9fc61..fbe32aae 100644 --- a/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/Lab.JsonCompare.UnitTest.csproj +++ b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/Lab.JsonCompare.UnitTest.csproj @@ -8,11 +8,12 @@ - - - - - + + + + + + diff --git a/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/NewtonsoftJsonDiffPathTests.cs b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/NewtonsoftJsonDiffPathTests.cs new file mode 100644 index 00000000..5585fd1a --- /dev/null +++ b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/NewtonsoftJsonDiffPathTests.cs @@ -0,0 +1,57 @@ +using System; +using JsonDiffPatchDotNet; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json.Linq; + +namespace Lab.JsonCompare.UnitTest; + +[TestClass] +public class NewtonsoftJsonDiffPathTests +{ + [TestMethod] + public void 比對兩個一樣的JObject() + { + JObject o1 = new JObject + { + { "Integer", 12345 }, + { "String", "A string" }, + { "Items", new JArray(1, 2) } + }; + + JObject o2 = new JObject + { + { "Integer", 12345 }, + { "String", "A string" }, + { "Items", new JArray(1, 2) } + }; + var isEquals = JToken.DeepEquals(o1, o2); + Assert.IsTrue(isEquals); + } + + [TestMethod] + public void 比對兩個不一樣的JObject() + { + var o1 = new JObject + { + { "Integer", 12345 }, + { "String", "A string" }, + { "Items", new JArray(1, 2) } + }; + + var o2 = new JObject + { + { "integer", 12345 }, + { "String", "A string" }, + { "Items", new JArray(1, 2, new JArray { "a", "b" }) } + }; + var diffPath = new JsonDiffPatch(); + var diff = diffPath.Diff(o1, o2); + + if (diff != null) + { + Console.WriteLine(diff.ToString()); + } + + Assert.IsNotNull(diff); + } +} \ No newline at end of file diff --git a/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonTests.cs b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonDiffPathTests.cs similarity index 98% rename from Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonTests.cs rename to Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonDiffPathTests.cs index 5a15b6d0..c2074020 100644 --- a/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonTests.cs +++ b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonDiffPathTests.cs @@ -8,7 +8,7 @@ namespace Lab.JsonCompare.UnitTest; [TestClass] -public class SystemTextJsonTests +public class SystemTextJsonDiffPathTests { [TestMethod] public void 比對兩個一樣的Json字串_via_JsonDiffPatcher() From c136d927b6817855271a26d16d3ab997489685f1 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 14 May 2022 21:59:00 +0800 Subject: [PATCH 213/424] refactor --- .../NewtonsoftJsonDiffPathTests.cs | 12 ++-- .../SystemTextJsonDiffPathTests.cs | 59 +++++++++++++------ 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/NewtonsoftJsonDiffPathTests.cs b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/NewtonsoftJsonDiffPathTests.cs index 5585fd1a..4a12ad38 100644 --- a/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/NewtonsoftJsonDiffPathTests.cs +++ b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/NewtonsoftJsonDiffPathTests.cs @@ -11,41 +11,41 @@ public class NewtonsoftJsonDiffPathTests [TestMethod] public void 比對兩個一樣的JObject() { - JObject o1 = new JObject + var source = new JObject { { "Integer", 12345 }, { "String", "A string" }, { "Items", new JArray(1, 2) } }; - JObject o2 = new JObject + var dest = new JObject { { "Integer", 12345 }, { "String", "A string" }, { "Items", new JArray(1, 2) } }; - var isEquals = JToken.DeepEquals(o1, o2); + var isEquals = JToken.DeepEquals(source, dest); Assert.IsTrue(isEquals); } [TestMethod] public void 比對兩個不一樣的JObject() { - var o1 = new JObject + var source = new JObject { { "Integer", 12345 }, { "String", "A string" }, { "Items", new JArray(1, 2) } }; - var o2 = new JObject + var dest = new JObject { { "integer", 12345 }, { "String", "A string" }, { "Items", new JArray(1, 2, new JArray { "a", "b" }) } }; var diffPath = new JsonDiffPatch(); - var diff = diffPath.Diff(o1, o2); + var diff = diffPath.Diff(source, dest); if (diff != null) { diff --git a/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonDiffPathTests.cs b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonDiffPathTests.cs index c2074020..9218117e 100644 --- a/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonDiffPathTests.cs +++ b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonDiffPathTests.cs @@ -13,22 +13,22 @@ public class SystemTextJsonDiffPathTests [TestMethod] public void 比對兩個一樣的Json字串_via_JsonDiffPatcher() { - var o1 = new JsonObject + var source = new JsonObject { { "Integer", 12345 }, { "String", "A string" }, { "Items", new JsonArray(1, 2) } }; - var o2 = new JsonObject + var dest = new JsonObject { { "Integer", 12345 }, { "String", "A string" }, { "Items", new JsonArray(1, 2) } }; - var left = o1.ToJsonString(); - var right = o2.ToJsonString(); + var left = source.ToJsonString(); + var right = dest.ToJsonString(); var diff = JsonDiffPatcher.Diff(left, right); if (diff != null) { @@ -41,21 +41,21 @@ public void 比對兩個一樣的Json字串_via_JsonDiffPatcher() [TestMethod] public void 比對兩個不一樣的JsonObject() { - var o1 = new JsonObject + var source = new JsonObject { { "Integer", 12345 }, { "String", JsonValue.Create("A string") }, { "Items", new JsonArray(1, 2) } }; - var o2 = new JsonObject + var dest = new JsonObject { { "integer", 12345 }, { "String", "A string" }, { "Items", new JsonArray(1, 2, new JsonArray { "a", "b" }) } }; - var diff = o1.Diff(o2); + var diff = source.Diff(dest); if (diff != null) { Console.WriteLine(JsonSerializer.Serialize(diff)); @@ -67,22 +67,22 @@ public void 比對兩個不一樣的JsonObject() [TestMethod] public void 比對兩個不一樣的JsonNode() { - var o1 = new JsonObject + var source = new JsonObject { { "Integer", 12345 }, { "String", JsonValue.Create("A string") }, { "Items", new JsonArray(1, 2) } }; - var o2 = new JsonObject + var dest = new JsonObject { { "integer", 12345 }, { "String", "A string" }, { "Items", new JsonArray(1, 2, new JsonArray { "a", "b" }) } }; - var left = JsonNode.Parse(o1.ToJsonString()); - var right = JsonNode.Parse(o2.ToJsonString()); + var left = JsonNode.Parse(source.ToJsonString()); + var right = JsonNode.Parse(dest.ToJsonString()); var diff = left.Diff(right); if (diff != null) { @@ -95,43 +95,66 @@ public void 比對兩個不一樣的JsonNode() [TestMethod] public void 比對兩個不一樣的JsonObject_via_JsonAssert() { - var o1 = new JsonObject + var source = new JsonObject { { "Integer", 12345 }, { "String", JsonValue.Create("A string") }, { "Items", new JsonArray(1, 2) } }; - var o2 = new JsonObject + var dest = new JsonObject { { "integer", 12345 }, { "String", "A string" }, { "Items", new JsonArray(1, 2, new JsonArray { "a", "b" }) } }; - Assert.That.JsonAreEqual(o1, o2, true); + Assert.That.JsonAreEqual(source, dest, true); } [TestMethod] public void 比對兩個不一樣的JsonDocument() { - var o1 = new JsonObject + var source = new JsonObject { { "Integer", 12345 }, { "String", JsonValue.Create("A string") }, { "Items", new JsonArray(1, 2) } }; - var o2 = new JsonObject + var dest = new JsonObject { { "integer", 12345 }, { "String", "A string" }, { "Items", new JsonArray(1, 2, new JsonArray { "a", "b" }) } }; - var left = JsonDocument.Parse(o1.ToJsonString()); - var right = JsonDocument.Parse(o2.ToJsonString()); + var left = JsonDocument.Parse(source.ToJsonString()); + var right = JsonDocument.Parse(dest.ToJsonString()); var isEquals = left.DeepEquals(right); Assert.IsFalse(isEquals); } + + [TestMethod] + public void 更新節點() + { + var source = new JsonObject + { + { "Integer", 12345 }, + { "String", "A string" }, + { "Items", new JsonArray(1, 2, new JsonArray { "a", "b" }) } + }; + + var dest = new JsonObject + { + { "Integer", 12345 }, + { "String", "A string" }, + { "Items", new JsonArray(1, 2) } + }; + + var diff = source.Diff(dest); + JsonDiffPatcher.Patch(ref diff, dest); + + Assert.That.JsonAreEqual(source,dest); + } } \ No newline at end of file From d0895a8a2669b65e9d5f13671245e6e196858d90 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 15 May 2022 02:01:45 +0800 Subject: [PATCH 214/424] add json patch test case --- .../SystemTextJsonDiffPathTests.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonDiffPathTests.cs b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonDiffPathTests.cs index 9218117e..32259e38 100644 --- a/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonDiffPathTests.cs +++ b/Json/Lab.JsonCompare/Lab.JsonCompare.UnitTest/SystemTextJsonDiffPathTests.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Text.Json; using System.Text.Json.JsonDiffPatch; using System.Text.Json.JsonDiffPatch.MsTest; @@ -123,8 +125,8 @@ public void 比對兩個不一樣的JsonDocument() var dest = new JsonObject { - { "integer", 12345 }, - { "String", "A string" }, + { "Integer", 12345 }, + { "String", JsonValue.Create("A string") }, { "Items", new JsonArray(1, 2, new JsonArray { "a", "b" }) } }; @@ -151,10 +153,14 @@ public void 更新節點() { "String", "A string" }, { "Items", new JsonArray(1, 2) } }; + + var left = JsonNode.Parse(dest.ToJsonString()); + var right = JsonNode.Parse(source.ToJsonString()); + + //左邊不等於來源,跟我認知的不一樣 + var diff = left.Diff(right); + JsonDiffPatcher.Patch(ref left, diff); - var diff = source.Diff(dest); - JsonDiffPatcher.Patch(ref diff, dest); - - Assert.That.JsonAreEqual(source,dest); + Assert.That.JsonAreEqual(right, right, true); } } \ No newline at end of file From 07b74a6251c362b3752948a817f7d63d77cd35aa Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 22 May 2022 13:25:20 +0800 Subject: [PATCH 215/424] feat: add project --- ...sicAuthenticationSite.IntegrateTest.csproj | 21 +++++ .../TestServer.cs | 18 ++++ .../UnitTest1.cs | 19 ++++ .../Controllers/DemoController.cs | 22 +++++ .../Controllers/ProtectController.cs | 21 +++++ ...re.Security.BasicAuthenticationSite.csproj | 22 +++++ .../Program.cs | 30 +++++++ .../Properties/launchSettings.json | 31 +++++++ .../BasicAuthenticationDefaults.cs | 6 ++ .../BasicAuthenticationExtensions.cs | 39 ++++++++ .../BasicAuthenticationHandler.cs | 89 +++++++++++++++++++ .../BasicAuthenticationOptions.cs | 8 ++ ...BasicAuthenticationPostConfigureOptions.cs | 14 +++ .../BasicAuthenticationProvider.cs | 9 ++ .../IBasicAuthenticationProvider.cs | 6 ++ .../appsettings.Development.json | 8 ++ .../appsettings.json | 9 ++ .../Lab.AspNetCore.Security.sln | 22 +++++ 18 files changed, 394 insertions(+) create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/TestServer.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/ProtectController.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Properties/launchSettings.json create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/appsettings.Development.json create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/appsettings.json create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj new file mode 100644 index 00000000..90db6373 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/TestServer.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/TestServer.cs new file mode 100644 index 00000000..9330cf99 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/TestServer.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest; + +public class TestServer : WebApplicationFactory +{ + private void ConfigureServices(IServiceCollection services) + { + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureServices(this.ConfigureServices); + builder.UseSetting("https_port", "9527"); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs new file mode 100644 index 00000000..2ea722f9 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs @@ -0,0 +1,19 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void 訪問沒有授權的服務() + { + var server = new TestServer(); + var httpClient = server.CreateClient(); + var url = "demo"; + var response = httpClient.GetAsync(url).Result; + var result = response.Content.ReadAsStringAsync().Result; + Console.WriteLine(result); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs new file mode 100644 index 00000000..674df575 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Controllers; + +[ApiController] +[Route("[controller]")] +public class DemoController : ControllerBase +{ + private readonly ILogger _logger; + + public DemoController(ILogger logger) + { + this._logger = logger; + } + + public async Task Get() + { + this._logger.LogDebug("訪問沒有授權的端點"); + Console.WriteLine("AAAAA"); + return this.Ok(); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/ProtectController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/ProtectController.cs new file mode 100644 index 00000000..b679085a --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/ProtectController.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Controllers; + +[ApiController] +[Route("[controller]")] +public class ProtectController : ControllerBase +{ + private readonly ILogger _logger; + + public ProtectController(ILogger logger) + { + this._logger = logger; + } + + // [HttpGet(Name = "GetWeatherForecast")] + public async Task Get() + { + return this.Ok(); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj new file mode 100644 index 00000000..b4a1e621 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs new file mode 100644 index 00000000..bc7e56af --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs @@ -0,0 +1,30 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Logging.AddConsole(); +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); + +public partial class Program +{ +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Properties/launchSettings.json b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Properties/launchSettings.json new file mode 100644 index 00000000..4ed84fd8 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:20169", + "sslPort": 44329 + } + }, + "profiles": { + "Lab.AspNetCore.Security.BasicAuthenticationSite": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7089;http://localhost:5089", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs new file mode 100644 index 00000000..ccd37f3f --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs @@ -0,0 +1,6 @@ +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; + +public static class BasicAuthenticationDefaults +{ + public const string AuthenticationScheme = "Basic"; +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs new file mode 100644 index 00000000..d01902b6 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Options; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; + +public static class BasicAuthenticationExtensions +{ + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder) + where TAuthService : class, IBasicAuthenticationProvider + { + return AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, _ => { }); + } + + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, + string authenticationScheme) + where TAuthService : class, IBasicAuthenticationProvider + { + return AddBasic(builder, authenticationScheme, _ => { }); + } + + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, + Action configureOptions) + where TAuthService : class, IBasicAuthenticationProvider + { + return AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, configureOptions); + } + + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, + string authenticationScheme, Action configureOptions) + where TAuthService : class, IBasicAuthenticationProvider + { + builder.Services + .AddSingleton, BasicAuthenticationPostConfigureOptions>(); + builder.Services.AddTransient(); + + return builder.AddScheme( + authenticationScheme, configureOptions); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs new file mode 100644 index 00000000..1ae4fcae --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs @@ -0,0 +1,89 @@ +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Options; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; + +public class BasicAuthenticationHandler : AuthenticationHandler +{ + private const string AuthorizationHeaderName = "Authorization"; + private const string BasicSchemeName = "Basic"; + private readonly IBasicAuthenticationProvider _authenticationProvider; + + public BasicAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock, + IBasicAuthenticationProvider authenticationService) + : base(options, logger, encoder, clock) + { + this._authenticationProvider = authenticationService; + } + + protected override async Task HandleAuthenticateAsync() + { + if (!this.Request.Headers.ContainsKey(AuthorizationHeaderName)) + { + // return AuthenticateResult.Fail("Invalid Basic authentication header"); + //Authorization header not in request + return AuthenticateResult.NoResult(); + } + + if (!AuthenticationHeaderValue.TryParse(this.Request.Headers[AuthorizationHeaderName], + out var headerValue)) + { + //Invalid Authorization header + return AuthenticateResult.NoResult(); + } + + if (headerValue.Scheme.StartsWith(BasicSchemeName, StringComparison.InvariantCultureIgnoreCase) == false) + { + return AuthenticateResult.NoResult(); + } + + if (!BasicSchemeName.Equals(headerValue.Scheme, StringComparison.OrdinalIgnoreCase)) + { + //Not Basic authentication header + return AuthenticateResult.NoResult(); + } + + var headerValueBytes = Convert.FromBase64String(headerValue.Parameter); + var userAndPassword = Encoding.UTF8.GetString(headerValueBytes); + var parts = userAndPassword.Split(':'); + if (parts.Length != 2) + { + return AuthenticateResult.Fail("Invalid Basic authentication header"); + } + + var user = parts[0]; + var password = parts[1]; + + var isValidUser = await this._authenticationProvider.IsValidUserAsync(user, password); + + if (!isValidUser) + { + return AuthenticateResult.Fail("Invalid username or password"); + } + + return this.SignIn(user); + } + + protected override async Task HandleChallengeAsync(AuthenticationProperties properties) + { + this.Response.Headers["WWW-Authenticate"] = $"Basic realm=\"{this.Options.Realm}\", charset=\"UTF-8\""; + await base.HandleChallengeAsync(properties); + } + + private AuthenticateResult SignIn(string user) + { + var claims = new[] { new Claim(ClaimTypes.Name, user) }; + var identity = new ClaimsIdentity(claims, this.Scheme.Name); + var principal = new ClaimsPrincipal(identity); + var ticket = new AuthenticationTicket(principal, this.Scheme.Name); + return AuthenticateResult.Success(ticket); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs new file mode 100644 index 00000000..e40c62d7 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Authentication; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; + +public class BasicAuthenticationOptions : AuthenticationSchemeOptions +{ + public string Realm { get; set; } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs new file mode 100644 index 00000000..40c7a136 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Options; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; + +public class BasicAuthenticationPostConfigureOptions : IPostConfigureOptions +{ + public void PostConfigure(string name, BasicAuthenticationOptions options) + { + if (string.IsNullOrEmpty(options.Realm)) + { + throw new InvalidOperationException("Realm must be provided in options"); + } + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs new file mode 100644 index 00000000..eee8b35d --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs @@ -0,0 +1,9 @@ +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; + +public class BasicAuthenticationProvider : IBasicAuthenticationProvider +{ + public Task IsValidUserAsync(string user, string password) + { + return Task.FromResult(true); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs new file mode 100644 index 00000000..ef68a3a5 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs @@ -0,0 +1,6 @@ +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; + +public interface IBasicAuthenticationProvider +{ + Task IsValidUserAsync(string user, string password); +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/appsettings.Development.json b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/appsettings.json b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln new file mode 100644 index 00000000..1d518087 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AspNetCore.Security.BasicAuthenticationSite", "Lab.AspNetCore.Security.BasicAuthenticationSite\Lab.AspNetCore.Security.BasicAuthenticationSite.csproj", "{C2FC8F88-DAD8-4677-8C5A-A9353C5353D7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest", "Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest\Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj", "{13085C3E-F174-45D2-B8F7-3EE51D42DDF4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C2FC8F88-DAD8-4677-8C5A-A9353C5353D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2FC8F88-DAD8-4677-8C5A-A9353C5353D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2FC8F88-DAD8-4677-8C5A-A9353C5353D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2FC8F88-DAD8-4677-8C5A-A9353C5353D7}.Release|Any CPU.Build.0 = Release|Any CPU + {13085C3E-F174-45D2-B8F7-3EE51D42DDF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {13085C3E-F174-45D2-B8F7-3EE51D42DDF4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13085C3E-F174-45D2-B8F7-3EE51D42DDF4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {13085C3E-F174-45D2-B8F7-3EE51D42DDF4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From b67c3afd2f518d017b5d987f427d74851fc03f25 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 22 May 2022 19:15:17 +0800 Subject: [PATCH 216/424] feat: add test case --- .../TestServer.cs | 7 +++- .../UnitTest1.cs | 41 +++++++++++++++++++ .../Controllers/DemoController.cs | 4 +- .../Controllers/ProtectController.cs | 21 ---------- .../Controllers/User.cs | 8 ++++ .../Controllers/UserController.cs | 27 ++++++++++++ .../Program.cs | 9 ++++ .../BasicAuthenticationHandler.cs | 16 +++----- .../BasicAuthenticationOptions.cs | 2 +- .../BasicAuthenticationProvider.cs | 17 +++++++- .../IBasicAuthenticationProvider.cs | 2 +- 11 files changed, 114 insertions(+), 40 deletions(-) delete mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/ProtectController.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/User.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/UserController.cs diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/TestServer.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/TestServer.cs index 9330cf99..3ddc0f95 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/TestServer.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/TestServer.cs @@ -12,7 +12,10 @@ private void ConfigureServices(IServiceCollection services) protected override void ConfigureWebHost(IWebHostBuilder builder) { - builder.ConfigureServices(this.ConfigureServices); - builder.UseSetting("https_port", "9527"); + builder.ConfigureServices(this.ConfigureServices) + .UseSetting("https_port", "9527") + + // .UseUrls("https://localhost:9527") + ; } } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs index 2ea722f9..e4400bd3 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs @@ -1,4 +1,8 @@ using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest; @@ -16,4 +20,41 @@ public void 訪問沒有授權的服務() var result = response.Content.ReadAsStringAsync().Result; Console.WriteLine(result); } + + [TestMethod] + public void 訪問受保護的服務() + { + var server = new TestServer(); + var httpClient = server.CreateClient(); + var url = "user"; + var clientId = "YAO"; + var clientSecret = "9527"; + using var requestMessage = CreateBasicAuthenticationRequest(url, clientId, clientSecret); + var response = httpClient.SendAsync(requestMessage).Result; + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + } + + [TestMethod] + public void 訪問受保護的服務_驗證失敗() + { + var server = new TestServer(); + var httpClient = server.CreateClient(); + var url = "user"; + var clientId = "YAO1234"; + var clientSecret = "9527"; + using var requestMessage = CreateBasicAuthenticationRequest(url, clientId, clientSecret); + var response = httpClient.SendAsync(requestMessage).Result; + response.Headers.TryGetValues("WWW-Authenticate", out var result); + Console.WriteLine($"驗證失敗:{result}"); + Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode); + } + + private static HttpRequestMessage CreateBasicAuthenticationRequest(string url, string clientId, string clientSecret) + { + var requestMessage = new HttpRequestMessage(HttpMethod.Get, url); + var authenticationString = $"{clientId}:{clientSecret}"; + var base64Encoded = Convert.ToBase64String(Encoding.ASCII.GetBytes(authenticationString)); + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("basic", base64Encoded); + return requestMessage; + } } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs index 674df575..0a598033 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs @@ -15,8 +15,6 @@ public DemoController(ILogger logger) public async Task Get() { - this._logger.LogDebug("訪問沒有授權的端點"); - Console.WriteLine("AAAAA"); - return this.Ok(); + return this.Ok("好"); } } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/ProtectController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/ProtectController.cs deleted file mode 100644 index b679085a..00000000 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/ProtectController.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Controllers; - -[ApiController] -[Route("[controller]")] -public class ProtectController : ControllerBase -{ - private readonly ILogger _logger; - - public ProtectController(ILogger logger) - { - this._logger = logger; - } - - // [HttpGet(Name = "GetWeatherForecast")] - public async Task Get() - { - return this.Ok(); - } -} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/User.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/User.cs new file mode 100644 index 00000000..acde4a03 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/User.cs @@ -0,0 +1,8 @@ +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Controllers; + +internal class User +{ + public string Name { get; set; } + + public int Age { get; set; } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/UserController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/UserController.cs new file mode 100644 index 00000000..695aa74a --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/UserController.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Controllers; + +[ApiController] +[Route("[controller]")] +public class UserController : ControllerBase +{ + private readonly ILogger _logger; + + public UserController(ILogger logger) + { + this._logger = logger; + } + + [HttpGet] + [Authorize] + public async Task Get() + { + return this.Ok(new User + { + Name = this.User.Identity.Name, + Age = 18 + }); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs index bc7e56af..f6fb3d9d 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs @@ -1,3 +1,5 @@ +using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; + var builder = WebApplication.CreateBuilder(args); // Add services to the container. @@ -8,6 +10,12 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Logging.AddConsole(); +builder.Services.AddAuthentication("Basic") + .AddScheme("Basic", null); +builder.Services.AddSingleton(); + +// builder.Services.AddSingleton(); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -19,6 +27,7 @@ app.UseHttpsRedirection(); +app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs index 1ae4fcae..b10e244c 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs @@ -10,7 +10,7 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authenticatio public class BasicAuthenticationHandler : AuthenticationHandler { private const string AuthorizationHeaderName = "Authorization"; - private const string BasicSchemeName = "Basic"; + private const string BasicSchemeName = BasicAuthenticationDefaults.AuthenticationScheme; private readonly IBasicAuthenticationProvider _authenticationProvider; public BasicAuthenticationHandler( @@ -18,10 +18,10 @@ public BasicAuthenticationHandler( ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, - IBasicAuthenticationProvider authenticationService) + IBasicAuthenticationProvider authenticationProvider) : base(options, logger, encoder, clock) { - this._authenticationProvider = authenticationService; + this._authenticationProvider = authenticationProvider; } protected override async Task HandleAuthenticateAsync() @@ -45,12 +45,6 @@ protected override async Task HandleAuthenticateAsync() return AuthenticateResult.NoResult(); } - if (!BasicSchemeName.Equals(headerValue.Scheme, StringComparison.OrdinalIgnoreCase)) - { - //Not Basic authentication header - return AuthenticateResult.NoResult(); - } - var headerValueBytes = Convert.FromBase64String(headerValue.Parameter); var userAndPassword = Encoding.UTF8.GetString(headerValueBytes); var parts = userAndPassword.Split(':'); @@ -62,9 +56,9 @@ protected override async Task HandleAuthenticateAsync() var user = parts[0]; var password = parts[1]; - var isValidUser = await this._authenticationProvider.IsValidUserAsync(user, password); + var isValidate = await this._authenticationProvider.IsValidateAsync(user, password, CancellationToken.None); - if (!isValidUser) + if (!isValidate) { return AuthenticateResult.Fail("Invalid username or password"); } diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs index e40c62d7..735e435f 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs @@ -4,5 +4,5 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authenticatio public class BasicAuthenticationOptions : AuthenticationSchemeOptions { - public string Realm { get; set; } + public string Realm { get; set; } = "Demo Site"; } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs index eee8b35d..38de0579 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs @@ -2,8 +2,23 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authenticatio public class BasicAuthenticationProvider : IBasicAuthenticationProvider { - public Task IsValidUserAsync(string user, string password) + private readonly Dictionary _clientIdentities = new(StringComparer.InvariantCultureIgnoreCase) { + { "yao", "9527" } + }; + + public Task IsValidateAsync(string user, string password, CancellationToken cancel = default) + { + if (this._clientIdentities.TryGetValue(user, out var secret) == false) + { + return Task.FromResult(false); + } + + if (password != secret) + { + return Task.FromResult(false); + } + return Task.FromResult(true); } } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs index ef68a3a5..a5c4d00a 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs @@ -2,5 +2,5 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authenticatio public interface IBasicAuthenticationProvider { - Task IsValidUserAsync(string user, string password); + Task IsValidateAsync(string user, string password, CancellationToken cancel); } \ No newline at end of file From 99627a32ee217cb077d0e6109e93291cf4a2dd56 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 22 May 2022 20:19:15 +0800 Subject: [PATCH 217/424] =?UTF-8?q?feat:=20=E8=A8=AA=E5=95=8F=E4=B8=8D?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E6=8E=88=E6=AC=8A=E7=9A=84=E6=9C=8D=E5=8B=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UnitTest1.cs | 4 +- .../Controllers/DemoController.cs | 2 + .../Program.cs | 4 +- .../BasicAuthenticationHandler.cs | 41 +++++++++++-------- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs index e4400bd3..fcd14617 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs @@ -11,7 +11,7 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest; public class UnitTest1 { [TestMethod] - public void 訪問沒有授權的服務() + public void 訪問不需要授權的服務() { var server = new TestServer(); var httpClient = server.CreateClient(); @@ -19,6 +19,8 @@ public void 訪問沒有授權的服務() var response = httpClient.GetAsync(url).Result; var result = response.Content.ReadAsStringAsync().Result; Console.WriteLine(result); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + } [TestMethod] diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs index 0a598033..f08acf9e 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Controllers; @@ -13,6 +14,7 @@ public DemoController(ILogger logger) this._logger = logger; } + [AllowAnonymous] public async Task Get() { return this.Ok("好"); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs index f6fb3d9d..2616f652 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs @@ -10,8 +10,8 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Logging.AddConsole(); -builder.Services.AddAuthentication("Basic") - .AddScheme("Basic", null); +builder.Services.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme) + .AddScheme(BasicAuthenticationDefaults.AuthenticationScheme, null); builder.Services.AddSingleton(); // builder.Services.AddSingleton(); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs index b10e244c..43f09cdf 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs @@ -3,6 +3,7 @@ using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Options; namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; @@ -10,7 +11,6 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authenticatio public class BasicAuthenticationHandler : AuthenticationHandler { private const string AuthorizationHeaderName = "Authorization"; - private const string BasicSchemeName = BasicAuthenticationDefaults.AuthenticationScheme; private readonly IBasicAuthenticationProvider _authenticationProvider; public BasicAuthenticationHandler( @@ -26,35 +26,39 @@ public BasicAuthenticationHandler( protected override async Task HandleAuthenticateAsync() { - if (!this.Request.Headers.ContainsKey(AuthorizationHeaderName)) + var schemeName = this.Scheme.Name; //由外部注入 + var endpoint = this.Context.GetEndpoint(); + if (endpoint?.Metadata?.GetMetadata() != null) { - // return AuthenticateResult.Fail("Invalid Basic authentication header"); - //Authorization header not in request return AuthenticateResult.NoResult(); } + if (!this.Request.Headers.ContainsKey(AuthorizationHeaderName)) + { + return AuthenticateResult.Fail("Invalid basic authentication header"); + } + if (!AuthenticationHeaderValue.TryParse(this.Request.Headers[AuthorizationHeaderName], - out var headerValue)) + out var authHeaderValue)) { - //Invalid Authorization header - return AuthenticateResult.NoResult(); + return AuthenticateResult.Fail("Invalid authorization Header"); } - if (headerValue.Scheme.StartsWith(BasicSchemeName, StringComparison.InvariantCultureIgnoreCase) == false) + if (authHeaderValue.Scheme.StartsWith(schemeName, StringComparison.InvariantCultureIgnoreCase) == false) { - return AuthenticateResult.NoResult(); + return AuthenticateResult.Fail("Invalid authorization scheme name"); } - var headerValueBytes = Convert.FromBase64String(headerValue.Parameter); - var userAndPassword = Encoding.UTF8.GetString(headerValueBytes); - var parts = userAndPassword.Split(':'); - if (parts.Length != 2) + var credentialBytes = Convert.FromBase64String(authHeaderValue.Parameter); + var userAndPassword = Encoding.UTF8.GetString(credentialBytes); + var credentials = userAndPassword.Split(':'); + if (credentials.Length != 2) { - return AuthenticateResult.Fail("Invalid Basic authentication header"); + return AuthenticateResult.Fail("Invalid basic authentication header"); } - var user = parts[0]; - var password = parts[1]; + var user = credentials[0]; + var password = credentials[1]; var isValidate = await this._authenticationProvider.IsValidateAsync(user, password, CancellationToken.None); @@ -74,10 +78,11 @@ protected override async Task HandleChallengeAsync(AuthenticationProperties prop private AuthenticateResult SignIn(string user) { + var schemeName = this.Scheme.Name; var claims = new[] { new Claim(ClaimTypes.Name, user) }; - var identity = new ClaimsIdentity(claims, this.Scheme.Name); + var identity = new ClaimsIdentity(claims, schemeName); var principal = new ClaimsPrincipal(identity); - var ticket = new AuthenticationTicket(principal, this.Scheme.Name); + var ticket = new AuthenticationTicket(principal, schemeName); return AuthenticateResult.Success(ticket); } } \ No newline at end of file From 47245684d9dfa536d77adc90a7461716aa855c49 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 22 May 2022 22:38:05 +0800 Subject: [PATCH 218/424] refactor --- .../UnitTest1.cs | 5 ++-- .../Program.cs | 6 +++-- .../BasicAuthenticationExtensions.cs | 25 +++++++++---------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs index fcd14617..ae869cf0 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -46,8 +47,8 @@ public void 訪問受保護的服務_驗證失敗() var clientSecret = "9527"; using var requestMessage = CreateBasicAuthenticationRequest(url, clientId, clientSecret); var response = httpClient.SendAsync(requestMessage).Result; - response.Headers.TryGetValues("WWW-Authenticate", out var result); - Console.WriteLine($"驗證失敗:{result}"); + response.Headers.TryGetValues("WWW-Authenticate", out var values); + Console.WriteLine($"驗證失敗:{values.First()}"); Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode); } diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs index 2616f652..2743337e 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs @@ -10,11 +10,13 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Logging.AddConsole(); + builder.Services.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme) - .AddScheme(BasicAuthenticationDefaults.AuthenticationScheme, null); + .AddScheme(BasicAuthenticationDefaults.AuthenticationScheme, + p => new BasicAuthenticationOptions()); builder.Services.AddSingleton(); -// builder.Services.AddSingleton(); +// builder.Services.AddBasicAuthentication(); var app = builder.Build(); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs index d01902b6..bfb37867 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs @@ -5,25 +5,24 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authenticatio public static class BasicAuthenticationExtensions { - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder) + public static AuthenticationBuilder AddBasicAuthentication(this IServiceCollection services) where TAuthService : class, IBasicAuthenticationProvider - { - return AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, _ => { }); - } + => services.AddAuthentication(o => o.DefaultScheme = BasicAuthenticationDefaults.AuthenticationScheme) + .AddBasic(); + + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder) + where TAuthService : class, IBasicAuthenticationProvider => + AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, _ => { }); public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme) - where TAuthService : class, IBasicAuthenticationProvider - { - return AddBasic(builder, authenticationScheme, _ => { }); - } + where TAuthService : class, IBasicAuthenticationProvider => + AddBasic(builder, authenticationScheme, _ => { }); public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, Action configureOptions) - where TAuthService : class, IBasicAuthenticationProvider - { - return AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, configureOptions); - } + where TAuthService : class, IBasicAuthenticationProvider => + AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, configureOptions); public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) @@ -31,7 +30,7 @@ public static AuthenticationBuilder AddBasic(this AuthenticationBu { builder.Services .AddSingleton, BasicAuthenticationPostConfigureOptions>(); - builder.Services.AddTransient(); + builder.Services.AddSingleton(); return builder.AddScheme( authenticationScheme, configureOptions); From 15c3376487ee8827c1ec73886a6ca80172bdc270 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 22 May 2022 23:08:06 +0800 Subject: [PATCH 219/424] refactor --- .../UnitTest1.cs | 1 - .../BasicAuthenticationExtensions.cs | 30 ++++++++++++------- .../BasicAuthenticationHandler.cs | 19 ++++++++---- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs index ae869cf0..4166dea8 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs @@ -21,7 +21,6 @@ public void 訪問不需要授權的服務() var result = response.Content.ReadAsStringAsync().Result; Console.WriteLine(result); Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - } [TestMethod] diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs index bfb37867..76845c24 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs @@ -5,24 +5,25 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authenticatio public static class BasicAuthenticationExtensions { - public static AuthenticationBuilder AddBasicAuthentication(this IServiceCollection services) - where TAuthService : class, IBasicAuthenticationProvider - => services.AddAuthentication(o => o.DefaultScheme = BasicAuthenticationDefaults.AuthenticationScheme) - .AddBasic(); - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder) - where TAuthService : class, IBasicAuthenticationProvider => - AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, _ => { }); + where TAuthService : class, IBasicAuthenticationProvider + { + return AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, _ => { }); + } public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme) - where TAuthService : class, IBasicAuthenticationProvider => - AddBasic(builder, authenticationScheme, _ => { }); + where TAuthService : class, IBasicAuthenticationProvider + { + return AddBasic(builder, authenticationScheme, _ => { }); + } public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, Action configureOptions) - where TAuthService : class, IBasicAuthenticationProvider => - AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, configureOptions); + where TAuthService : class, IBasicAuthenticationProvider + { + return AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, configureOptions); + } public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) @@ -35,4 +36,11 @@ public static AuthenticationBuilder AddBasic(this AuthenticationBu return builder.AddScheme( authenticationScheme, configureOptions); } + + public static AuthenticationBuilder AddBasicAuthentication(this IServiceCollection services) + where TAuthService : class, IBasicAuthenticationProvider + { + return services.AddAuthentication(o => o.DefaultScheme = BasicAuthenticationDefaults.AuthenticationScheme) + .AddBasic(); + } } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs index 43f09cdf..bee997de 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs @@ -4,6 +4,7 @@ using System.Text.Encodings.Web; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Options; namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; @@ -12,6 +13,7 @@ public class BasicAuthenticationHandler : AuthenticationHandler options, @@ -35,17 +37,20 @@ protected override async Task HandleAuthenticateAsync() if (!this.Request.Headers.ContainsKey(AuthorizationHeaderName)) { - return AuthenticateResult.Fail("Invalid basic authentication header"); + this._failReason = "Invalid basic authentication header"; + return AuthenticateResult.Fail(this._failReason); } if (!AuthenticationHeaderValue.TryParse(this.Request.Headers[AuthorizationHeaderName], out var authHeaderValue)) { - return AuthenticateResult.Fail("Invalid authorization Header"); + this._failReason = "Invalid authorization Header"; + return AuthenticateResult.Fail(this._failReason); } if (authHeaderValue.Scheme.StartsWith(schemeName, StringComparison.InvariantCultureIgnoreCase) == false) { + this._failReason = "Invalid authorization scheme name"; return AuthenticateResult.Fail("Invalid authorization scheme name"); } @@ -54,7 +59,8 @@ protected override async Task HandleAuthenticateAsync() var credentials = userAndPassword.Split(':'); if (credentials.Length != 2) { - return AuthenticateResult.Fail("Invalid basic authentication header"); + this._failReason = "Invalid basic authentication header"; + return AuthenticateResult.Fail(this._failReason); } var user = credentials[0]; @@ -64,7 +70,8 @@ protected override async Task HandleAuthenticateAsync() if (!isValidate) { - return AuthenticateResult.Fail("Invalid username or password"); + this._failReason = "Invalid username or password"; + return AuthenticateResult.Fail(this._failReason); } return this.SignIn(user); @@ -72,8 +79,10 @@ protected override async Task HandleAuthenticateAsync() protected override async Task HandleChallengeAsync(AuthenticationProperties properties) { + this.Response.StatusCode = 401; + this.Response.HttpContext.Features.Get().ReasonPhrase = this._failReason; this.Response.Headers["WWW-Authenticate"] = $"Basic realm=\"{this.Options.Realm}\", charset=\"UTF-8\""; - await base.HandleChallengeAsync(properties); + await Task.CompletedTask; } private AuthenticateResult SignIn(string user) From 25a093e959acd1cb60b9775e8d0f54d2c90a32cb Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 22 May 2022 23:21:02 +0800 Subject: [PATCH 220/424] refactor --- .../Controllers/DemoController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs index f08acf9e..65298d94 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs @@ -15,6 +15,7 @@ public DemoController(ILogger logger) } [AllowAnonymous] + [HttpGet] public async Task Get() { return this.Ok("好"); From 57534f33183b7062becb383210d8f5d75ac25554 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 16 Jun 2022 13:47:22 +0800 Subject: [PATCH 221/424] feat: add project --- .../Lab.AspNetCoreMiddleware.UnitTest.csproj | 23 ++++ .../ValidateRequireHeaderMiddlewareTests.cs | 106 +++++++++++++++++ .../ValidateRequireHeaderMiddlewareTests2.cs | 109 ++++++++++++++++++ .../Lab.AspNetCoreMiddleware.Web.csproj | 9 ++ .../Lab.AspNetCoreMiddleware.Web/Program.cs | 7 ++ .../Properties/launchSettings.json | 28 +++++ .../appsettings.Development.json | 8 ++ .../appsettings.json | 9 ++ .../Lab.AspNetCoreMiddleware.sln | 28 +++++ .../Lab.AspNetCoreMiddleware/Failure.cs | 8 ++ .../Lab.AspNetCoreMiddleware/FailureCode.cs | 8 ++ .../Lab.AspNetCoreMiddleware/FailureResult.cs | 12 ++ .../Lab.AspNetCoreMiddleware/HeaderNames.cs | 7 ++ .../Lab.AspNetCoreMiddleware.csproj | 14 +++ .../ValidateRequiredHeaderMiddleware.cs | 71 ++++++++++++ 15 files changed, 447 insertions(+) create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/Lab.AspNetCoreMiddleware.UnitTest.csproj create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/ValidateRequireHeaderMiddlewareTests.cs create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/ValidateRequireHeaderMiddlewareTests2.cs create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/Lab.AspNetCoreMiddleware.Web.csproj create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/Program.cs create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/Properties/launchSettings.json create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/appsettings.Development.json create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/appsettings.json create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.sln create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/Failure.cs create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/FailureCode.cs create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/FailureResult.cs create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/HeaderNames.cs create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.csproj create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/ValidateRequiredHeaderMiddleware.cs diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/Lab.AspNetCoreMiddleware.UnitTest.csproj b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/Lab.AspNetCoreMiddleware.UnitTest.csproj new file mode 100644 index 00000000..02fadc0b --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/Lab.AspNetCoreMiddleware.UnitTest.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + + + diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/ValidateRequireHeaderMiddlewareTests.cs b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/ValidateRequireHeaderMiddlewareTests.cs new file mode 100644 index 00000000..fd44b1bd --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/ValidateRequireHeaderMiddlewareTests.cs @@ -0,0 +1,106 @@ +using System; +using System.IO; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.JsonDiffPatch.MsTest; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.AspNetCoreMiddleware.UnitTest; + +[TestClass] +public class ValidateRequireHeaderMiddlewareTests +{ + [TestMethod] + public async Task HeaderCode型別錯誤會驗證失敗() + { + var expected = @" +{ + ""code"": ""INVALID_REQUEST"", + ""messages"": [ + { + ""code"": ""INVALID_TYPE"", + ""propertyName"": ""X-Code"", + ""messages"": ""'abc' not numbers"", + ""value"": ""abc"" + } + ] +} +"; + using var testServer = await CreateTestServer(); + var httpContext = await testServer.SendAsync(context => + { + context.Request.Headers[HeaderNames.UserId] = "yao"; + context.Request.Headers[HeaderNames.Code] = "abc"; + }); + var response = httpContext.Response; + var stream = response.Body; + + var actual = await new StreamReader(stream).ReadToEndAsync(); + Assert.That.JsonAreEqual(expected, actual, true); + } + + [TestMethod] + public async Task 所有Header為空會驗證失敗() + { + var expected = @" +{ + ""code"": ""INVALID_REQUEST"", + ""messages"": [ + { + ""code"": ""INVALID_FORMAT"", + ""propertyName"": ""X-User-Id"", + ""messages"": ""The 'X-User-Id' header is required."" + }, + { + ""code"": ""INVALID_FORMAT"", + ""propertyName"": ""X-Code"", + ""messages"": ""The 'X-Code' header is required."" + } + ] +} +"; + using var testServer = await CreateTestServer(); + var httpContext = await testServer.SendAsync(context => { }); + var response = httpContext.Response; + var stream = response.Body; + + var actual = await new StreamReader(stream).ReadToEndAsync(); + Assert.That.JsonAreEqual(expected, actual, true); + } + + private static async Task CreateTestServer() + { + var host = await new HostBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseTestServer() + .ConfigureServices( + services => + { + services.AddSingleton(p => new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = false, + + // Encoder = JavaScriptEncoder.Create(UnicodeRanges.All, UnicodeRanges.All), + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }); + }) + .Configure(app => { app.UseMiddleware(); }); + }) + .StartAsync(); + + var server = host.GetTestServer(); + server.BaseAddress = new Uri("https://我真的是假的/不要打我的臉/"); + return server; + } +} \ No newline at end of file diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/ValidateRequireHeaderMiddlewareTests2.cs b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/ValidateRequireHeaderMiddlewareTests2.cs new file mode 100644 index 00000000..aa36cda6 --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/ValidateRequireHeaderMiddlewareTests2.cs @@ -0,0 +1,109 @@ +using System; +using System.IO; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.JsonDiffPatch.MsTest; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.AspNetCoreMiddleware.UnitTest; + +[TestClass] +public class ValidateRequireHeaderMiddlewareTests2 +{ + [TestMethod] + public async Task HeaderCode型別錯誤會驗證失敗() + { + var expected = @" +{ + ""code"": ""INVALID_REQUEST"", + ""messages"": [ + { + ""code"": ""INVALID_TYPE"", + ""propertyName"": ""X-Code"", + ""messages"": ""'abc' not numbers"", + ""value"": ""abc"" + } + ] +} +"; + var jsonSerializerOptions = CreateJsonSerializerOptions(); + var httpContext = new DefaultHttpContext() + { + Response = { Body = new MemoryStream()} + }; + httpContext.Request.Headers[HeaderNames.UserId] = "yao"; + httpContext.Request.Headers[HeaderNames.Code] = "abc"; + var target = new ValidateRequiredHeaderMiddleware((_) => Task.CompletedTask); + await target.InvokeAsync(httpContext, jsonSerializerOptions); + var response = httpContext.Response; + var stream = response.Body; + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + var actual = await new StreamReader(stream).ReadToEndAsync(); + Assert.That.JsonAreEqual(expected, actual, true); + } + + [TestMethod] + public async Task 所有Header為空會驗證失敗() + { + var expected = @" +{ + ""code"": ""INVALID_REQUEST"", + ""messages"": [ + { + ""code"": ""INVALID_FORMAT"", + ""propertyName"": ""X-User-Id"", + ""messages"": ""The 'X-User-Id' header is required."" + }, + { + ""code"": ""INVALID_FORMAT"", + ""propertyName"": ""X-Code"", + ""messages"": ""The 'X-Code' header is required."" + } + ] +} +"; + var jsonSerializerOptions = CreateJsonSerializerOptions(); + var httpContext = new DefaultHttpContext + { + Response = { Body = new MemoryStream() } + }; + + var target = new ValidateRequiredHeaderMiddleware(_ => Task.CompletedTask); + await target.InvokeAsync(httpContext, jsonSerializerOptions); + var response = httpContext.Response; + var stream = response.Body; + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + var actual = await new StreamReader(stream).ReadToEndAsync(); + Assert.That.JsonAreEqual(expected, actual, true); + } + + private static JsonSerializerOptions CreateJsonSerializerOptions() + { + return new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = false, + + // Encoder = JavaScriptEncoder.Create(UnicodeRanges.All, UnicodeRanges.All), + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + } +} \ No newline at end of file diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/Lab.AspNetCoreMiddleware.Web.csproj b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/Lab.AspNetCoreMiddleware.Web.csproj new file mode 100644 index 00000000..d52487bf --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/Lab.AspNetCoreMiddleware.Web.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/Program.cs b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/Program.cs new file mode 100644 index 00000000..d61665b8 --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/Program.cs @@ -0,0 +1,7 @@ +var builder = WebApplication.CreateBuilder(args); +var app = builder.Build(); + +app.MapGet("/", () => "Hello World!"); + +app.Run(); +public partial class Program { } \ No newline at end of file diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/Properties/launchSettings.json b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/Properties/launchSettings.json new file mode 100644 index 00000000..e1658380 --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:55168", + "sslPort": 44383 + } + }, + "profiles": { + "Lab.AspNetCoreMiddleware.Web": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7251;http://localhost:5251", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/appsettings.Development.json b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/appsettings.json b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.Web/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.sln b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.sln new file mode 100644 index 00000000..e2a0a451 --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AspNetCoreMiddleware", "Lab.AspNetCoreMiddleware\Lab.AspNetCoreMiddleware.csproj", "{FA33EE2C-5954-457C-9A00-ED3FDD38010C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AspNetCoreMiddleware.UnitTest", "Lab.AspNetCoreMiddleware.UnitTest\Lab.AspNetCoreMiddleware.UnitTest.csproj", "{FAB4FE0D-4232-46EA-A0A4-038D419E208F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AspNetCoreMiddleware.Web", "Lab.AspNetCoreMiddleware.Web\Lab.AspNetCoreMiddleware.Web.csproj", "{93EB2B91-FE4B-4A90-B517-8A959AD0BC40}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FA33EE2C-5954-457C-9A00-ED3FDD38010C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA33EE2C-5954-457C-9A00-ED3FDD38010C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA33EE2C-5954-457C-9A00-ED3FDD38010C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA33EE2C-5954-457C-9A00-ED3FDD38010C}.Release|Any CPU.Build.0 = Release|Any CPU + {FAB4FE0D-4232-46EA-A0A4-038D419E208F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FAB4FE0D-4232-46EA-A0A4-038D419E208F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FAB4FE0D-4232-46EA-A0A4-038D419E208F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FAB4FE0D-4232-46EA-A0A4-038D419E208F}.Release|Any CPU.Build.0 = Release|Any CPU + {93EB2B91-FE4B-4A90-B517-8A959AD0BC40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93EB2B91-FE4B-4A90-B517-8A959AD0BC40}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93EB2B91-FE4B-4A90-B517-8A959AD0BC40}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93EB2B91-FE4B-4A90-B517-8A959AD0BC40}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/Failure.cs b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/Failure.cs new file mode 100644 index 00000000..24909aa8 --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/Failure.cs @@ -0,0 +1,8 @@ +namespace Lab.AspNetCoreMiddleware; + +internal class Failure +{ + public string Code { get; init; } + + public IEnumerable Messages { get; init; } +} \ No newline at end of file diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/FailureCode.cs b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/FailureCode.cs new file mode 100644 index 00000000..aa0463a7 --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/FailureCode.cs @@ -0,0 +1,8 @@ +namespace Lab.AspNetCoreMiddleware; + +enum FailureCode +{ + INVALID_FORMAT, + INVALID_REQUEST, + INVALID_TYPE +} \ No newline at end of file diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/FailureResult.cs b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/FailureResult.cs new file mode 100644 index 00000000..6c659c4d --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/FailureResult.cs @@ -0,0 +1,12 @@ +namespace Lab.AspNetCoreMiddleware; + +internal class FailureResult +{ + public string Code { get; init; } + + public string PropertyName { get; init; } + + public string Messages { get; init; } + + public string Value { get; init ; } +} \ No newline at end of file diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/HeaderNames.cs b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/HeaderNames.cs new file mode 100644 index 00000000..b3a8dd66 --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/HeaderNames.cs @@ -0,0 +1,7 @@ +namespace Lab.AspNetCoreMiddleware; + +public class HeaderNames +{ + public static string UserId = "X-User-Id"; + public static string Code = "X-Code"; +} \ No newline at end of file diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.csproj b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.csproj new file mode 100644 index 00000000..76bd7e85 --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + + + + + + + + diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/ValidateRequiredHeaderMiddleware.cs b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/ValidateRequiredHeaderMiddleware.cs new file mode 100644 index 00000000..7b8b594b --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/ValidateRequiredHeaderMiddleware.cs @@ -0,0 +1,71 @@ +using System.Text; +using System.Text.Json; +using Microsoft.AspNetCore.Http; + +namespace Lab.AspNetCoreMiddleware; + +public class ValidateRequiredHeaderMiddleware +{ + private readonly string[] _requireHeaderNames = + { + HeaderNames.UserId, + HeaderNames.Code, + }; + + private readonly RequestDelegate _next; + + public ValidateRequiredHeaderMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context, + JsonSerializerOptions jsonSerializerOptions) + { + var failureResults = new List(); + foreach (var name in this._requireHeaderNames) + { + if (context.Request.Headers.TryGetValue(name, out var value) == false) + { + failureResults.Add(new FailureResult + { + Code = FailureCode.INVALID_FORMAT.ToString(), + PropertyName = name, + Messages = $"The '{name}' header is required.", + }); + } + else + { + if (name == HeaderNames.Code) + { + if (long.TryParse(value, out var code) == false) + { + failureResults.Add(new FailureResult + { + Code = FailureCode.INVALID_TYPE.ToString(), + PropertyName = name, + Value = value, + Messages = $"'{value}' not numbers", + }); + } + } + } + } + + if (failureResults.Count > 0) + { + var failure = new Failure + { + Code = FailureCode.INVALID_REQUEST.ToString(), + Messages = failureResults + }; + var failureJson = JsonSerializer.Serialize(failure, jsonSerializerOptions); + context.Response.StatusCode = 400; + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(failureJson, Encoding.UTF8, context.RequestAborted); + return; + } + + await this._next(context); + } +} \ No newline at end of file From aee7db6b03216db659c4e76d23a1032074ce4ea1 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 16 Jun 2022 15:33:55 +0800 Subject: [PATCH 222/424] refactor --- .../Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.csproj b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.csproj index 76bd7e85..2a1c792b 100644 --- a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.csproj +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.csproj @@ -7,7 +7,7 @@ - + From ad7760d49ac7456cf908784291ae27d255c17ec2 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 16 Jun 2022 16:14:06 +0800 Subject: [PATCH 223/424] add test client test case --- .../Lab.AspNetCoreMiddleware.UnitTest.csproj | 14 +-- .../ValidateRequireHeaderMiddlewareTests1.cs | 98 +++++++++++++++++++ 2 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/ValidateRequireHeaderMiddlewareTests1.cs diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/Lab.AspNetCoreMiddleware.UnitTest.csproj b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/Lab.AspNetCoreMiddleware.UnitTest.csproj index 02fadc0b..3b992dbd 100644 --- a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/Lab.AspNetCoreMiddleware.UnitTest.csproj +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/Lab.AspNetCoreMiddleware.UnitTest.csproj @@ -8,16 +8,16 @@ - - - - - - + + + + + + - + diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/ValidateRequireHeaderMiddlewareTests1.cs b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/ValidateRequireHeaderMiddlewareTests1.cs new file mode 100644 index 00000000..61530a38 --- /dev/null +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/ValidateRequireHeaderMiddlewareTests1.cs @@ -0,0 +1,98 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.JsonDiffPatch.MsTest; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.AspNetCoreMiddleware.UnitTest; + +[TestClass] +public class ValidateRequireHeaderMiddlewareTests1 +{ + [TestMethod] + public async Task HeaderCode型別錯誤會驗證失敗() + { + var expected = @" +{ + ""code"": ""INVALID_REQUEST"", + ""messages"": [ + { + ""code"": ""INVALID_TYPE"", + ""propertyName"": ""X-Code"", + ""messages"": ""'abc' not numbers"", + ""value"": ""abc"" + } + ] +} +"; + using var httpClient = await CreateTestClient(); + var request = new HttpRequestMessage(HttpMethod.Get, "/青菜"); + request.Headers.Add(HeaderNames.UserId, "yao"); + request.Headers.Add(HeaderNames.Code, "abc"); + var response = await httpClient.SendAsync(request); + var actual = await response.Content.ReadAsStringAsync(); + Assert.That.JsonAreEqual(expected, actual, true); + } + + [TestMethod] + public async Task 所有Header為空會驗證失敗() + { + var expected = @" +{ + ""code"": ""INVALID_REQUEST"", + ""messages"": [ + { + ""code"": ""INVALID_FORMAT"", + ""propertyName"": ""X-User-Id"", + ""messages"": ""The 'X-User-Id' header is required."" + }, + { + ""code"": ""INVALID_FORMAT"", + ""propertyName"": ""X-Code"", + ""messages"": ""The 'X-Code' header is required."" + } + ] +} +"; + using var httpClient = await CreateTestClient(); + var request = new HttpRequestMessage(HttpMethod.Get, "/青菜"); + var response = await httpClient.SendAsync(request); + var actual = await response.Content.ReadAsStringAsync(); + Assert.That.JsonAreEqual(expected, actual, true); + } + + private static async Task CreateTestClient() + { + var host = await new HostBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseTestServer() + .ConfigureServices( + services => + { + services.AddSingleton(p => new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = false, + + // Encoder = JavaScriptEncoder.Create(UnicodeRanges.All, UnicodeRanges.All), + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }); + }) + .Configure(app => { app.UseMiddleware(); }); + }) + .StartAsync(); + return host.GetTestClient(); + } +} \ No newline at end of file From 179f1a5a95ae11cb4125ffba88f745a710700d8d Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 17 Jun 2022 19:19:01 +0800 Subject: [PATCH 224/424] refactor --- .../Lab.AspNetCoreMiddleware.UnitTest.csproj | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/Lab.AspNetCoreMiddleware.UnitTest.csproj b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/Lab.AspNetCoreMiddleware.UnitTest.csproj index 3b992dbd..37e06a28 100644 --- a/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/Lab.AspNetCoreMiddleware.UnitTest.csproj +++ b/Test/Lab.AspNetCoreMiddleware/Lab.AspNetCoreMiddleware.UnitTest/Lab.AspNetCoreMiddleware.UnitTest.csproj @@ -8,16 +8,16 @@ - - - - - - + + + + + + - + From 1edfd775e28110790bfbf1d8fb4d069478b54624 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 18 Jun 2022 23:02:04 +0800 Subject: [PATCH 225/424] feat: add unit test --- ...64\345\220\210\346\270\254\350\251\246.cs" | 2 +- .../Controllers/TestController.cs | 25 ++++ ...56\345\205\203\346\270\254\350\251\246.cs" | 116 ++++++++++++++++++ ...ty.BasicAuthenticationSite.UnitTest.csproj | 22 ++++ .../Program.cs | 10 +- .../BasicAuthenticationExtensions.cs | 43 ++----- .../BasicAuthenticationHandler.cs | 1 + .../DefaultBasicAuthenticationProvider.cs | 9 ++ .../Lab.AspNetCore.Security.sln | 6 + 9 files changed, 196 insertions(+), 38 deletions(-) rename WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs => "WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/BasicAuthenticationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" (97%) create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/TestController.cs create mode 100644 "WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest.csproj create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/DefaultBasicAuthenticationProvider.cs diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/BasicAuthenticationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" similarity index 97% rename from WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs rename to "WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/BasicAuthenticationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" index 4166dea8..be40b7a4 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/UnitTest1.cs +++ "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/BasicAuthenticationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" @@ -9,7 +9,7 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest; [TestClass] -public class UnitTest1 +public class BasicAuthenticationMiddleware整合測試 { [TestMethod] public void 訪問不需要授權的服務() diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/TestController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/TestController.cs new file mode 100644 index 00000000..bbbdca23 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/TestController.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.Controllers; + +[ApiController] +[Route("[controller]")] +public class TestController : ControllerBase +{ + private readonly ILogger _logger; + + public TestController(ILogger logger) + { + this._logger = logger; + } + + [AllowAnonymous] + [HttpGet] + public async Task Get() + { + return this.Ok("好"); + } +} \ No newline at end of file diff --git "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" new file mode 100644 index 00000000..a6d428b4 --- /dev/null +++ "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" @@ -0,0 +1,116 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest; + +[TestClass] +public class BasicAuthenticationMiddleware單元測試 +{ + [TestMethod] + public async Task aaaa() + { + var host = await new HostBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseTestServer() + .ConfigureServices( + services => + { + services.AddSingleton(); + services.AddBasicAuthentication(_ => { }); + services.AddAuthorization(); + }) + .Configure(app => + { + app.UseAuthentication(); + app.UseAuthorization(); + }); + }) + .StartAsync(); + var optionsMonitor = host.Services.GetService>(); + var handler = host.Services.GetService(); + var authenticationScheme = new AuthenticationScheme("basic", "basic", typeof(BasicAuthenticationHandler)); + handler.InitializeAsync(authenticationScheme, new DefaultHttpContext()); + var authenticateResult = await handler.AuthenticateAsync(); + + using var server = await CreateTestServer(); + var httpContext = await server.SendAsync(config => + { + config.Request.Headers.Authorization = CreateBasicAuthenticationValue("yao", "9527xxxx"); + }); + + // 驗證失敗沒有觸發 BasicAuthenticationHandler.HandleChallengeAsync + var userPrincipal = httpContext.User; + Assert.AreEqual(false, userPrincipal.Identity.IsAuthenticated); + } + + [TestMethod] + public async Task 驗證失敗() + { + using var server = await CreateTestServer(); + var httpContext = await server.SendAsync(config => + { + config.Request.Headers.Authorization = CreateBasicAuthenticationValue("yao", "9527xxxx"); + }); + + // 驗證失敗沒有觸發 BasicAuthenticationHandler.HandleChallengeAsync + var userPrincipal = httpContext.User; + Assert.AreEqual(false, userPrincipal.Identity.IsAuthenticated); + } + + [TestMethod] + public async Task 驗證成功() + { + using var server = await CreateTestServer(); + var httpContext = await server.SendAsync(config => + { + config.Request.Headers.Authorization = CreateBasicAuthenticationValue("yao", "9527"); + }); + var userPrincipal = httpContext.User; + Assert.AreEqual(true, userPrincipal.Identity.IsAuthenticated); + } + + private static string CreateBasicAuthenticationValue(string userId, string password) + { + var certificate = $"{userId}:{password}"; + var base64Encode = Convert.ToBase64String(Encoding.ASCII.GetBytes(certificate)); + return $"Basic {base64Encode}"; + } + + private static async Task CreateTestServer() + { + var host = await new HostBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseTestServer() + .ConfigureServices( + services => + { + services.AddSingleton(); + services.AddBasicAuthentication(_ => { }); + services.AddAuthorization(); + }) + .Configure(app => + { + app.UseAuthentication(); + app.UseAuthorization(); + }); + }) + .StartAsync(); + + var server = host.GetTestServer(); + server.BaseAddress = new Uri("https://我真的是假的/不要打我的臉/"); + return server; + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest.csproj new file mode 100644 index 00000000..65a60277 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + + diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs index 2743337e..c86fbb6f 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs @@ -11,12 +11,12 @@ builder.Services.AddSwaggerGen(); builder.Logging.AddConsole(); -builder.Services.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme) - .AddScheme(BasicAuthenticationDefaults.AuthenticationScheme, - p => new BasicAuthenticationOptions()); -builder.Services.AddSingleton(); +// builder.Services.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme) +// .AddScheme(BasicAuthenticationDefaults.AuthenticationScheme, +// p => new BasicAuthenticationOptions()); +// builder.Services.AddSingleton(); -// builder.Services.AddBasicAuthentication(); +builder.Services.AddBasicAuthentication(options => { }); var app = builder.Build(); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs index 76845c24..267ade4f 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs @@ -1,46 +1,25 @@ using Microsoft.AspNetCore.Authentication; -using Microsoft.Extensions.Options; namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; public static class BasicAuthenticationExtensions { - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder) - where TAuthService : class, IBasicAuthenticationProvider - { - return AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, _ => { }); - } - - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, - string authenticationScheme) - where TAuthService : class, IBasicAuthenticationProvider - { - return AddBasic(builder, authenticationScheme, _ => { }); - } - - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, Action configureOptions) - where TAuthService : class, IBasicAuthenticationProvider { - return AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, configureOptions); - } - - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, - string authenticationScheme, Action configureOptions) - where TAuthService : class, IBasicAuthenticationProvider - { - builder.Services - .AddSingleton, BasicAuthenticationPostConfigureOptions>(); - builder.Services.AddSingleton(); - return builder.AddScheme( - authenticationScheme, configureOptions); + BasicAuthenticationDefaults.AuthenticationScheme, + BasicAuthenticationDefaults.AuthenticationScheme, configureOptions); } - public static AuthenticationBuilder AddBasicAuthentication(this IServiceCollection services) - where TAuthService : class, IBasicAuthenticationProvider + public static AuthenticationBuilder AddBasicAuthentication(this IServiceCollection services, + Action configureOptions) { - return services.AddAuthentication(o => o.DefaultScheme = BasicAuthenticationDefaults.AuthenticationScheme) - .AddBasic(); + return services.AddAuthentication(o => + { + o.DefaultAuthenticateScheme = BasicAuthenticationDefaults.AuthenticationScheme; + o.DefaultChallengeScheme = BasicAuthenticationDefaults.AuthenticationScheme; + }) + .AddBasic(configureOptions); } } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs index bee997de..789d7ca5 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs @@ -79,6 +79,7 @@ protected override async Task HandleAuthenticateAsync() protected override async Task HandleChallengeAsync(AuthenticationProperties properties) { + // todo: write detail log this.Response.StatusCode = 401; this.Response.HttpContext.Features.Get().ReasonPhrase = this._failReason; this.Response.Headers["WWW-Authenticate"] = $"Basic realm=\"{this.Options.Realm}\", charset=\"UTF-8\""; diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/DefaultBasicAuthenticationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/DefaultBasicAuthenticationProvider.cs new file mode 100644 index 00000000..b90712b5 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/DefaultBasicAuthenticationProvider.cs @@ -0,0 +1,9 @@ +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; + +public class DefaultBasicAuthenticationProvider : IBasicAuthenticationProvider +{ + public Task IsValidateAsync(string user, string password, CancellationToken cancel = default) + { + return Task.FromResult(true); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln index 1d518087..1fc06b88 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln @@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AspNetCore.Security.Bas EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest", "Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest\Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj", "{13085C3E-F174-45D2-B8F7-3EE51D42DDF4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest", "Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest\Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest.csproj", "{12F00FC6-1D31-48CD-AB17-B00F76846A33}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {13085C3E-F174-45D2-B8F7-3EE51D42DDF4}.Debug|Any CPU.Build.0 = Debug|Any CPU {13085C3E-F174-45D2-B8F7-3EE51D42DDF4}.Release|Any CPU.ActiveCfg = Release|Any CPU {13085C3E-F174-45D2-B8F7-3EE51D42DDF4}.Release|Any CPU.Build.0 = Release|Any CPU + {12F00FC6-1D31-48CD-AB17-B00F76846A33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {12F00FC6-1D31-48CD-AB17-B00F76846A33}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12F00FC6-1D31-48CD-AB17-B00F76846A33}.Release|Any CPU.ActiveCfg = Release|Any CPU + {12F00FC6-1D31-48CD-AB17-B00F76846A33}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From f0909525365ee61603ecddac91af091c3a32a99b Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 19 Jun 2022 14:41:45 +0800 Subject: [PATCH 226/424] refactor --- ...64\345\220\210\346\270\254\350\251\246.cs" | 4 +- .../Controllers/Models/User.cs | 8 ++++ .../Controllers/TestController.cs | 8 ++++ ...sicAuthenticationSite.IntegrateTest.csproj | 12 +++--- .../TestServer.cs | 5 ++- ...56\345\205\203\346\270\254\350\251\246.cs" | 39 ------------------- .../Controllers/User.cs | 8 ---- .../Controllers/UserController.cs | 27 ------------- ...re.Security.BasicAuthenticationSite.csproj | 4 +- .../Program.cs | 2 +- 10 files changed, 31 insertions(+), 86 deletions(-) create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/Models/User.cs delete mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/User.cs delete mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/UserController.cs diff --git "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/BasicAuthenticationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/BasicAuthenticationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" index be40b7a4..5e71f2b0 100644 --- "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/BasicAuthenticationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" +++ "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/BasicAuthenticationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" @@ -16,7 +16,7 @@ public void 訪問不需要授權的服務() { var server = new TestServer(); var httpClient = server.CreateClient(); - var url = "demo"; + var url = "test"; var response = httpClient.GetAsync(url).Result; var result = response.Content.ReadAsStringAsync().Result; Console.WriteLine(result); @@ -28,7 +28,7 @@ public void 訪問受保護的服務() { var server = new TestServer(); var httpClient = server.CreateClient(); - var url = "user"; + var url = "test"; var clientId = "YAO"; var clientSecret = "9527"; using var requestMessage = CreateBasicAuthenticationRequest(url, clientId, clientSecret); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/Models/User.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/Models/User.cs new file mode 100644 index 00000000..d923c451 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/Models/User.cs @@ -0,0 +1,8 @@ +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.Controllers.Models; + +public class User +{ + public string Name { get; set; } + + public int Age { get; set; } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/TestController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/TestController.cs index bbbdca23..c453bc1a 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/TestController.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/TestController.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.Controllers.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -22,4 +23,11 @@ public async Task Get() { return this.Ok("好"); } + + [Authorize] + [HttpPost] + public async Task Post(User user) + { + return this.Ok("好"); + } } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj index 90db6373..a9c20e84 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj @@ -8,14 +8,14 @@ - - - - - + + + + + - + diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/TestServer.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/TestServer.cs index 3ddc0f95..b94e4863 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/TestServer.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/TestServer.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Hosting; +using Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.Controllers; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; @@ -8,6 +9,8 @@ public class TestServer : WebApplicationFactory { private void ConfigureServices(IServiceCollection services) { + services.AddControllers() + .AddApplicationPart(typeof(TestController).Assembly); } protected override void ConfigureWebHost(IWebHostBuilder builder) diff --git "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" index a6d428b4..07915dc4 100644 --- "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" +++ "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Options; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest; @@ -17,44 +16,6 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest; [TestClass] public class BasicAuthenticationMiddleware單元測試 { - [TestMethod] - public async Task aaaa() - { - var host = await new HostBuilder() - .ConfigureWebHost(webBuilder => - { - webBuilder.UseTestServer() - .ConfigureServices( - services => - { - services.AddSingleton(); - services.AddBasicAuthentication(_ => { }); - services.AddAuthorization(); - }) - .Configure(app => - { - app.UseAuthentication(); - app.UseAuthorization(); - }); - }) - .StartAsync(); - var optionsMonitor = host.Services.GetService>(); - var handler = host.Services.GetService(); - var authenticationScheme = new AuthenticationScheme("basic", "basic", typeof(BasicAuthenticationHandler)); - handler.InitializeAsync(authenticationScheme, new DefaultHttpContext()); - var authenticateResult = await handler.AuthenticateAsync(); - - using var server = await CreateTestServer(); - var httpContext = await server.SendAsync(config => - { - config.Request.Headers.Authorization = CreateBasicAuthenticationValue("yao", "9527xxxx"); - }); - - // 驗證失敗沒有觸發 BasicAuthenticationHandler.HandleChallengeAsync - var userPrincipal = httpContext.User; - Assert.AreEqual(false, userPrincipal.Identity.IsAuthenticated); - } - [TestMethod] public async Task 驗證失敗() { diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/User.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/User.cs deleted file mode 100644 index acde4a03..00000000 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/User.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Controllers; - -internal class User -{ - public string Name { get; set; } - - public int Age { get; set; } -} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/UserController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/UserController.cs deleted file mode 100644 index 695aa74a..00000000 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/UserController.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Controllers; - -[ApiController] -[Route("[controller]")] -public class UserController : ControllerBase -{ - private readonly ILogger _logger; - - public UserController(ILogger logger) - { - this._logger = logger; - } - - [HttpGet] - [Authorize] - public async Task Get() - { - return this.Ok(new User - { - Name = this.User.Identity.Name, - Age = 18 - }); - } -} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj index b4a1e621..ab416cfa 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs index c86fbb6f..d1f5376b 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs @@ -14,7 +14,7 @@ // builder.Services.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme) // .AddScheme(BasicAuthenticationDefaults.AuthenticationScheme, // p => new BasicAuthenticationOptions()); -// builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddBasicAuthentication(options => { }); From 95c4cbc385a61555a8d60f816f2e715927f2fb20 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 19 Jun 2022 16:18:38 +0800 Subject: [PATCH 227/424] =?UTF-8?q?add=20BasicAuthenticationHandler?= =?UTF-8?q?=E5=96=AE=E5=85=83=E6=B8=AC=E8=A9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...sicAuthenticationSite.IntegrateTest.csproj | 12 +- ...56\345\205\203\346\270\254\350\251\246.cs" | 167 ++++++++++++++++++ ...56\345\205\203\346\270\254\350\251\246.cs" | 2 - ...re.Security.BasicAuthenticationSite.csproj | 4 +- .../BasicAuthenticationHandler.cs | 21 ++- 5 files changed, 192 insertions(+), 14 deletions(-) create mode 100644 "WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj index a9c20e84..90db6373 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj @@ -8,14 +8,14 @@ - - - - - + + + + + - + diff --git "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" new file mode 100644 index 00000000..eb6e8b6e --- /dev/null +++ "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" @@ -0,0 +1,167 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest; + +[TestClass] +public class BasicAuthenticationHandler單元測試 +{ + [TestMethod] + public async Task AuthenticateAsyncTest() + { + var context = new DefaultHttpContext(); + var authorizationHeader = new StringValues(string.Empty); + context.Request.Headers.Add(HeaderNames.Authorization, authorizationHeader); + + using var testHost = await CreateTestHost(); + var handler = testHost.Services.GetService(); + await handler.InitializeAsync(new AuthenticationScheme("basic", + "basic", + typeof(BasicAuthenticationHandler)), + context); + var result = await handler.AuthenticateAsync(); + + Assert.IsFalse(result.Succeeded); + Assert.AreEqual("Invalid authorization Header", result.Failure.Message); + } + + [TestMethod] + public async Task ChallengeAsyncTest() + { + var context = new DefaultHttpContext + { + Response = { Body = new MemoryStream() } + }; + + var authorizationHeader = new StringValues(string.Empty); + context.Request.Headers.Add(HeaderNames.Authorization, authorizationHeader); + + using var testHost = await CreateTestHost(); + var handler = testHost.Services.GetService(); + await handler.InitializeAsync(new AuthenticationScheme("basic", + "basic", + typeof(BasicAuthenticationHandler)), + context); + var authenticateResult = await handler.AuthenticateAsync(); + await handler.ChallengeAsync(authenticateResult.Properties); + var response = context.Response; + + // Assert.IsFalse(result.Succeeded); + var expected = "Basic realm=\"Demo Site\", charset=\"UTF-8\""; + Assert.AreEqual(expected, response.Headers.WWWAuthenticate.ToString()); + } + + [TestMethod] + public async Task 驗證成功() + { + using var server = await CreateTestServer(); + var httpContext = await server.SendAsync(config => + { + config.Request.Headers.Authorization = CreateBasicAuthenticationValue("yao", "9527"); + }); + var userPrincipal = httpContext.User; + Assert.AreEqual(true, userPrincipal.Identity.IsAuthenticated); + } + + private static AuthenticationHeaderValue CreateAuthenticationHeaderValue(string userId, string password) + { + var certificate = CreateBasicAuthenticationValue(userId, password); + var authenticationHeaderValue = new AuthenticationHeaderValue("basic", certificate); + return authenticationHeaderValue; + } + + private static string CreateBasicAuthenticationValue(string userId, string password) + { + var certificate = $"{userId}:{password}"; + var base64Encode = Convert.ToBase64String(Encoding.ASCII.GetBytes(certificate)); + return $"Basic {base64Encode}"; + } + + private static async Task CreateTestClient() + { + var host = await new HostBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseTestServer() + .ConfigureServices( + services => + { + services.AddSingleton(); + services.AddBasicAuthentication(_ => { }); + services.AddAuthorization(); + }) + .Configure(app => + { + app.UseAuthentication(); + app.UseAuthorization(); + }); + }) + .StartAsync(); + + return host.GetTestClient(); + } + + private static async Task CreateTestHost() + { + var host = await new HostBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseTestServer() + .ConfigureServices( + services => + { + services.AddSingleton(); + services.AddBasicAuthentication(_ => { }); + services.AddAuthorization(); + }) + .Configure(app => + { + app.UseAuthentication(); + app.UseAuthorization(); + }); + }) + .StartAsync(); + return host; + } + + private static async Task CreateTestServer() + { + var host = await new HostBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.UseTestServer() + .ConfigureServices( + services => + { + services.AddSingleton(); + services.AddBasicAuthentication(_ => { }); + services.AddAuthorization(); + }) + .Configure(app => + { + app.UseAuthentication(); + app.UseAuthorization(); + }); + }) + .StartAsync(); + + var server = host.GetTestServer(); + server.BaseAddress = new Uri("https://我真的是假的/不要打我的臉/"); + return server; + } +} \ No newline at end of file diff --git "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" index 07915dc4..91941d36 100644 --- "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" +++ "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" @@ -2,10 +2,8 @@ using System.Text; using System.Threading.Tasks; using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; -using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj index ab416cfa..b4a1e621 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs index 789d7ca5..5f85a0e1 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs @@ -6,12 +6,12 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; public class BasicAuthenticationHandler : AuthenticationHandler { - private const string AuthorizationHeaderName = "Authorization"; private readonly IBasicAuthenticationProvider _authenticationProvider; private string _failReason; @@ -35,13 +35,13 @@ protected override async Task HandleAuthenticateAsync() return AuthenticateResult.NoResult(); } - if (!this.Request.Headers.ContainsKey(AuthorizationHeaderName)) + if (!this.Request.Headers.ContainsKey(HeaderNames.Authorization)) { this._failReason = "Invalid basic authentication header"; return AuthenticateResult.Fail(this._failReason); } - if (!AuthenticationHeaderValue.TryParse(this.Request.Headers[AuthorizationHeaderName], + if (!AuthenticationHeaderValue.TryParse(this.Request.Headers[HeaderNames.Authorization], out var authHeaderValue)) { this._failReason = "Invalid authorization Header"; @@ -79,10 +79,23 @@ protected override async Task HandleAuthenticateAsync() protected override async Task HandleChallengeAsync(AuthenticationProperties properties) { - // todo: write detail log + // 寫入詳細的失敗原因,排除敏感性資料 + this.Logger.LogInformation("{FailureReason}", new + { + Code = "InvalidAuthentication", + Message = this._failReason + }); + this.Response.StatusCode = 401; this.Response.HttpContext.Features.Get().ReasonPhrase = this._failReason; this.Response.Headers["WWW-Authenticate"] = $"Basic realm=\"{this.Options.Realm}\", charset=\"UTF-8\""; + + // 響應粗糙的內容,這不是標準的 Basic Authentication 失敗的回傳,僅是為了示意 + this.Response.WriteAsJsonAsync(new + { + Code = "InvalidAuthentication", + Message = "Please contact your administrator" + }); await Task.CompletedTask; } From 54673a6cab8f4216e8df728864d858fe37e21d86 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 19 Jun 2022 16:45:09 +0800 Subject: [PATCH 228/424] refactor --- ...56\345\205\203\346\270\254\350\251\246.cs" | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" index eb6e8b6e..733c1c64 100644 --- "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" +++ "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" @@ -22,7 +22,25 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest; public class BasicAuthenticationHandler單元測試 { [TestMethod] - public async Task AuthenticateAsyncTest() + public async Task 驗證成功() + { + var context = new DefaultHttpContext(); + var authorizationHeader = new StringValues(CreateBasicAuthenticationValue("yao", "9527")); + context.Request.Headers.Add(HeaderNames.Authorization, authorizationHeader); + + using var testHost = await CreateTestHost(); + var handler = testHost.Services.GetService(); + await handler.InitializeAsync(new AuthenticationScheme("basic", + "basic", + typeof(BasicAuthenticationHandler)), + context); + var result = await handler.AuthenticateAsync(); + + Assert.IsTrue(result.Succeeded); + } + + [TestMethod] + public async Task 驗證失敗() { var context = new DefaultHttpContext(); var authorizationHeader = new StringValues(string.Empty); @@ -41,14 +59,14 @@ await handler.InitializeAsync(new AuthenticationScheme("basic", } [TestMethod] - public async Task ChallengeAsyncTest() + public async Task 驗證失敗後回應錯誤() { var context = new DefaultHttpContext { Response = { Body = new MemoryStream() } }; - var authorizationHeader = new StringValues(string.Empty); + var authorizationHeader = new StringValues(CreateBasicAuthenticationValue("yao", "9527")); context.Request.Headers.Add(HeaderNames.Authorization, authorizationHeader); using var testHost = await CreateTestHost(); @@ -61,30 +79,11 @@ await handler.InitializeAsync(new AuthenticationScheme("basic", await handler.ChallengeAsync(authenticateResult.Properties); var response = context.Response; - // Assert.IsFalse(result.Succeeded); + Assert.IsFalse(authenticateResult.Succeeded); var expected = "Basic realm=\"Demo Site\", charset=\"UTF-8\""; Assert.AreEqual(expected, response.Headers.WWWAuthenticate.ToString()); } - [TestMethod] - public async Task 驗證成功() - { - using var server = await CreateTestServer(); - var httpContext = await server.SendAsync(config => - { - config.Request.Headers.Authorization = CreateBasicAuthenticationValue("yao", "9527"); - }); - var userPrincipal = httpContext.User; - Assert.AreEqual(true, userPrincipal.Identity.IsAuthenticated); - } - - private static AuthenticationHeaderValue CreateAuthenticationHeaderValue(string userId, string password) - { - var certificate = CreateBasicAuthenticationValue(userId, password); - var authenticationHeaderValue = new AuthenticationHeaderValue("basic", certificate); - return authenticationHeaderValue; - } - private static string CreateBasicAuthenticationValue(string userId, string password) { var certificate = $"{userId}:{password}"; From 512503be66192d25edc1c4da1099872b3526fdcf Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 20 Jun 2022 21:40:03 +0800 Subject: [PATCH 229/424] fix --- ...64\345\220\210\346\270\254\350\251\246.cs" | 26 ++++++++++++++----- .../Controllers/TestController.cs | 2 +- ...56\345\205\203\346\270\254\350\251\246.cs" | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/BasicAuthenticationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/BasicAuthenticationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" index 5e71f2b0..892e61b5 100644 --- "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/BasicAuthenticationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" +++ "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/BasicAuthenticationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" @@ -28,11 +28,15 @@ public void 訪問受保護的服務() { var server = new TestServer(); var httpClient = server.CreateClient(); - var url = "test"; + var url = "protect"; var clientId = "YAO"; var clientSecret = "9527"; - using var requestMessage = CreateBasicAuthenticationRequest(url, clientId, clientSecret); - var response = httpClient.SendAsync(requestMessage).Result; + var request = new HttpRequestMessage(HttpMethod.Get, url) + { + Headers = { Authorization = CreateAuthenticationHeaderValue(clientId, clientSecret) } + }; + + var response = httpClient.SendAsync(request).Result; Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); } @@ -41,11 +45,14 @@ public void 訪問受保護的服務_驗證失敗() { var server = new TestServer(); var httpClient = server.CreateClient(); - var url = "user"; + var url = "protect"; var clientId = "YAO1234"; var clientSecret = "9527"; - using var requestMessage = CreateBasicAuthenticationRequest(url, clientId, clientSecret); - var response = httpClient.SendAsync(requestMessage).Result; + var request = new HttpRequestMessage(HttpMethod.Get, url) + { + Headers = { Authorization = CreateAuthenticationHeaderValue(clientId, clientSecret) } + }; + var response = httpClient.SendAsync(request).Result; response.Headers.TryGetValues("WWW-Authenticate", out var values); Console.WriteLine($"驗證失敗:{values.First()}"); Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode); @@ -59,4 +66,11 @@ private static HttpRequestMessage CreateBasicAuthenticationRequest(string url, s requestMessage.Headers.Authorization = new AuthenticationHeaderValue("basic", base64Encoded); return requestMessage; } + + private static AuthenticationHeaderValue CreateAuthenticationHeaderValue(string clientId, string clientSecret) + { + var authenticationString = $"{clientId}:{clientSecret}"; + var base64Encoded = Convert.ToBase64String(Encoding.ASCII.GetBytes(authenticationString)); + return new AuthenticationHeaderValue("basic", base64Encoded); + } } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/TestController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/TestController.cs index c453bc1a..7d3a574d 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/TestController.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/TestController.cs @@ -24,7 +24,7 @@ public async Task Get() return this.Ok("好"); } - [Authorize] + [AllowAnonymous] [HttpPost] public async Task Post(User user) { diff --git "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" index 733c1c64..aa95c4f2 100644 --- "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" +++ "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" @@ -66,7 +66,7 @@ public async Task 驗證失敗後回應錯誤() Response = { Body = new MemoryStream() } }; - var authorizationHeader = new StringValues(CreateBasicAuthenticationValue("yao", "9527")); + var authorizationHeader = new StringValues(CreateBasicAuthenticationValue("yao123", "9527")); context.Request.Headers.Add(HeaderNames.Authorization, authorizationHeader); using var testHost = await CreateTestHost(); From 80b3c9ae694a6f6593f292885676f38738543726 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 20 Jun 2022 21:56:37 +0800 Subject: [PATCH 230/424] fix --- .../Controllers/ProtectController.cs | 33 +++++++++++++++++++ ...sicAuthenticationSite.IntegrateTest.csproj | 12 +++---- ...re.Security.BasicAuthenticationSite.csproj | 7 ++-- .../PermissionAuthorizationHandler.cs | 27 +++++++++++++++ ...ionAuthorizationMiddlewareResultHandler.cs | 15 +++++++++ .../PermissionAuthorizationRequirement.cs | 7 ++++ 6 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/ProtectController.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationHandler.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationRequirement.cs diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/ProtectController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/ProtectController.cs new file mode 100644 index 00000000..b61a4108 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/ProtectController.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.Controllers.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.Controllers; + +[ApiController] +[Route("[controller]")] +public class ProtectController : ControllerBase +{ + private readonly ILogger _logger; + + public ProtectController(ILogger logger) + { + this._logger = logger; + } + + [Authorize] + [HttpGet] + public async Task Get() + { + return this.Ok("好"); + } + + [AllowAnonymous] + [HttpPost] + public async Task Post(User user) + { + return this.Ok("好"); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj index 90db6373..a9c20e84 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.csproj @@ -8,14 +8,14 @@ - - - - - + + + + + - + diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj index b4a1e621..e71b3ef4 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj @@ -7,13 +7,10 @@ - - + + - - - diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationHandler.cs new file mode 100644 index 00000000..ffb736a0 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationHandler.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization; + +public class PermissionAuthorizationHandler : AuthorizationHandler +{ + + public override Task HandleAsync(AuthorizationHandlerContext context) + { + return base.HandleAsync(context); + } + + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, + PermissionAuthorizationRequirement requirement) + { + if (context.User.Identity.IsAuthenticated==false) + { + + // return Task.FromResult(false); + } + + + + // Task.FromResult(false); + } + +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs new file mode 100644 index 00000000..578239d0 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization.Policy; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization; + +public class PermissionAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler +{ + public async Task HandleAsync(RequestDelegate next, + HttpContext context, + AuthorizationPolicy policy, + PolicyAuthorizationResult authorizeResult) + { + await next(context); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationRequirement.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationRequirement.cs new file mode 100644 index 00000000..96f2e195 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationRequirement.cs @@ -0,0 +1,7 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization; + +public class PermissionAuthorizationRequirement : IAuthorizationRequirement +{ +} \ No newline at end of file From 24323d8de6286aab2308aa2481a2032c2c62e7a7 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 21 Jun 2022 19:30:36 +0800 Subject: [PATCH 231/424] =?UTF-8?q?feat:=20=E5=AF=A6=E4=BD=9C=E6=8E=88?= =?UTF-8?q?=E6=AC=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/PermissionController.cs | 34 +++++++++ ...64\345\220\210\346\270\254\350\251\246.cs" | 76 +++++++++++++++++++ .../FieldTypeAssistant.cs | 41 ++++++++++ .../Program.cs | 24 +++++- .../IPermissionAuthorizationProvider.cs | 6 ++ .../Security/Authorization/Permission.cs | 22 ++++++ .../PermissionAuthorizationHandler.cs | 25 ++++-- ...ionAuthorizationMiddlewareResultHandler.cs | 45 ++++++++++- .../PermissionAuthorizationPolicyProvider.cs | 53 +++++++++++++ .../PermissionAuthorizationProvider.cs | 21 +++++ .../PermissionAuthorizationRequirement.cs | 1 + 11 files changed, 336 insertions(+), 12 deletions(-) create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/PermissionController.cs create mode 100644 "WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/PermissionAuthorizationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/FieldTypeAssistant.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/IPermissionAuthorizationProvider.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/Permission.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationPolicyProvider.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationProvider.cs diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/PermissionController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/PermissionController.cs new file mode 100644 index 00000000..d45d4f3b --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/Controllers/PermissionController.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.Controllers.Models; +using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.Controllers; + +[ApiController] +[Route("[controller]")] +public class PermissionController : ControllerBase +{ + private readonly ILogger _logger; + + public PermissionController(ILogger logger) + { + this._logger = logger; + } + + [Authorize(Policy = Permission.Operation.Read)] + [HttpGet] + public async Task Get() + { + return this.Ok("好"); + } + + [AllowAnonymous] + [HttpPost] + public async Task Post(User user) + { + return this.Ok("好"); + } +} \ No newline at end of file diff --git "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/PermissionAuthorizationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/PermissionAuthorizationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" new file mode 100644 index 00000000..2ada55db --- /dev/null +++ "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest/PermissionAuthorizationMiddleware\346\225\264\345\220\210\346\270\254\350\251\246.cs" @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest.Controllers; +using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.IntegrateTest; + +[TestClass] +public class PermissionAuthorizationMiddleware整合測試 +{ + [TestMethod] + public async Task 訪問受保護的服務_授權成功() + { + var server = CreateTestServer(); + var httpClient = server.CreateClient(); + var url = "permission"; + var clientId = "YAO"; + var clientSecret = "9527"; + var request = new HttpRequestMessage(HttpMethod.Get, url) + { + Headers = { Authorization = CreateAuthenticationHeaderValue(clientId, clientSecret) } + }; + var response = httpClient.SendAsync(request).Result; + var content = await response.Content.ReadAsStringAsync(); + Console.WriteLine(content); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + } + + [TestMethod] + public async Task 訪問受保護的服務_授權失敗() + { + var server = CreateTestServer(); + var httpClient = server.CreateClient(); + var url = "permission"; + var clientId = "jojo"; + var clientSecret = "9527"; + var request = new HttpRequestMessage(HttpMethod.Get, url) + { + Headers = { Authorization = CreateAuthenticationHeaderValue(clientId, clientSecret) } + }; + var response = httpClient.SendAsync(request).Result; + var content = await response.Content.ReadAsStringAsync(); + Console.WriteLine(content); + Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode); + } + + private static WebApplicationFactory CreateTestServer() + { + var server = new WebApplicationFactory().WithWebHostBuilder(builder => + { + builder.ConfigureServices(services => + { + services.AddSingleton(); + services.AddControllers() + .AddApplicationPart(typeof(TestController).Assembly); + }); + }); + return server; + } + + private static AuthenticationHeaderValue CreateAuthenticationHeaderValue(string clientId, string clientSecret) + { + var authenticationString = $"{clientId}:{clientSecret}"; + var base64Encoded = Convert.ToBase64String(Encoding.ASCII.GetBytes(authenticationString)); + return new AuthenticationHeaderValue("basic", base64Encoded); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/FieldTypeAssistant.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/FieldTypeAssistant.cs new file mode 100644 index 00000000..a40d46a2 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/FieldTypeAssistant.cs @@ -0,0 +1,41 @@ +using System.Collections.Concurrent; +using System.Reflection; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite; + +public class FieldTypeAssistant +{ + private static ConcurrentDictionary> s_fieldTypeList = new(); + + public static Dictionary GetEnumValues() + { + return Enum.GetValues(typeof(T)) + .Cast() + .ToDictionary(p => p.ToString(), p => p); + } + + public static Dictionary GetStaticFieldName() + { + var type = typeof(T); + var fieldTypeList = s_fieldTypeList; + if (fieldTypeList.TryGetValue(type, out var results)) + { + return results; + } + + var bindingFlags = BindingFlags.Public + | BindingFlags.Static + ; + results = new Dictionary(); + var fieldInfosInfos = type.GetFields(bindingFlags); + foreach (var fieldInfo in fieldInfosInfos) + { + var value = fieldInfo.GetValue(null); + + results.Add(value.ToString(), fieldInfo.FieldType); + } + + fieldTypeList.TryAdd(type, results); + return results; + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs index d1f5376b..26a528f4 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs @@ -1,4 +1,10 @@ +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Unicode; using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; +using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization; +using Microsoft.AspNetCore.Authorization; var builder = WebApplication.CreateBuilder(args); @@ -14,9 +20,25 @@ // builder.Services.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme) // .AddScheme(BasicAuthenticationDefaults.AuthenticationScheme, // p => new BasicAuthenticationOptions()); +builder.Services.AddSingleton(p=>new JsonSerializerOptions +{ + Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs), + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, +}); builder.Services.AddSingleton(); - builder.Services.AddBasicAuthentication(options => { }); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +// builder.Services.AddAuthorization(options => +// { +// options.AddPolicy("Permission", policy => +// policy.Requirements.Add(new PermissionAuthorizationRequirement())); +// }); var app = builder.Build(); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/IPermissionAuthorizationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/IPermissionAuthorizationProvider.cs new file mode 100644 index 00000000..a6fa3b2a --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/IPermissionAuthorizationProvider.cs @@ -0,0 +1,6 @@ +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization; + +public interface IPermissionAuthorizationProvider +{ + IEnumerable GetPermissions(string userId); +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/Permission.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/Permission.cs new file mode 100644 index 00000000..837cda0c --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/Permission.cs @@ -0,0 +1,22 @@ +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization; + +public class Permission +{ + public class Operation + { + public const string Write = $"{nameof(Operation)}.{nameof(Write)}"; + public const string Read = $"{nameof(Operation)}.{nameof(Read)}"; + + private static readonly Lazy> s_values + = new(() => + { + return FieldTypeAssistant.GetStaticFieldName() + .ToDictionary(p => p.Key, + p => p.Value, + StringComparer.InvariantCultureIgnoreCase); + }); + + public static Dictionary GetValues() + => s_values.Value; + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationHandler.cs index ffb736a0..b1a9288c 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationHandler.cs @@ -4,24 +4,33 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization public class PermissionAuthorizationHandler : AuthorizationHandler { + private IPermissionAuthorizationProvider _authorizationProvider; - public override Task HandleAsync(AuthorizationHandlerContext context) + public PermissionAuthorizationHandler(IPermissionAuthorizationProvider authorizationProvider) { - return base.HandleAsync(context); + this._authorizationProvider = authorizationProvider; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement) { - if (context.User.Identity.IsAuthenticated==false) + if (context.User.Identity.IsAuthenticated == false) { - - // return Task.FromResult(false); + context.Fail(new AuthorizationFailureReason(this, $"目前請求沒有通過驗證")); + return; } - + var userId = context.User.Identity.Name; + var permissions = this._authorizationProvider.GetPermissions(userId); + if (permissions.Any(p => p.StartsWith(requirement.PolicyName, StringComparison.InvariantCultureIgnoreCase)) == + false) + { + context.Fail(new AuthorizationFailureReason(this, $"用戶 '{userId}',沒有授權 '{requirement.PolicyName}'")); + } - // Task.FromResult(false); + if (context.HasFailed == false) + { + context.Succeed(requirement); + } } - } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs index 578239d0..a60f2fa8 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs @@ -1,15 +1,54 @@ -using Microsoft.AspNetCore.Authorization; +using System.Text.Json; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization; public class PermissionAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler { - public async Task HandleAsync(RequestDelegate next, + private readonly ILogger _logger; + + // private readonly AuthorizationMiddlewareResultHandler _defaultHandler = new(); + private readonly JsonSerializerOptions _jsonSerializerOptions; + + public PermissionAuthorizationMiddlewareResultHandler( + ILogger logger, + JsonSerializerOptions jsonSerializerOptions) + { + this._logger = logger; + this._jsonSerializerOptions = jsonSerializerOptions; + } + + public async Task HandleAsync( + RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) { - await next(context); + var permissionAuthorizationRequirements = policy.Requirements.OfType(); + + if (authorizeResult.Forbidden + && permissionAuthorizationRequirements.Any()) + { + context.Response.StatusCode = 403; + this._logger.LogInformation("{AuthorizationFailureResults}", new + { + ErrorCode = "Invalid Authorization", + ErrorMessages = authorizeResult.AuthorizationFailure.FailureReasons + }); + + // 回傳前端模糊訊息 + await context.Response.WriteAsJsonAsync(new + { + ErrorCode = "Invalid Authorization", + ErrorMessages = new[] { "Please contact your administrator" } + // ErrorMessages = authorizeResult.AuthorizationFailure.FailureReasons + }, this._jsonSerializerOptions); + return; + } + + await next.Invoke(context); + + // await next(context); } } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationPolicyProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationPolicyProvider.cs new file mode 100644 index 00000000..63d1a91c --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationPolicyProvider.cs @@ -0,0 +1,53 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Options; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization; + +internal class PermissionAuthorizationPolicyProvider : IAuthorizationPolicyProvider +{ + const string POLICY_PREFIX = "Permission"; + + public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } + + public PermissionAuthorizationPolicyProvider(IOptions options) + { + // ASP.NET Core only uses one authorization policy provider, so if the custom implementation + // doesn't handle all policies (including default policies, etc.) it should fall back to an + // alternate provider. + // + // In this sample, a default authorization policy provider (constructed with options from the + // dependency injection container) is used if this custom provider isn't able to handle a given + // policy name. + // + // If a custom policy provider is able to handle all expected policy names then, of course, this + // fallback pattern is unnecessary. + FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options); + } + + public Task GetDefaultPolicyAsync() => FallbackPolicyProvider.GetDefaultPolicyAsync(); + + public Task GetFallbackPolicyAsync() => FallbackPolicyProvider.GetFallbackPolicyAsync(); + + // Policies are looked up by string name, so expect 'parameters' (like age) + // to be embedded in the policy names. This is abstracted away from developers + // by the more strongly-typed attributes derived from AuthorizeAttribute + // (like [MinimumAgeAuthorize] in this sample) + public Task GetPolicyAsync(string policyName) + { + var operationValues = Permission.Operation.GetValues(); + if (operationValues.Any(p => p.Key.StartsWith(policyName, StringComparison.InvariantCultureIgnoreCase))) + { + var policy = new AuthorizationPolicyBuilder(); + policy.AddRequirements(new PermissionAuthorizationRequirement + { + PolicyName = policyName + }); + return Task.FromResult(policy.Build()); + } + + // If the policy name doesn't match the format expected by this policy provider, + // try the fallback provider. If no fallback provider is used, this would return + // Task.FromResult(null) instead. + return FallbackPolicyProvider.GetPolicyAsync(policyName); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationProvider.cs new file mode 100644 index 00000000..aa339fb4 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationProvider.cs @@ -0,0 +1,21 @@ +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization; + +public class PermissionAuthorizationProvider : IPermissionAuthorizationProvider +{ + private readonly Dictionary> _clientPermissions = + new(StringComparer.InvariantCultureIgnoreCase) + { + { "yao", new[] { Permission.Operation.Read, Permission.Operation.Write } }, + { "jojo", new[] { Permission.Operation.Read} } + }; + + public IEnumerable GetPermissions(string userId) + { + if (this._clientPermissions.TryGetValue(userId, out var result) == false) + { + result = new List(); + } + + return result; + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationRequirement.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationRequirement.cs index 96f2e195..61888f90 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationRequirement.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationRequirement.cs @@ -4,4 +4,5 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization public class PermissionAuthorizationRequirement : IAuthorizationRequirement { + public string PolicyName { get; init; } } \ No newline at end of file From 1ae04203c1afc5789315efa5187366f381c73ea3 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 21 Jun 2022 23:39:49 +0800 Subject: [PATCH 232/424] refactor --- .../Security/Authorization/Permission.cs | 4 ++-- .../Security/Authorization/PermissionAuthorizationHandler.cs | 2 +- .../PermissionAuthorizationMiddlewareResultHandler.cs | 1 - .../Authorization/PermissionAuthorizationPolicyProvider.cs | 2 -- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/Permission.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/Permission.cs index 837cda0c..5af9af80 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/Permission.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/Permission.cs @@ -4,8 +4,8 @@ public class Permission { public class Operation { - public const string Write = $"{nameof(Operation)}.{nameof(Write)}"; - public const string Read = $"{nameof(Operation)}.{nameof(Read)}"; + public const string Write = $"{nameof(Permission)}.{nameof(Operation)}:{nameof(Write)}"; + public const string Read = $"{nameof(Permission)}.{nameof(Operation)}:{nameof(Read)}"; private static readonly Lazy> s_values = new(() => diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationHandler.cs index b1a9288c..fed3f092 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationHandler.cs @@ -4,7 +4,7 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization public class PermissionAuthorizationHandler : AuthorizationHandler { - private IPermissionAuthorizationProvider _authorizationProvider; + private readonly IPermissionAuthorizationProvider _authorizationProvider; public PermissionAuthorizationHandler(IPermissionAuthorizationProvider authorizationProvider) { diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs index a60f2fa8..03e594a0 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs @@ -8,7 +8,6 @@ public class PermissionAuthorizationMiddlewareResultHandler : IAuthorizationMidd { private readonly ILogger _logger; - // private readonly AuthorizationMiddlewareResultHandler _defaultHandler = new(); private readonly JsonSerializerOptions _jsonSerializerOptions; public PermissionAuthorizationMiddlewareResultHandler( diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationPolicyProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationPolicyProvider.cs index 63d1a91c..774f1094 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationPolicyProvider.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationPolicyProvider.cs @@ -5,8 +5,6 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization internal class PermissionAuthorizationPolicyProvider : IAuthorizationPolicyProvider { - const string POLICY_PREFIX = "Permission"; - public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } public PermissionAuthorizationPolicyProvider(IOptions options) From 3499b66d07fb4d1e00582697039d2b2a73201eb7 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 26 Jun 2022 13:53:00 +0800 Subject: [PATCH 233/424] =?UTF-8?q?feat:=20=E5=A5=97=E7=94=A8=E7=AF=84?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../K8sTemplateEngineTests.cs | 28 +++++++++++++++++++ .../Lab.RazorTemplate.Test.csproj | 22 +++++++++++++++ .../Lab.RazorTemplate.Test/Usings.cs | 1 + .../Lab.RazorTemplate/Lab.RazorTemplate.sln | 22 +++++++++++++++ .../Lab.RazorTemplate/K8sTemplateEngine.cs | 15 ++++++++++ .../Lab.RazorTemplate/K8sValue.cs | 22 +++++++++++++++ .../Lab.RazorTemplate.csproj | 15 ++++++++++ .../Template.ConfigMap.cshtml | 9 ++++++ .../BasicAuthenticationHandler.cs | 12 ++++++++ 9 files changed, 146 insertions(+) create mode 100644 Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/K8sTemplateEngineTests.cs create mode 100644 Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/Lab.RazorTemplate.Test.csproj create mode 100644 Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/Usings.cs create mode 100644 Template/Lab.RazorTemplate/Lab.RazorTemplate.sln create mode 100644 Template/Lab.RazorTemplate/Lab.RazorTemplate/K8sTemplateEngine.cs create mode 100644 Template/Lab.RazorTemplate/Lab.RazorTemplate/K8sValue.cs create mode 100644 Template/Lab.RazorTemplate/Lab.RazorTemplate/Lab.RazorTemplate.csproj create mode 100644 Template/Lab.RazorTemplate/Lab.RazorTemplate/Template.ConfigMap.cshtml diff --git a/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/K8sTemplateEngineTests.cs b/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/K8sTemplateEngineTests.cs new file mode 100644 index 00000000..f001f3a6 --- /dev/null +++ b/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/K8sTemplateEngineTests.cs @@ -0,0 +1,28 @@ +namespace Lab.RazorTemplate.Test; + +[TestClass] +public class K8sTemplateEngineTests +{ + [TestMethod] + public async Task 替換範本() + { + var templatePath = "Template.ConfigMap.cshtml"; + var k8sValue = new K8sValue() + { + Common = new Common + { + ProjectName = "member-service-api", + Namespace = "member-service", + }, + }; + var k8sDynamicValues = new Dictionary + { + ["Value1"] = "1", + ["Value2"] = "2", + ["K8S_COMMON_SERVICE_NAME"] = "3", + }; + var engine = new K8sTemplateEngine(); + var result = await engine.RenderAsync(templatePath, k8sValue, k8sDynamicValues); + Console.WriteLine(result); + } +} \ No newline at end of file diff --git a/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/Lab.RazorTemplate.Test.csproj b/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/Lab.RazorTemplate.Test.csproj new file mode 100644 index 00000000..54419e07 --- /dev/null +++ b/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/Lab.RazorTemplate.Test.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + + diff --git a/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/Usings.cs b/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/Template/Lab.RazorTemplate/Lab.RazorTemplate.sln b/Template/Lab.RazorTemplate/Lab.RazorTemplate.sln new file mode 100644 index 00000000..cc567ad9 --- /dev/null +++ b/Template/Lab.RazorTemplate/Lab.RazorTemplate.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.RazorTemplate", "Lab.RazorTemplate\Lab.RazorTemplate.csproj", "{4AC18C9C-3D58-4CD8-99D2-EA5C1ED9B1EA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.RazorTemplate.Test", "Lab.RazorTemplate.Test\Lab.RazorTemplate.Test.csproj", "{9FA27B86-E221-4D17-866F-DE5B2ED983CA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4AC18C9C-3D58-4CD8-99D2-EA5C1ED9B1EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4AC18C9C-3D58-4CD8-99D2-EA5C1ED9B1EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4AC18C9C-3D58-4CD8-99D2-EA5C1ED9B1EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4AC18C9C-3D58-4CD8-99D2-EA5C1ED9B1EA}.Release|Any CPU.Build.0 = Release|Any CPU + {9FA27B86-E221-4D17-866F-DE5B2ED983CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FA27B86-E221-4D17-866F-DE5B2ED983CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FA27B86-E221-4D17-866F-DE5B2ED983CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FA27B86-E221-4D17-866F-DE5B2ED983CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Template/Lab.RazorTemplate/Lab.RazorTemplate/K8sTemplateEngine.cs b/Template/Lab.RazorTemplate/Lab.RazorTemplate/K8sTemplateEngine.cs new file mode 100644 index 00000000..14e417b1 --- /dev/null +++ b/Template/Lab.RazorTemplate/Lab.RazorTemplate/K8sTemplateEngine.cs @@ -0,0 +1,15 @@ +using System.Text; + +namespace Lab.RazorTemplate; + +public class K8sTemplateEngine +{ + public async Task RenderAsync(string templatePath, + K8sValue k8sValue, + Dictionary k8sDynamicValue) + { + return await Razor.Templating.Core.RazorTemplateEngine.RenderAsync(templatePath, + k8sValue, + k8sDynamicValue); + } +} diff --git a/Template/Lab.RazorTemplate/Lab.RazorTemplate/K8sValue.cs b/Template/Lab.RazorTemplate/Lab.RazorTemplate/K8sValue.cs new file mode 100644 index 00000000..795e6fd8 --- /dev/null +++ b/Template/Lab.RazorTemplate/Lab.RazorTemplate/K8sValue.cs @@ -0,0 +1,22 @@ +namespace Lab.RazorTemplate; + +public class K8sValue +{ + public Common Common { get; set; } + + public Resource Resource { get; set; } +} + +public class Common +{ + public string ProjectName { get; set; } + + public string Namespace { get; set; } +} + +public class Resource +{ + public uint CPU { get; set; } + + public uint Memory { get; set; } +} diff --git a/Template/Lab.RazorTemplate/Lab.RazorTemplate/Lab.RazorTemplate.csproj b/Template/Lab.RazorTemplate/Lab.RazorTemplate/Lab.RazorTemplate.csproj new file mode 100644 index 00000000..41c69183 --- /dev/null +++ b/Template/Lab.RazorTemplate/Lab.RazorTemplate/Lab.RazorTemplate.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + true + + + + + + + + diff --git a/Template/Lab.RazorTemplate/Lab.RazorTemplate/Template.ConfigMap.cshtml b/Template/Lab.RazorTemplate/Lab.RazorTemplate/Template.ConfigMap.cshtml new file mode 100644 index 00000000..348b1de3 --- /dev/null +++ b/Template/Lab.RazorTemplate/Lab.RazorTemplate/Template.ConfigMap.cshtml @@ -0,0 +1,9 @@ +@model Lab.RazorTemplate.K8sValue +apiVersion: v1 +kind: ConfigMap +metadata: +name: @Model.Common.ProjectName +namespace: @Model.Common.Namespace +spec1: @ViewData["Value1"] +spec2: @ViewBag.Value2 +spec3: @ViewBag.K8S_COMMON_SERVICE_NAME \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs index 5f85a0e1..41f3d02b 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs @@ -26,6 +26,18 @@ public BasicAuthenticationHandler( this._authenticationProvider = authenticationProvider; } + /// + /// + /// + void CreateTestServer() + { + + } + + void CreateTask() + { + + } protected override async Task HandleAuthenticateAsync() { var schemeName = this.Scheme.Name; //由外部注入 From bc3728a9c0af2f19f2fdae0ea875ad2505727332 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 26 Jun 2022 23:21:04 +0800 Subject: [PATCH 234/424] feat: add other temp --- .../K8sTemplateEngineTests.cs | 30 ++++++++++++++++++ .../Lab.RazorTemplate/EnvTemplate.cshtml | 31 +++++++++++++++++++ .../Lab.RazorTemplate/EnvironmentType.cs | 9 ++++++ .../Lab.RazorTemplate/MarketType.cs | 8 +++++ 4 files changed, 78 insertions(+) create mode 100644 Template/Lab.RazorTemplate/Lab.RazorTemplate/EnvTemplate.cshtml create mode 100644 Template/Lab.RazorTemplate/Lab.RazorTemplate/EnvironmentType.cs create mode 100644 Template/Lab.RazorTemplate/Lab.RazorTemplate/MarketType.cs diff --git a/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/K8sTemplateEngineTests.cs b/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/K8sTemplateEngineTests.cs index f001f3a6..4ddae473 100644 --- a/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/K8sTemplateEngineTests.cs +++ b/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/K8sTemplateEngineTests.cs @@ -1,3 +1,6 @@ +using System.Net; +using System.Text; + namespace Lab.RazorTemplate.Test; [TestClass] @@ -21,6 +24,33 @@ public async Task 替換範本() ["Value2"] = "2", ["K8S_COMMON_SERVICE_NAME"] = "3", }; + + var engine = new K8sTemplateEngine(); + var result = await engine.RenderAsync(templatePath, k8sValue, k8sDynamicValues); + Console.WriteLine(result); + } + + [TestMethod] + public async Task 替換範本aa() + { + var templatePath = "EnvTemplate.cshtml"; + var k8sValue = new K8sValue() + { + Common = new Common + { + ProjectName = "member-service-api", + Namespace = "member-service", + }, + }; + var k8sDynamicValues = new Dictionary + { + ["Market"] = "TW", + ["Environment"] = "Dev", + }; + var response = new HttpResponseMessage(); + response.StatusCode = HttpStatusCode.NoContent; + response.Content = new StringContent("[]", Encoding.UTF8); + var engine = new K8sTemplateEngine(); var result = await engine.RenderAsync(templatePath, k8sValue, k8sDynamicValues); Console.WriteLine(result); diff --git a/Template/Lab.RazorTemplate/Lab.RazorTemplate/EnvTemplate.cshtml b/Template/Lab.RazorTemplate/Lab.RazorTemplate/EnvTemplate.cshtml new file mode 100644 index 00000000..9e599b98 --- /dev/null +++ b/Template/Lab.RazorTemplate/Lab.RazorTemplate/EnvTemplate.cshtml @@ -0,0 +1,31 @@ +@using Lab.RazorTemplate +@model Lab.RazorTemplate.K8sValue +@{ + var _market = ViewBag.Market; + var _environment = ViewBag.Environment; + Console.WriteLine(_environment.ToString()); +} +@{ + var K8S_COMMON_SERVICE_NAME = new Dictionary() + { + { MarketName.TW, "k8s-common-tw" }, + { MarketName.HK, "k8s-common-hk" }, + { MarketName.MY, "k8s-common-my" }, + }; + Console.WriteLine($"{nameof(K8S_COMMON_SERVICE_NAME)} = {K8S_COMMON_SERVICE_NAME}"); +} +K8S_COMMON_SERVICE_NAME= @K8S_COMMON_SERVICE_NAME[_market] +@{ + string NMQ_APIMIN_BASE_URL = null; + if (_environment == EnvironmentName.Dev) + { + NMQ_APIMIN_BASE_URL = "ABC"; + } + else if (_environment == EnvironmentName.QA + && _market == MarketName.TW) + { + NMQ_APIMIN_BASE_URL = "DEF"; + } + Console.WriteLine($"{nameof(NMQ_APIMIN_BASE_URL)} = {NMQ_APIMIN_BASE_URL}"); +} +NMQ_APIMIN_BASE_URL = @NMQ_APIMIN_BASE_URL; \ No newline at end of file diff --git a/Template/Lab.RazorTemplate/Lab.RazorTemplate/EnvironmentType.cs b/Template/Lab.RazorTemplate/Lab.RazorTemplate/EnvironmentType.cs new file mode 100644 index 00000000..0cf65b4a --- /dev/null +++ b/Template/Lab.RazorTemplate/Lab.RazorTemplate/EnvironmentType.cs @@ -0,0 +1,9 @@ +namespace Lab.RazorTemplate; + +public class EnvironmentName +{ + public const string Dev = "Dev"; + public const string Test = "Test"; + public const string QA = "QA"; + public const string Production = "Prod"; +} \ No newline at end of file diff --git a/Template/Lab.RazorTemplate/Lab.RazorTemplate/MarketType.cs b/Template/Lab.RazorTemplate/Lab.RazorTemplate/MarketType.cs new file mode 100644 index 00000000..5948fd30 --- /dev/null +++ b/Template/Lab.RazorTemplate/Lab.RazorTemplate/MarketType.cs @@ -0,0 +1,8 @@ +namespace Lab.RazorTemplate; + +public class MarketName +{ + public const string TW = "TW"; + public const string MY = "MY"; + public const string HK = "HK"; +} \ No newline at end of file From 5e786ca21cc759c17aa5e217cb1cbccf31b40a5d Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 26 Jun 2022 23:30:02 +0800 Subject: [PATCH 235/424] refactor --- .../K8sTemplateEngineTests.cs | 19 +++++-------------- .../Lab.RazorTemplate/EnvTemplate.cshtml | 3 ++- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/K8sTemplateEngineTests.cs b/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/K8sTemplateEngineTests.cs index 4ddae473..d8b241c1 100644 --- a/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/K8sTemplateEngineTests.cs +++ b/Template/Lab.RazorTemplate/Lab.RazorTemplate.Test/K8sTemplateEngineTests.cs @@ -27,32 +27,23 @@ public async Task 替換範本() var engine = new K8sTemplateEngine(); var result = await engine.RenderAsync(templatePath, k8sValue, k8sDynamicValues); - Console.WriteLine(result); + Console.WriteLine($"Render Result:\r\n{result}"); } [TestMethod] - public async Task 替換範本aa() + public async Task 替換範本_1() { var templatePath = "EnvTemplate.cshtml"; - var k8sValue = new K8sValue() - { - Common = new Common - { - ProjectName = "member-service-api", - Namespace = "member-service", - }, - }; + var k8sValue = new K8sValue(); var k8sDynamicValues = new Dictionary { ["Market"] = "TW", ["Environment"] = "Dev", }; - var response = new HttpResponseMessage(); - response.StatusCode = HttpStatusCode.NoContent; - response.Content = new StringContent("[]", Encoding.UTF8); var engine = new K8sTemplateEngine(); var result = await engine.RenderAsync(templatePath, k8sValue, k8sDynamicValues); - Console.WriteLine(result); + Console.WriteLine(); + Console.WriteLine($"Render Result:\r\n{result}"); } } \ No newline at end of file diff --git a/Template/Lab.RazorTemplate/Lab.RazorTemplate/EnvTemplate.cshtml b/Template/Lab.RazorTemplate/Lab.RazorTemplate/EnvTemplate.cshtml index 9e599b98..e039e1a2 100644 --- a/Template/Lab.RazorTemplate/Lab.RazorTemplate/EnvTemplate.cshtml +++ b/Template/Lab.RazorTemplate/Lab.RazorTemplate/EnvTemplate.cshtml @@ -3,7 +3,8 @@ @{ var _market = ViewBag.Market; var _environment = ViewBag.Environment; - Console.WriteLine(_environment.ToString()); + Console.WriteLine($"{nameof(_market)} = {_market}"); + Console.WriteLine($"{nameof(_environment)} = {_environment}"); } @{ var K8S_COMMON_SERVICE_NAME = new Dictionary() From 4f887189c91d27b8b9b713a699bf417906edcccb Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 9 Jul 2022 00:15:13 +0800 Subject: [PATCH 236/424] add minio s3 sample --- .../Lab.Aws.S3.MinIOS3.csproj | 19 ++++++++++++++ AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs | 25 +++++++++++++++++++ AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Usings.cs | 1 + AWS/Lab.AwsS3/Lab.AwsS3.sln | 21 ++++++++++++++++ AWS/Lab.AwsS3/docker-compose.yaml | 17 +++++++++++++ 5 files changed, 83 insertions(+) create mode 100644 AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Lab.Aws.S3.MinIOS3.csproj create mode 100644 AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs create mode 100644 AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Usings.cs create mode 100644 AWS/Lab.AwsS3/Lab.AwsS3.sln create mode 100644 AWS/Lab.AwsS3/docker-compose.yaml diff --git a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Lab.Aws.S3.MinIOS3.csproj b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Lab.Aws.S3.MinIOS3.csproj new file mode 100644 index 00000000..547f14e9 --- /dev/null +++ b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Lab.Aws.S3.MinIOS3.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + diff --git a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs new file mode 100644 index 00000000..6a096e8b --- /dev/null +++ b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs @@ -0,0 +1,25 @@ +using Amazon; +using Amazon.S3; +using Amazon.S3.Model; + +namespace Lab.Aws.S3.MinIOS3; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public async Task 新增一個儲存桶() + { + var s3Config = new AmazonS3Config() + { + RegionEndpoint = RegionEndpoint.USEast1, + ServiceURL = "http://localhost:9000", + ForcePathStyle = true + }; + var s3Client = new AmazonS3Client(s3Config); + var response = await s3Client.PutBucketAsync(new PutBucketRequest + { + BucketName = "test-bucket", + }); + } +} \ No newline at end of file diff --git a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Usings.cs b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/AWS/Lab.AwsS3/Lab.AwsS3.sln b/AWS/Lab.AwsS3/Lab.AwsS3.sln new file mode 100644 index 00000000..c4a97c99 --- /dev/null +++ b/AWS/Lab.AwsS3/Lab.AwsS3.sln @@ -0,0 +1,21 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Aws.S3.MinIOS3", "Lab.Aws.S3.MinIOS3\Lab.Aws.S3.MinIOS3.csproj", "{0FF96B3C-2410-4444-A113-FB2B80E4D940}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{17F77AC4-5953-4CD1-917D-28A4746AB759}" + ProjectSection(SolutionItems) = preProject + docker-compose.yaml = docker-compose.yaml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0FF96B3C-2410-4444-A113-FB2B80E4D940}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0FF96B3C-2410-4444-A113-FB2B80E4D940}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0FF96B3C-2410-4444-A113-FB2B80E4D940}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0FF96B3C-2410-4444-A113-FB2B80E4D940}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/AWS/Lab.AwsS3/docker-compose.yaml b/AWS/Lab.AwsS3/docker-compose.yaml new file mode 100644 index 00000000..018232a7 --- /dev/null +++ b/AWS/Lab.AwsS3/docker-compose.yaml @@ -0,0 +1,17 @@ +version: "3.8" + +services: + s3-minio: + container_name: "s3-minio" + hostname: "minio" + image: minio/minio:latest + volumes: + - ./minio/data:/data + ports: + - "9000:9000" + - "9001:9001" + environment: + # 這裡的 key 要跟 .aws/credentials 裡的 key 名稱一樣,aws cli 才能正常的運作 + MINIO_ROOT_USER: "AKIAIOSFODNN7EXAMPLE" + MINIO_ROOT_PASSWORD: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" + command: server --console-address :9001 /data \ No newline at end of file From b1f55a106844ca7a5a1dfe0e3918f656beae7bc1 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 14 Jul 2022 10:12:29 +0800 Subject: [PATCH 237/424] feat: add yaml and web project --- .../Lab.SpecFirst/AutoGenerated/Controller.cs | 137 +++++ .../Swagger/Lab.SpecFirst/Lab.SpecFirst.sln | 39 ++ WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml | 15 + WebAPI/Swagger/Lab.SpecFirst/doc/index.yaml | 112 ++++ .../AutoGenerated/LabSpecClient.cs | 531 ++++++++++++++++++ .../Lab.SpecFirst.Adapter.csproj | 9 + .../src/Lab.SpecFirst.Web/.dockerignore | 25 + .../AutoGenerated/Controller.cs | 137 +++++ .../Controllers/WeatherForecastController.cs | 32 ++ .../src/Lab.SpecFirst.Web/Dockerfile | 20 + .../Lab.SpecFirst.Web.csproj | 14 + .../src/Lab.SpecFirst.Web/Program.cs | 26 + .../Properties/launchSettings.json | 31 + .../src/Lab.SpecFirst.Web/WeatherForecast.cs | 12 + .../appsettings.Development.json | 8 + .../src/Lab.SpecFirst.Web/appsettings.json | 9 + 16 files changed, 1157 insertions(+) create mode 100644 WebAPI/Swagger/Lab.SpecFirst/AutoGenerated/Controller.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst/Lab.SpecFirst.sln create mode 100644 WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml create mode 100644 WebAPI/Swagger/Lab.SpecFirst/doc/index.yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Adapter/Lab.SpecFirst.Adapter.csproj create mode 100644 WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/.dockerignore create mode 100644 WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Controllers/WeatherForecastController.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Dockerfile create mode 100644 WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj create mode 100644 WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Program.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Properties/launchSettings.json create mode 100644 WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/WeatherForecast.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/appsettings.Development.json create mode 100644 WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/appsettings.json diff --git a/WebAPI/Swagger/Lab.SpecFirst/AutoGenerated/Controller.cs b/WebAPI/Swagger/Lab.SpecFirst/AutoGenerated/Controller.cs new file mode 100644 index 00000000..bb7137b4 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/AutoGenerated/Controller.cs @@ -0,0 +1,137 @@ +//---------------------- +// +// Generated using the NSwag toolchain v13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0)) (http://NSwag.org) +// +//---------------------- + +#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." +#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." +#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' +#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... +#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." +#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" +#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" + +namespace Lab.SpecFirst.Web +{ + using System = global::System; + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public interface ILab.SpecFirst.WebController + { + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + System.Threading.Tasks.Task> ListPetsAsync(int? limit); + + /// Create a pet + /// Null response + System.Threading.Tasks.Task CreatePetsAsync(); + + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + System.Threading.Tasks.Task ShowPetByIdAsync(string petId); + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + [Microsoft.AspNetCore.Mvc.Route("api/")] + public partial class Lab.SpecFirst.WebController : Microsoft.AspNetCore.Mvc.ControllerBase + { + private ILab.SpecFirst.WebController _implementation; + + public Lab.SpecFirst.WebController(ILab.SpecFirst.WebController implementation) + { + _implementation = implementation; + } + + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pets")] + public System.Threading.Tasks.Task> ListPets([Microsoft.AspNetCore.Mvc.FromQuery] int? limit) + { + return _implementation.ListPetsAsync(limit); + } + + /// Create a pet + /// Null response + [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("pets")] + public System.Threading.Tasks.Task CreatePets() + { + return _implementation.CreatePetsAsync(); + } + + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pets/{petId}")] + public System.Threading.Tasks.Task ShowPetById(string petId) + { + return _implementation.ShowPetByIdAsync(petId); + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pet + { + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)] + public long Id { get; set; } + + [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } + + [Newtonsoft.Json.JsonProperty("tag", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Tag { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pets : System.Collections.ObjectModel.Collection + { + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Error + { + [Newtonsoft.Json.JsonProperty("code", Required = Newtonsoft.Json.Required.Always)] + public int Code { get; set; } + + [Newtonsoft.Json.JsonProperty("message", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Message { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + +} + +#pragma warning restore 1591 +#pragma warning restore 1573 +#pragma warning restore 472 +#pragma warning restore 114 +#pragma warning restore 108 +#pragma warning restore 3016 \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst/Lab.SpecFirst.sln b/WebAPI/Swagger/Lab.SpecFirst/Lab.SpecFirst.sln new file mode 100644 index 00000000..9b8579aa --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/Lab.SpecFirst.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{1C8C1BD9-1338-47A0-963B-D35B1AD07476}" + ProjectSection(SolutionItems) = preProject + Taskfile.yml = Taskfile.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{7C405A84-132F-43F2-9F97-388CC40BED1D}" + ProjectSection(SolutionItems) = preProject + doc\index.yaml = doc\index.yaml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F900DCE0-EF45-4629-AA24-949E65BAB714}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SpecFirst.Web", "src\Lab.SpecFirst.Web\Lab.SpecFirst.Web.csproj", "{70299524-F9F4-41DF-80EF-D1CE03C2965A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SpecFirst.Adapter", "src\Lab.SpecFirst.Adapter\Lab.SpecFirst.Adapter.csproj", "{F5AAACC5-781F-41E6-8CD5-39389A00942C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {70299524-F9F4-41DF-80EF-D1CE03C2965A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70299524-F9F4-41DF-80EF-D1CE03C2965A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70299524-F9F4-41DF-80EF-D1CE03C2965A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70299524-F9F4-41DF-80EF-D1CE03C2965A}.Release|Any CPU.Build.0 = Release|Any CPU + {F5AAACC5-781F-41E6-8CD5-39389A00942C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5AAACC5-781F-41E6-8CD5-39389A00942C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5AAACC5-781F-41E6-8CD5-39389A00942C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5AAACC5-781F-41E6-8CD5-39389A00942C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {7C405A84-132F-43F2-9F97-388CC40BED1D} = {1C8C1BD9-1338-47A0-963B-D35B1AD07476} + {70299524-F9F4-41DF-80EF-D1CE03C2965A} = {F900DCE0-EF45-4629-AA24-949E65BAB714} + {F5AAACC5-781F-41E6-8CD5-39389A00942C} = {F900DCE0-EF45-4629-AA24-949E65BAB714} + EndGlobalSection +EndGlobal diff --git a/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml b/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml new file mode 100644 index 00000000..ed4d14b1 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml @@ -0,0 +1,15 @@ +version: "3" + +dotenv: [ "secrets/secrets.env" ] + +tasks: + rest-codegen-client: + desc: 產生 Client Code + cmds: + - nswag openapi2csclient /input:doc/index.yaml /classname:LabSpecClient /namespace:Lab.SpecFirst.Adapter /output:src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs /jsonLibrary:SystemTextJson /generateClientInterfaces:true /exposeJsonSerializerSettings:false /useBaseUrl:false + + + rest-codegen-server: + desc: 產生 Server Code + cmds: + - nswag openapi2cscontroller /input:doc/index.yaml /classname:SpecFirstConteoller /namespace:Lab.SpecFirst.Web /output:src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs /jsonLibrary:SystemTextJson diff --git a/WebAPI/Swagger/Lab.SpecFirst/doc/index.yaml b/WebAPI/Swagger/Lab.SpecFirst/doc/index.yaml new file mode 100644 index 00000000..565bfc49 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/doc/index.yaml @@ -0,0 +1,112 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://localhost:7087/api/ +# - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs new file mode 100644 index 00000000..55ae054c --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs @@ -0,0 +1,531 @@ +//---------------------- +// +// Generated using the NSwag toolchain v13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0)) (http://NSwag.org) +// +//---------------------- + +#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." +#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." +#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' +#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... +#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." +#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" +#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" + +namespace Lab.SpecFirst.Adapter +{ + using System = global::System; + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial interface ILabSpecClient + { + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + /// A server side error occurred. + System.Threading.Tasks.Task> ListPetsAsync(int? limit); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + /// A server side error occurred. + System.Threading.Tasks.Task> ListPetsAsync(int? limit, System.Threading.CancellationToken cancellationToken); + + /// Create a pet + /// Null response + /// A server side error occurred. + System.Threading.Tasks.Task CreatePetsAsync(); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Create a pet + /// Null response + /// A server side error occurred. + System.Threading.Tasks.Task CreatePetsAsync(System.Threading.CancellationToken cancellationToken); + + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + /// A server side error occurred. + System.Threading.Tasks.Task ShowPetByIdAsync(string petId); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + /// A server side error occurred. + System.Threading.Tasks.Task ShowPetByIdAsync(string petId, System.Threading.CancellationToken cancellationToken); + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class LabSpecClient : ILabSpecClient + { + private System.Net.Http.HttpClient _httpClient; + private System.Lazy _settings; + + public LabSpecClient(System.Net.Http.HttpClient httpClient) + { + _httpClient = httpClient; + _settings = new System.Lazy(CreateSerializerSettings); + } + + private System.Text.Json.JsonSerializerOptions CreateSerializerSettings() + { + var settings = new System.Text.Json.JsonSerializerOptions(); + UpdateJsonSerializerSettings(settings); + return settings; + } + + protected System.Text.Json.JsonSerializerOptions JsonSerializerSettings { get { return _settings.Value; } } + + partial void UpdateJsonSerializerSettings(System.Text.Json.JsonSerializerOptions settings); + + + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); + partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + /// A server side error occurred. + public System.Threading.Tasks.Task> ListPetsAsync(int? limit) + { + return ListPetsAsync(limit, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + /// A server side error occurred. + public async System.Threading.Tasks.Task> ListPetsAsync(int? limit, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append("pets?"); + if (limit != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("limit") + "=").Append(System.Uri.EscapeDataString(ConvertToString(limit, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("unexpected error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Create a pet + /// Null response + /// A server side error occurred. + public System.Threading.Tasks.Task CreatePetsAsync() + { + return CreatePetsAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Create a pet + /// Null response + /// A server side error occurred. + public async System.Threading.Tasks.Task CreatePetsAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append("pets"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json"); + request_.Method = new System.Net.Http.HttpMethod("POST"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 201) + { + return; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("unexpected error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + /// A server side error occurred. + public System.Threading.Tasks.Task ShowPetByIdAsync(string petId) + { + return ShowPetByIdAsync(petId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + /// A server side error occurred. + public async System.Threading.Tasks.Task ShowPetByIdAsync(string petId, System.Threading.CancellationToken cancellationToken) + { + if (petId == null) + throw new System.ArgumentNullException("petId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append("pets/{petId}"); + urlBuilder_.Replace("{petId}", System.Uri.EscapeDataString(ConvertToString(petId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("unexpected error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + protected struct ObjectResponseResult + { + public ObjectResponseResult(T responseObject, string responseText) + { + this.Object = responseObject; + this.Text = responseText; + } + + public T Object { get; } + + public string Text { get; } + } + + public bool ReadResponseAsString { get; set; } + + protected virtual async System.Threading.Tasks.Task> ReadObjectResponseAsync(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Threading.CancellationToken cancellationToken) + { + if (response == null || response.Content == null) + { + return new ObjectResponseResult(default(T), string.Empty); + } + + if (ReadResponseAsString) + { + var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + var typedBody = System.Text.Json.JsonSerializer.Deserialize(responseText, JsonSerializerSettings); + return new ObjectResponseResult(typedBody, responseText); + } + catch (System.Text.Json.JsonException exception) + { + var message = "Could not deserialize the response body string as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception); + } + } + else + { + try + { + using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + { + var typedBody = await System.Text.Json.JsonSerializer.DeserializeAsync(responseStream, JsonSerializerSettings, cancellationToken).ConfigureAwait(false); + return new ObjectResponseResult(typedBody, string.Empty); + } + } + catch (System.Text.Json.JsonException exception) + { + var message = "Could not deserialize the response body stream as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception); + } + } + } + + private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo) + { + if (value == null) + { + return ""; + } + + if (value is System.Enum) + { + var name = System.Enum.GetName(value.GetType(), value); + if (name != null) + { + var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); + if (field != null) + { + var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) + as System.Runtime.Serialization.EnumMemberAttribute; + if (attribute != null) + { + return attribute.Value != null ? attribute.Value : name; + } + } + + var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo)); + return converted == null ? string.Empty : converted; + } + } + else if (value is bool) + { + return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant(); + } + else if (value is byte[]) + { + return System.Convert.ToBase64String((byte[]) value); + } + else if (value.GetType().IsArray) + { + var array = System.Linq.Enumerable.OfType((System.Array) value); + return string.Join(",", System.Linq.Enumerable.Select(array, o => ConvertToString(o, cultureInfo))); + } + + var result = System.Convert.ToString(value, cultureInfo); + return result == null ? "" : result; + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pet + { + [System.Text.Json.Serialization.JsonPropertyName("id")] + public long Id { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("tag")] + public string Tag { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pets : System.Collections.ObjectModel.Collection + { + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Error + { + [System.Text.Json.Serialization.JsonPropertyName("code")] + public int Code { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("message")] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Message { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class ApiException : System.Exception + { + public int StatusCode { get; private set; } + + public string Response { get; private set; } + + public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; } + + public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Exception innerException) + : base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + ((response == null) ? "(null)" : response.Substring(0, response.Length >= 512 ? 512 : response.Length)), innerException) + { + StatusCode = statusCode; + Response = response; + Headers = headers; + } + + public override string ToString() + { + return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString()); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class ApiException : ApiException + { + public TResult Result { get; private set; } + + public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, TResult result, System.Exception innerException) + : base(message, statusCode, response, headers, innerException) + { + Result = result; + } + } + +} + +#pragma warning restore 1591 +#pragma warning restore 1573 +#pragma warning restore 472 +#pragma warning restore 114 +#pragma warning restore 108 +#pragma warning restore 3016 \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Adapter/Lab.SpecFirst.Adapter.csproj b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Adapter/Lab.SpecFirst.Adapter.csproj new file mode 100644 index 00000000..eb2460e9 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Adapter/Lab.SpecFirst.Adapter.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/.dockerignore b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/.dockerignore new file mode 100644 index 00000000..cd967fc3 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs new file mode 100644 index 00000000..eee39071 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs @@ -0,0 +1,137 @@ +//---------------------- +// +// Generated using the NSwag toolchain v13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0)) (http://NSwag.org) +// +//---------------------- + +#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." +#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." +#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' +#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... +#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." +#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" +#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" + +namespace Lab.SpecFirst.Web +{ + using System = global::System; + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public interface ISpecFirstConteollerController + { + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + System.Threading.Tasks.Task> ListPetsAsync(int? limit); + + /// Create a pet + /// Null response + System.Threading.Tasks.Task CreatePetsAsync(); + + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + System.Threading.Tasks.Task ShowPetByIdAsync(string petId); + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + [Microsoft.AspNetCore.Mvc.Route("api/")] + public partial class SpecFirstConteollerController : Microsoft.AspNetCore.Mvc.ControllerBase + { + private ISpecFirstConteollerController _implementation; + + public SpecFirstConteollerController(ISpecFirstConteollerController implementation) + { + _implementation = implementation; + } + + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pets")] + public System.Threading.Tasks.Task> ListPets([Microsoft.AspNetCore.Mvc.FromQuery] int? limit) + { + return _implementation.ListPetsAsync(limit); + } + + /// Create a pet + /// Null response + [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("pets")] + public System.Threading.Tasks.Task CreatePets() + { + return _implementation.CreatePetsAsync(); + } + + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pets/{petId}")] + public System.Threading.Tasks.Task ShowPetById(string petId) + { + return _implementation.ShowPetByIdAsync(petId); + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pet + { + [System.Text.Json.Serialization.JsonPropertyName("id")] + public long Id { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("tag")] + public string Tag { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pets : System.Collections.ObjectModel.Collection + { + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Error + { + [System.Text.Json.Serialization.JsonPropertyName("code")] + public int Code { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("message")] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Message { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + +} + +#pragma warning restore 1591 +#pragma warning restore 1573 +#pragma warning restore 472 +#pragma warning restore 114 +#pragma warning restore 108 +#pragma warning restore 3016 \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Controllers/WeatherForecastController.cs b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..f0631a40 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Controllers/WeatherForecastController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.SpecFirst.Web.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Dockerfile b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Dockerfile new file mode 100644 index 00000000..e568de25 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Dockerfile @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj", "Lab.SpecFirst.Web/"] +RUN dotnet restore "src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj" +COPY . . +WORKDIR "/src/Lab.SpecFirst.Web" +RUN dotnet build "Lab.SpecFirst.Web.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Lab.SpecFirst.Web.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Lab.SpecFirst.Web.dll"] diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj new file mode 100644 index 00000000..0db9dec4 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + Linux + + + + + + + diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Program.cs b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Program.cs new file mode 100644 index 00000000..329fe361 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Program.cs @@ -0,0 +1,26 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Properties/launchSettings.json b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Properties/launchSettings.json new file mode 100644 index 00000000..466bd7be --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:9860", + "sslPort": 44313 + } + }, + "profiles": { + "Lab.SpecFirst.Web": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7041;http://localhost:5041", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/WeatherForecast.cs b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/WeatherForecast.cs new file mode 100644 index 00000000..bacb51a3 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace Lab.SpecFirst.Web; + +public class WeatherForecast +{ + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/appsettings.Development.json b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/appsettings.json b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From 4c277b6d70db1fc2f194834ba06a8449550f2e2b Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 17 Jul 2022 00:04:45 +0800 Subject: [PATCH 238/424] add task --- WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml b/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml index ed4d14b1..a2fa967f 100644 --- a/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml +++ b/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml @@ -3,12 +3,16 @@ version: "3" dotenv: [ "secrets/secrets.env" ] tasks: + rest-codegen-code: + cmds: + - task: rest-codegen-client + - task: rest-codegen-server + rest-codegen-client: desc: 產生 Client Code cmds: - nswag openapi2csclient /input:doc/index.yaml /classname:LabSpecClient /namespace:Lab.SpecFirst.Adapter /output:src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs /jsonLibrary:SystemTextJson /generateClientInterfaces:true /exposeJsonSerializerSettings:false /useBaseUrl:false - rest-codegen-server: desc: 產生 Server Code cmds: From 51428f41f78bb653c1d75774f30faa8776540c84 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 17 Jul 2022 00:20:17 +0800 Subject: [PATCH 239/424] refactor --- WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml b/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml index a2fa967f..6913848c 100644 --- a/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml +++ b/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml @@ -4,6 +4,7 @@ dotenv: [ "secrets/secrets.env" ] tasks: rest-codegen-code: + desc: 產生 Client / Server Code cmds: - task: rest-codegen-client - task: rest-codegen-server From 57391bd35703ad6ee9caa3dccf69f09f16d18359 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 17 Jul 2022 00:25:52 +0800 Subject: [PATCH 240/424] remove folder --- .../Lab.SpecFirst/AutoGenerated/Controller.cs | 137 ------------------ 1 file changed, 137 deletions(-) delete mode 100644 WebAPI/Swagger/Lab.SpecFirst/AutoGenerated/Controller.cs diff --git a/WebAPI/Swagger/Lab.SpecFirst/AutoGenerated/Controller.cs b/WebAPI/Swagger/Lab.SpecFirst/AutoGenerated/Controller.cs deleted file mode 100644 index bb7137b4..00000000 --- a/WebAPI/Swagger/Lab.SpecFirst/AutoGenerated/Controller.cs +++ /dev/null @@ -1,137 +0,0 @@ -//---------------------- -// -// Generated using the NSwag toolchain v13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0)) (http://NSwag.org) -// -//---------------------- - -#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." -#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." -#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' -#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... -#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." -#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" -#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" - -namespace Lab.SpecFirst.Web -{ - using System = global::System; - - [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] - public interface ILab.SpecFirst.WebController - { - /// List all pets - /// How many items to return at one time (max 100) - /// A paged array of pets - System.Threading.Tasks.Task> ListPetsAsync(int? limit); - - /// Create a pet - /// Null response - System.Threading.Tasks.Task CreatePetsAsync(); - - /// Info for a specific pet - /// The id of the pet to retrieve - /// Expected response to a valid request - System.Threading.Tasks.Task ShowPetByIdAsync(string petId); - - } - - [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] - [Microsoft.AspNetCore.Mvc.Route("api/")] - public partial class Lab.SpecFirst.WebController : Microsoft.AspNetCore.Mvc.ControllerBase - { - private ILab.SpecFirst.WebController _implementation; - - public Lab.SpecFirst.WebController(ILab.SpecFirst.WebController implementation) - { - _implementation = implementation; - } - - /// List all pets - /// How many items to return at one time (max 100) - /// A paged array of pets - [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pets")] - public System.Threading.Tasks.Task> ListPets([Microsoft.AspNetCore.Mvc.FromQuery] int? limit) - { - return _implementation.ListPetsAsync(limit); - } - - /// Create a pet - /// Null response - [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("pets")] - public System.Threading.Tasks.Task CreatePets() - { - return _implementation.CreatePetsAsync(); - } - - /// Info for a specific pet - /// The id of the pet to retrieve - /// Expected response to a valid request - [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pets/{petId}")] - public System.Threading.Tasks.Task ShowPetById(string petId) - { - return _implementation.ShowPetByIdAsync(petId); - } - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Pet - { - [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)] - public long Id { get; set; } - - [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Always)] - [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] - public string Name { get; set; } - - [Newtonsoft.Json.JsonProperty("tag", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public string Tag { get; set; } - - private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); - - [Newtonsoft.Json.JsonExtensionData] - public System.Collections.Generic.IDictionary AdditionalProperties - { - get { return _additionalProperties; } - set { _additionalProperties = value; } - } - - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Pets : System.Collections.ObjectModel.Collection - { - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Error - { - [Newtonsoft.Json.JsonProperty("code", Required = Newtonsoft.Json.Required.Always)] - public int Code { get; set; } - - [Newtonsoft.Json.JsonProperty("message", Required = Newtonsoft.Json.Required.Always)] - [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] - public string Message { get; set; } - - private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); - - [Newtonsoft.Json.JsonExtensionData] - public System.Collections.Generic.IDictionary AdditionalProperties - { - get { return _additionalProperties; } - set { _additionalProperties = value; } - } - - - } - -} - -#pragma warning restore 1591 -#pragma warning restore 1573 -#pragma warning restore 472 -#pragma warning restore 114 -#pragma warning restore 108 -#pragma warning restore 3016 \ No newline at end of file From d06f4bb992178313339ffb8bc891dd5aa31fedd8 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 17 Jul 2022 02:01:49 +0800 Subject: [PATCH 241/424] impl controlle --- WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml | 2 +- .../AutoGenerated/Controller.cs | 10 ++--- .../Controllers/SpecFirstController.cs | 38 +++++++++++++++++++ .../src/Lab.SpecFirst.Web/Program.cs | 5 ++- 4 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Controllers/SpecFirstController.cs diff --git a/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml b/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml index 6913848c..61cb3a6a 100644 --- a/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml +++ b/WebAPI/Swagger/Lab.SpecFirst/Taskfile.yml @@ -17,4 +17,4 @@ tasks: rest-codegen-server: desc: 產生 Server Code cmds: - - nswag openapi2cscontroller /input:doc/index.yaml /classname:SpecFirstConteoller /namespace:Lab.SpecFirst.Web /output:src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs /jsonLibrary:SystemTextJson + - nswag openapi2cscontroller /input:doc/index.yaml /classname:SpecFirstContract /namespace:Lab.SpecFirst.Web.Controllers /output:src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs /jsonLibrary:SystemTextJson diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs index eee39071..c458dd95 100644 --- a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs @@ -12,12 +12,12 @@ #pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" #pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" -namespace Lab.SpecFirst.Web +namespace Lab.SpecFirst.Web.Controllers { using System = global::System; [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] - public interface ISpecFirstConteollerController + public interface ISpecFirstContractController { /// List all pets /// How many items to return at one time (max 100) @@ -37,11 +37,11 @@ public interface ISpecFirstConteollerController [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] [Microsoft.AspNetCore.Mvc.Route("api/")] - public partial class SpecFirstConteollerController : Microsoft.AspNetCore.Mvc.ControllerBase + public partial class SpecFirstContractController : Microsoft.AspNetCore.Mvc.ControllerBase { - private ISpecFirstConteollerController _implementation; + private ISpecFirstContractController _implementation; - public SpecFirstConteollerController(ISpecFirstConteollerController implementation) + public SpecFirstContractController(ISpecFirstContractController implementation) { _implementation = implementation; } diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Controllers/SpecFirstController.cs b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Controllers/SpecFirstController.cs new file mode 100644 index 00000000..eed7b89a --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Controllers/SpecFirstController.cs @@ -0,0 +1,38 @@ +namespace Lab.SpecFirst.Web.Controllers; + +class SpecFirstController : ISpecFirstContractController +{ + public async Task> ListPetsAsync(int? limit) + { + return new List() + { + new() + { + Id = 1, + Name = "yao", + Tag = "dog", + AdditionalProperties = new Dictionary() + { + } + }, + }; + } + + public async Task CreatePetsAsync() + { + + } + + public async Task ShowPetByIdAsync(string petId) + { + return new() + { + Id = 1, + Name = "yao", + Tag = "dog", + AdditionalProperties = new Dictionary() + { + } + }; + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Program.cs b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Program.cs index 329fe361..8f8bd1ae 100644 --- a/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Program.cs +++ b/WebAPI/Swagger/Lab.SpecFirst/src/Lab.SpecFirst.Web/Program.cs @@ -1,7 +1,10 @@ +using Lab.SpecFirst.Web.Controllers; + var builder = WebApplication.CreateBuilder(args); -// Add services to the container. +builder.Services.AddScoped(); +// Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle From 87721cc8173e7e6971a4685223ae21eb8a50487e Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 19 Jul 2022 00:59:26 +0800 Subject: [PATCH 242/424] from fork lab.specfirst --- .../Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln | 44 ++ WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml | 20 + .../doc/components/parameter - Copy (2).yaml | 112 ++++ .../doc/components/parameter - Copy.yaml | 112 ++++ .../doc/components/parameter.yaml | 112 ++++ WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml | 112 ++++ .../Lab.SpecFirst2/doc/schemas/index.yaml | 19 + .../AutoGenerated/LabSpecClient.cs | 531 ++++++++++++++++++ .../Lab.SpecFirst.Adapter.csproj | 9 + .../src/Lab.SpecFirst.Web/.dockerignore | 25 + .../AutoGenerated/Controller.cs | 137 +++++ .../Controllers/SpecFirstController.cs | 38 ++ .../src/Lab.SpecFirst.Web/Dockerfile | 20 + .../Lab.SpecFirst.Web.csproj | 14 + .../src/Lab.SpecFirst.Web/Program.cs | 29 + .../Properties/launchSettings.json | 31 + .../src/Lab.SpecFirst.Web/WeatherForecast.cs | 12 + .../appsettings.Development.json | 8 + .../src/Lab.SpecFirst.Web/appsettings.json | 9 + 19 files changed, 1394 insertions(+) create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/Lab.SpecFirst.Adapter.csproj create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/.dockerignore create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Controllers/SpecFirstController.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Dockerfile create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Program.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Properties/launchSettings.json create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/WeatherForecast.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.Development.json create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.json diff --git a/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln b/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln new file mode 100644 index 00000000..70b4eaae --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln @@ -0,0 +1,44 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{1C8C1BD9-1338-47A0-963B-D35B1AD07476}" + ProjectSection(SolutionItems) = preProject + Taskfile.yml = Taskfile.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{7C405A84-132F-43F2-9F97-388CC40BED1D}" + ProjectSection(SolutionItems) = preProject + doc\index.yaml = doc\index.yaml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F900DCE0-EF45-4629-AA24-949E65BAB714}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SpecFirst.Web", "src\Lab.SpecFirst.Web\Lab.SpecFirst.Web.csproj", "{70299524-F9F4-41DF-80EF-D1CE03C2965A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SpecFirst.Adapter", "src\Lab.SpecFirst.Adapter\Lab.SpecFirst.Adapter.csproj", "{F5AAACC5-781F-41E6-8CD5-39389A00942C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "schemas", "schemas", "{087A302F-7CB9-411A-A189-33919965A007}" + ProjectSection(SolutionItems) = preProject + doc\schemas\index.yaml = doc\schemas\index.yaml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {70299524-F9F4-41DF-80EF-D1CE03C2965A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70299524-F9F4-41DF-80EF-D1CE03C2965A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70299524-F9F4-41DF-80EF-D1CE03C2965A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70299524-F9F4-41DF-80EF-D1CE03C2965A}.Release|Any CPU.Build.0 = Release|Any CPU + {F5AAACC5-781F-41E6-8CD5-39389A00942C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5AAACC5-781F-41E6-8CD5-39389A00942C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5AAACC5-781F-41E6-8CD5-39389A00942C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5AAACC5-781F-41E6-8CD5-39389A00942C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {70299524-F9F4-41DF-80EF-D1CE03C2965A} = {F900DCE0-EF45-4629-AA24-949E65BAB714} + {F5AAACC5-781F-41E6-8CD5-39389A00942C} = {F900DCE0-EF45-4629-AA24-949E65BAB714} + {087A302F-7CB9-411A-A189-33919965A007} = {7C405A84-132F-43F2-9F97-388CC40BED1D} + EndGlobalSection +EndGlobal diff --git a/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml b/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml new file mode 100644 index 00000000..61cb3a6a --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml @@ -0,0 +1,20 @@ +version: "3" + +dotenv: [ "secrets/secrets.env" ] + +tasks: + rest-codegen-code: + desc: 產生 Client / Server Code + cmds: + - task: rest-codegen-client + - task: rest-codegen-server + + rest-codegen-client: + desc: 產生 Client Code + cmds: + - nswag openapi2csclient /input:doc/index.yaml /classname:LabSpecClient /namespace:Lab.SpecFirst.Adapter /output:src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs /jsonLibrary:SystemTextJson /generateClientInterfaces:true /exposeJsonSerializerSettings:false /useBaseUrl:false + + rest-codegen-server: + desc: 產生 Server Code + cmds: + - nswag openapi2cscontroller /input:doc/index.yaml /classname:SpecFirstContract /namespace:Lab.SpecFirst.Web.Controllers /output:src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs /jsonLibrary:SystemTextJson diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml new file mode 100644 index 00000000..09458854 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml @@ -0,0 +1,112 @@ +openapi: "3.0.3" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://localhost:7087/api/ +# - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml new file mode 100644 index 00000000..09458854 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml @@ -0,0 +1,112 @@ +openapi: "3.0.3" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://localhost:7087/api/ +# - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml new file mode 100644 index 00000000..09458854 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml @@ -0,0 +1,112 @@ +openapi: "3.0.3" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://localhost:7087/api/ +# - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml new file mode 100644 index 00000000..09458854 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml @@ -0,0 +1,112 @@ +openapi: "3.0.3" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://localhost:7087/api/ +# - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml new file mode 100644 index 00000000..48e6371b --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml @@ -0,0 +1,19 @@ +parameters: + petId: + name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + +schemas: + +requestBodies: +responses: + okResponse: + type: object + properties: + data: + example: ok + type: string diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs new file mode 100644 index 00000000..55ae054c --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs @@ -0,0 +1,531 @@ +//---------------------- +// +// Generated using the NSwag toolchain v13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0)) (http://NSwag.org) +// +//---------------------- + +#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." +#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." +#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' +#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... +#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." +#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" +#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" + +namespace Lab.SpecFirst.Adapter +{ + using System = global::System; + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial interface ILabSpecClient + { + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + /// A server side error occurred. + System.Threading.Tasks.Task> ListPetsAsync(int? limit); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + /// A server side error occurred. + System.Threading.Tasks.Task> ListPetsAsync(int? limit, System.Threading.CancellationToken cancellationToken); + + /// Create a pet + /// Null response + /// A server side error occurred. + System.Threading.Tasks.Task CreatePetsAsync(); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Create a pet + /// Null response + /// A server side error occurred. + System.Threading.Tasks.Task CreatePetsAsync(System.Threading.CancellationToken cancellationToken); + + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + /// A server side error occurred. + System.Threading.Tasks.Task ShowPetByIdAsync(string petId); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + /// A server side error occurred. + System.Threading.Tasks.Task ShowPetByIdAsync(string petId, System.Threading.CancellationToken cancellationToken); + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class LabSpecClient : ILabSpecClient + { + private System.Net.Http.HttpClient _httpClient; + private System.Lazy _settings; + + public LabSpecClient(System.Net.Http.HttpClient httpClient) + { + _httpClient = httpClient; + _settings = new System.Lazy(CreateSerializerSettings); + } + + private System.Text.Json.JsonSerializerOptions CreateSerializerSettings() + { + var settings = new System.Text.Json.JsonSerializerOptions(); + UpdateJsonSerializerSettings(settings); + return settings; + } + + protected System.Text.Json.JsonSerializerOptions JsonSerializerSettings { get { return _settings.Value; } } + + partial void UpdateJsonSerializerSettings(System.Text.Json.JsonSerializerOptions settings); + + + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); + partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + /// A server side error occurred. + public System.Threading.Tasks.Task> ListPetsAsync(int? limit) + { + return ListPetsAsync(limit, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + /// A server side error occurred. + public async System.Threading.Tasks.Task> ListPetsAsync(int? limit, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append("pets?"); + if (limit != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("limit") + "=").Append(System.Uri.EscapeDataString(ConvertToString(limit, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("unexpected error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Create a pet + /// Null response + /// A server side error occurred. + public System.Threading.Tasks.Task CreatePetsAsync() + { + return CreatePetsAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Create a pet + /// Null response + /// A server side error occurred. + public async System.Threading.Tasks.Task CreatePetsAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append("pets"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json"); + request_.Method = new System.Net.Http.HttpMethod("POST"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 201) + { + return; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("unexpected error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + /// A server side error occurred. + public System.Threading.Tasks.Task ShowPetByIdAsync(string petId) + { + return ShowPetByIdAsync(petId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + /// A server side error occurred. + public async System.Threading.Tasks.Task ShowPetByIdAsync(string petId, System.Threading.CancellationToken cancellationToken) + { + if (petId == null) + throw new System.ArgumentNullException("petId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append("pets/{petId}"); + urlBuilder_.Replace("{petId}", System.Uri.EscapeDataString(ConvertToString(petId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("unexpected error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + protected struct ObjectResponseResult + { + public ObjectResponseResult(T responseObject, string responseText) + { + this.Object = responseObject; + this.Text = responseText; + } + + public T Object { get; } + + public string Text { get; } + } + + public bool ReadResponseAsString { get; set; } + + protected virtual async System.Threading.Tasks.Task> ReadObjectResponseAsync(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Threading.CancellationToken cancellationToken) + { + if (response == null || response.Content == null) + { + return new ObjectResponseResult(default(T), string.Empty); + } + + if (ReadResponseAsString) + { + var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + var typedBody = System.Text.Json.JsonSerializer.Deserialize(responseText, JsonSerializerSettings); + return new ObjectResponseResult(typedBody, responseText); + } + catch (System.Text.Json.JsonException exception) + { + var message = "Could not deserialize the response body string as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception); + } + } + else + { + try + { + using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + { + var typedBody = await System.Text.Json.JsonSerializer.DeserializeAsync(responseStream, JsonSerializerSettings, cancellationToken).ConfigureAwait(false); + return new ObjectResponseResult(typedBody, string.Empty); + } + } + catch (System.Text.Json.JsonException exception) + { + var message = "Could not deserialize the response body stream as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception); + } + } + } + + private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo) + { + if (value == null) + { + return ""; + } + + if (value is System.Enum) + { + var name = System.Enum.GetName(value.GetType(), value); + if (name != null) + { + var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); + if (field != null) + { + var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) + as System.Runtime.Serialization.EnumMemberAttribute; + if (attribute != null) + { + return attribute.Value != null ? attribute.Value : name; + } + } + + var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo)); + return converted == null ? string.Empty : converted; + } + } + else if (value is bool) + { + return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant(); + } + else if (value is byte[]) + { + return System.Convert.ToBase64String((byte[]) value); + } + else if (value.GetType().IsArray) + { + var array = System.Linq.Enumerable.OfType((System.Array) value); + return string.Join(",", System.Linq.Enumerable.Select(array, o => ConvertToString(o, cultureInfo))); + } + + var result = System.Convert.ToString(value, cultureInfo); + return result == null ? "" : result; + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pet + { + [System.Text.Json.Serialization.JsonPropertyName("id")] + public long Id { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("tag")] + public string Tag { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pets : System.Collections.ObjectModel.Collection + { + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Error + { + [System.Text.Json.Serialization.JsonPropertyName("code")] + public int Code { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("message")] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Message { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class ApiException : System.Exception + { + public int StatusCode { get; private set; } + + public string Response { get; private set; } + + public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; } + + public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Exception innerException) + : base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + ((response == null) ? "(null)" : response.Substring(0, response.Length >= 512 ? 512 : response.Length)), innerException) + { + StatusCode = statusCode; + Response = response; + Headers = headers; + } + + public override string ToString() + { + return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString()); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class ApiException : ApiException + { + public TResult Result { get; private set; } + + public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, TResult result, System.Exception innerException) + : base(message, statusCode, response, headers, innerException) + { + Result = result; + } + } + +} + +#pragma warning restore 1591 +#pragma warning restore 1573 +#pragma warning restore 472 +#pragma warning restore 114 +#pragma warning restore 108 +#pragma warning restore 3016 \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/Lab.SpecFirst.Adapter.csproj b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/Lab.SpecFirst.Adapter.csproj new file mode 100644 index 00000000..eb2460e9 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/Lab.SpecFirst.Adapter.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/.dockerignore b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/.dockerignore new file mode 100644 index 00000000..cd967fc3 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs new file mode 100644 index 00000000..c458dd95 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs @@ -0,0 +1,137 @@ +//---------------------- +// +// Generated using the NSwag toolchain v13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0)) (http://NSwag.org) +// +//---------------------- + +#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." +#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." +#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' +#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... +#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." +#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" +#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" + +namespace Lab.SpecFirst.Web.Controllers +{ + using System = global::System; + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public interface ISpecFirstContractController + { + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + System.Threading.Tasks.Task> ListPetsAsync(int? limit); + + /// Create a pet + /// Null response + System.Threading.Tasks.Task CreatePetsAsync(); + + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + System.Threading.Tasks.Task ShowPetByIdAsync(string petId); + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + [Microsoft.AspNetCore.Mvc.Route("api/")] + public partial class SpecFirstContractController : Microsoft.AspNetCore.Mvc.ControllerBase + { + private ISpecFirstContractController _implementation; + + public SpecFirstContractController(ISpecFirstContractController implementation) + { + _implementation = implementation; + } + + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pets")] + public System.Threading.Tasks.Task> ListPets([Microsoft.AspNetCore.Mvc.FromQuery] int? limit) + { + return _implementation.ListPetsAsync(limit); + } + + /// Create a pet + /// Null response + [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("pets")] + public System.Threading.Tasks.Task CreatePets() + { + return _implementation.CreatePetsAsync(); + } + + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pets/{petId}")] + public System.Threading.Tasks.Task ShowPetById(string petId) + { + return _implementation.ShowPetByIdAsync(petId); + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pet + { + [System.Text.Json.Serialization.JsonPropertyName("id")] + public long Id { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("tag")] + public string Tag { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pets : System.Collections.ObjectModel.Collection + { + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Error + { + [System.Text.Json.Serialization.JsonPropertyName("code")] + public int Code { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("message")] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Message { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + +} + +#pragma warning restore 1591 +#pragma warning restore 1573 +#pragma warning restore 472 +#pragma warning restore 114 +#pragma warning restore 108 +#pragma warning restore 3016 \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Controllers/SpecFirstController.cs b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Controllers/SpecFirstController.cs new file mode 100644 index 00000000..eed7b89a --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Controllers/SpecFirstController.cs @@ -0,0 +1,38 @@ +namespace Lab.SpecFirst.Web.Controllers; + +class SpecFirstController : ISpecFirstContractController +{ + public async Task> ListPetsAsync(int? limit) + { + return new List() + { + new() + { + Id = 1, + Name = "yao", + Tag = "dog", + AdditionalProperties = new Dictionary() + { + } + }, + }; + } + + public async Task CreatePetsAsync() + { + + } + + public async Task ShowPetByIdAsync(string petId) + { + return new() + { + Id = 1, + Name = "yao", + Tag = "dog", + AdditionalProperties = new Dictionary() + { + } + }; + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Dockerfile b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Dockerfile new file mode 100644 index 00000000..e568de25 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Dockerfile @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj", "Lab.SpecFirst.Web/"] +RUN dotnet restore "src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj" +COPY . . +WORKDIR "/src/Lab.SpecFirst.Web" +RUN dotnet build "Lab.SpecFirst.Web.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Lab.SpecFirst.Web.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Lab.SpecFirst.Web.dll"] diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj new file mode 100644 index 00000000..0db9dec4 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + Linux + + + + + + + diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Program.cs b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Program.cs new file mode 100644 index 00000000..8f8bd1ae --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Program.cs @@ -0,0 +1,29 @@ +using Lab.SpecFirst.Web.Controllers; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddScoped(); + +// Add services to the container. +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Properties/launchSettings.json b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Properties/launchSettings.json new file mode 100644 index 00000000..466bd7be --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:9860", + "sslPort": 44313 + } + }, + "profiles": { + "Lab.SpecFirst.Web": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7041;http://localhost:5041", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/WeatherForecast.cs b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/WeatherForecast.cs new file mode 100644 index 00000000..bacb51a3 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace Lab.SpecFirst.Web; + +public class WeatherForecast +{ + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.Development.json b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.json b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From e9f6b44d9a12fbdce1e946acd6fa49b969f74aa5 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 19 Jul 2022 10:02:56 +0800 Subject: [PATCH 243/424] feat: extra schema module --- .../Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln | 8 ++ .../Lab.SpecFirst2/doc/components/index.yaml | 9 ++ .../doc/components/parameter - Copy (2).yaml | 112 ------------------ .../doc/components/parameter - Copy.yaml | 112 ------------------ .../doc/components/parameter.yaml | 112 ------------------ .../doc/components/parameters.yaml | 8 ++ .../doc/components/schemas.yaml | 30 +++++ WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml | 48 ++------ 8 files changed, 62 insertions(+), 377 deletions(-) create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/index.yaml delete mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml delete mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml delete mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameters.yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/schemas.yaml diff --git a/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln b/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln index 70b4eaae..58347b91 100644 --- a/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln +++ b/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln @@ -21,6 +21,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "schemas", "schemas", "{087A doc\schemas\index.yaml = doc\schemas\index.yaml EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "components", "components", "{4D3DDFBF-E6C5-4E38-8FC2-AE1C10450670}" + ProjectSection(SolutionItems) = preProject + doc\components\index.yaml = doc\components\index.yaml + doc\components\parameters.yaml = doc\components\parameters.yaml + doc\components\schemas.yaml = doc\components\schemas.yaml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -40,5 +47,6 @@ Global {70299524-F9F4-41DF-80EF-D1CE03C2965A} = {F900DCE0-EF45-4629-AA24-949E65BAB714} {F5AAACC5-781F-41E6-8CD5-39389A00942C} = {F900DCE0-EF45-4629-AA24-949E65BAB714} {087A302F-7CB9-411A-A189-33919965A007} = {7C405A84-132F-43F2-9F97-388CC40BED1D} + {4D3DDFBF-E6C5-4E38-8FC2-AE1C10450670} = {7C405A84-132F-43F2-9F97-388CC40BED1D} EndGlobalSection EndGlobal diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/index.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/index.yaml new file mode 100644 index 00000000..21c814fc --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/index.yaml @@ -0,0 +1,9 @@ +parameters: + petId: + name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + example: 1 \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml deleted file mode 100644 index 09458854..00000000 --- a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml +++ /dev/null @@ -1,112 +0,0 @@ -openapi: "3.0.3" -info: - version: 1.0.0 - title: Swagger Petstore - license: - name: MIT -servers: - - url: http://localhost:7087/api/ -# - url: http://petstore.swagger.io/v1 -paths: - /pets: - get: - summary: List all pets - operationId: listPets - tags: - - pets - parameters: - - name: limit - in: query - description: How many items to return at one time (max 100) - required: false - schema: - type: integer - format: int32 - responses: - '200': - description: A paged array of pets - headers: - x-next: - description: A link to the next page of responses - schema: - type: string - content: - application/json: - schema: - $ref: "#/components/schemas/Pets" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - post: - summary: Create a pet - operationId: createPets - tags: - - pets - responses: - '201': - description: Null response - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - /pets/{petId}: - get: - summary: Info for a specific pet - operationId: showPetById - tags: - - pets - parameters: - - name: petId - in: path - required: true - description: The id of the pet to retrieve - schema: - type: string - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - $ref: "#/components/schemas/Pet" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" -components: - schemas: - Pet: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int64 - name: - type: string - tag: - type: string - Pets: - type: array - items: - $ref: "#/components/schemas/Pet" - Error: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml deleted file mode 100644 index 09458854..00000000 --- a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml +++ /dev/null @@ -1,112 +0,0 @@ -openapi: "3.0.3" -info: - version: 1.0.0 - title: Swagger Petstore - license: - name: MIT -servers: - - url: http://localhost:7087/api/ -# - url: http://petstore.swagger.io/v1 -paths: - /pets: - get: - summary: List all pets - operationId: listPets - tags: - - pets - parameters: - - name: limit - in: query - description: How many items to return at one time (max 100) - required: false - schema: - type: integer - format: int32 - responses: - '200': - description: A paged array of pets - headers: - x-next: - description: A link to the next page of responses - schema: - type: string - content: - application/json: - schema: - $ref: "#/components/schemas/Pets" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - post: - summary: Create a pet - operationId: createPets - tags: - - pets - responses: - '201': - description: Null response - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - /pets/{petId}: - get: - summary: Info for a specific pet - operationId: showPetById - tags: - - pets - parameters: - - name: petId - in: path - required: true - description: The id of the pet to retrieve - schema: - type: string - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - $ref: "#/components/schemas/Pet" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" -components: - schemas: - Pet: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int64 - name: - type: string - tag: - type: string - Pets: - type: array - items: - $ref: "#/components/schemas/Pet" - Error: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml deleted file mode 100644 index 09458854..00000000 --- a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml +++ /dev/null @@ -1,112 +0,0 @@ -openapi: "3.0.3" -info: - version: 1.0.0 - title: Swagger Petstore - license: - name: MIT -servers: - - url: http://localhost:7087/api/ -# - url: http://petstore.swagger.io/v1 -paths: - /pets: - get: - summary: List all pets - operationId: listPets - tags: - - pets - parameters: - - name: limit - in: query - description: How many items to return at one time (max 100) - required: false - schema: - type: integer - format: int32 - responses: - '200': - description: A paged array of pets - headers: - x-next: - description: A link to the next page of responses - schema: - type: string - content: - application/json: - schema: - $ref: "#/components/schemas/Pets" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - post: - summary: Create a pet - operationId: createPets - tags: - - pets - responses: - '201': - description: Null response - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - /pets/{petId}: - get: - summary: Info for a specific pet - operationId: showPetById - tags: - - pets - parameters: - - name: petId - in: path - required: true - description: The id of the pet to retrieve - schema: - type: string - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - $ref: "#/components/schemas/Pet" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" -components: - schemas: - Pet: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int64 - name: - type: string - tag: - type: string - Pets: - type: array - items: - $ref: "#/components/schemas/Pet" - Error: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameters.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameters.yaml new file mode 100644 index 00000000..b8dcfa9a --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameters.yaml @@ -0,0 +1,8 @@ +petId: + name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + example: 1 \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/schemas.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/schemas.yaml new file mode 100644 index 00000000..bf11a600 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/schemas.yaml @@ -0,0 +1,30 @@ +Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + +Pets: + type: array + items: + $ref: "#/Pet" + +Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml index 09458854..04dd77db 100644 --- a/WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml @@ -33,13 +33,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Pets" + $ref: "components/schemas.yaml#/Pets" + default: description: unexpected error content: application/json: schema: - $ref: "#/components/schemas/Error" + $ref: 'components/schemas.yaml#/Error' post: summary: Create a pet operationId: createPets @@ -53,7 +54,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Error" + $ref: "components/schemas.yaml#/Error" /pets/{petId}: get: summary: Info for a specific pet @@ -61,52 +62,17 @@ paths: tags: - pets parameters: - - name: petId - in: path - required: true - description: The id of the pet to retrieve - schema: - type: string + - $ref: "components/parameters.yaml#/petId" responses: '200': description: Expected response to a valid request content: application/json: schema: - $ref: "#/components/schemas/Pet" + $ref: "components/schemas.yaml#/Pet" default: description: unexpected error content: application/json: schema: - $ref: "#/components/schemas/Error" -components: - schemas: - Pet: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int64 - name: - type: string - tag: - type: string - Pets: - type: array - items: - $ref: "#/components/schemas/Pet" - Error: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string \ No newline at end of file + $ref: "components/schemas.yaml#/Error" \ No newline at end of file From a857da061ebe68b6091de6b69da10e3dcf6d5771 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 19 Jul 2022 23:52:09 +0800 Subject: [PATCH 244/424] feat: merge file --- .../Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln | 13 ++++++------- WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml | 8 ++++++++ .../Lab.SpecFirst2/doc/schemas/index.yaml | 19 ------------------- 3 files changed, 14 insertions(+), 26 deletions(-) delete mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml diff --git a/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln b/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln index 58347b91..37c434a3 100644 --- a/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln +++ b/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln @@ -16,18 +16,17 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SpecFirst.Web", "src\La EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SpecFirst.Adapter", "src\Lab.SpecFirst.Adapter\Lab.SpecFirst.Adapter.csproj", "{F5AAACC5-781F-41E6-8CD5-39389A00942C}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "schemas", "schemas", "{087A302F-7CB9-411A-A189-33919965A007}" - ProjectSection(SolutionItems) = preProject - doc\schemas\index.yaml = doc\schemas\index.yaml - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "components", "components", "{4D3DDFBF-E6C5-4E38-8FC2-AE1C10450670}" ProjectSection(SolutionItems) = preProject - doc\components\index.yaml = doc\components\index.yaml doc\components\parameters.yaml = doc\components\parameters.yaml doc\components\schemas.yaml = doc\components\schemas.yaml EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "merge", "merge", "{F28E0DEE-5774-42CF-ABE2-2654AF751215}" + ProjectSection(SolutionItems) = preProject + doc\merge\index.yaml = doc\merge\index.yaml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -46,7 +45,7 @@ Global GlobalSection(NestedProjects) = preSolution {70299524-F9F4-41DF-80EF-D1CE03C2965A} = {F900DCE0-EF45-4629-AA24-949E65BAB714} {F5AAACC5-781F-41E6-8CD5-39389A00942C} = {F900DCE0-EF45-4629-AA24-949E65BAB714} - {087A302F-7CB9-411A-A189-33919965A007} = {7C405A84-132F-43F2-9F97-388CC40BED1D} {4D3DDFBF-E6C5-4E38-8FC2-AE1C10450670} = {7C405A84-132F-43F2-9F97-388CC40BED1D} + {F28E0DEE-5774-42CF-ABE2-2654AF751215} = {7C405A84-132F-43F2-9F97-388CC40BED1D} EndGlobalSection EndGlobal diff --git a/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml b/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml index 61cb3a6a..fe24c234 100644 --- a/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml +++ b/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml @@ -18,3 +18,11 @@ tasks: desc: 產生 Server Code cmds: - nswag openapi2cscontroller /input:doc/index.yaml /classname:SpecFirstContract /namespace:Lab.SpecFirst.Web.Controllers /output:src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs /jsonLibrary:SystemTextJson + + merge-swagger-file: + desc: 合併 Swagger File + cmds: +# - speccy resolve ./doc/index.yaml -o ./doc/merge/index.yaml +# - swagger-cli bundle ./doc/index.yaml --outfile ./doc/merge/index.yaml --type yaml + - openapi-merger -i ./doc/index.yaml -o ./doc/merge/index.yaml + \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml deleted file mode 100644 index 48e6371b..00000000 --- a/WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml +++ /dev/null @@ -1,19 +0,0 @@ -parameters: - petId: - name: petId - in: path - required: true - description: The id of the pet to retrieve - schema: - type: string - -schemas: - -requestBodies: -responses: - okResponse: - type: object - properties: - data: - example: ok - type: string From 65158ec807ff1954165da1c3bb9740ad8fd2cc67 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 20 Jul 2022 09:36:15 +0800 Subject: [PATCH 245/424] modify task --- WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml | 17 +-- .../Lab.SpecFirst2/doc/merge/index.yaml | 115 ++++++++++++++++++ 2 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/merge/index.yaml diff --git a/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml b/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml index fe24c234..ae1af0b1 100644 --- a/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml +++ b/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml @@ -3,23 +3,24 @@ version: "3" dotenv: [ "secrets/secrets.env" ] tasks: - rest-codegen-code: + spec-codegen: desc: 產生 Client / Server Code cmds: - - task: rest-codegen-client - - task: rest-codegen-server + - task: spec-merge-file + - task: spec-codegen-client + - task: spec-codegen-server - rest-codegen-client: + spec-codegen-client: desc: 產生 Client Code cmds: - - nswag openapi2csclient /input:doc/index.yaml /classname:LabSpecClient /namespace:Lab.SpecFirst.Adapter /output:src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs /jsonLibrary:SystemTextJson /generateClientInterfaces:true /exposeJsonSerializerSettings:false /useBaseUrl:false + - nswag openapi2csclient /input:doc/merge/index.yaml /classname:LabSpecClient /namespace:Lab.SpecFirst.Adapter /output:src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs /jsonLibrary:SystemTextJson /generateClientInterfaces:true /exposeJsonSerializerSettings:false /useBaseUrl:false - rest-codegen-server: + spec-codegen-server: desc: 產生 Server Code cmds: - - nswag openapi2cscontroller /input:doc/index.yaml /classname:SpecFirstContract /namespace:Lab.SpecFirst.Web.Controllers /output:src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs /jsonLibrary:SystemTextJson + - nswag openapi2cscontroller /input:doc/merge/index.yaml /classname:SpecFirstContract /namespace:Lab.SpecFirst.Web.Controllers /output:src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs /jsonLibrary:SystemTextJson - merge-swagger-file: + spec-merge-file: desc: 合併 Swagger File cmds: # - speccy resolve ./doc/index.yaml -o ./doc/merge/index.yaml diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/merge/index.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/merge/index.yaml new file mode 100644 index 00000000..8b5b6d4d --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/merge/index.yaml @@ -0,0 +1,115 @@ +openapi: 3.0.3 +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: 'http://localhost:7087/api/' +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Pets' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '/pets/{petId}': + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - $ref: '#/components/parameters/petId' + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +components: + parameters: + petId: + name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + example: 1 + schemas: + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: '#/components/schemas/Pet' From 85f9f3b4ee9bdbe477e1c3aca006c83cef802ee7 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 20 Jul 2022 09:44:01 +0800 Subject: [PATCH 246/424] regen --- .../AutoGenerated/LabSpecClient.cs | 38 +++++++++---------- .../AutoGenerated/Controller.cs | 38 +++++++++---------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs index 55ae054c..1aecf161 100644 --- a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs @@ -434,17 +434,14 @@ private string ConvertToString(object value, System.Globalization.CultureInfo cu } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Pet + public partial class Error { - [System.Text.Json.Serialization.JsonPropertyName("id")] - public long Id { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("code")] + public int Code { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.Text.Json.Serialization.JsonPropertyName("message")] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] - public string Name { get; set; } - - [System.Text.Json.Serialization.JsonPropertyName("tag")] - public string Tag { get; set; } + public string Message { get; set; } private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); @@ -459,20 +456,17 @@ public System.Collections.Generic.IDictionary AdditionalProperti } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Pets : System.Collections.ObjectModel.Collection - { - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Error + public partial class Pet { - [System.Text.Json.Serialization.JsonPropertyName("code")] - public int Code { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("id")] + public long Id { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("message")] + [System.Text.Json.Serialization.JsonPropertyName("name")] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] - public string Message { get; set; } + public string Name { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("tag")] + public string Tag { get; set; } private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); @@ -484,6 +478,12 @@ public System.Collections.Generic.IDictionary AdditionalProperti } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pets : System.Collections.ObjectModel.Collection + { + } [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs index c458dd95..ddb19bf6 100644 --- a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs @@ -75,17 +75,14 @@ public System.Threading.Tasks.Task ShowPetById(string petId) } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Pet + public partial class Error { - [System.Text.Json.Serialization.JsonPropertyName("id")] - public long Id { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("code")] + public int Code { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.Text.Json.Serialization.JsonPropertyName("message")] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] - public string Name { get; set; } - - [System.Text.Json.Serialization.JsonPropertyName("tag")] - public string Tag { get; set; } + public string Message { get; set; } private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); @@ -100,20 +97,17 @@ public System.Collections.Generic.IDictionary AdditionalProperti } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Pets : System.Collections.ObjectModel.Collection - { - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Error + public partial class Pet { - [System.Text.Json.Serialization.JsonPropertyName("code")] - public int Code { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("id")] + public long Id { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("message")] + [System.Text.Json.Serialization.JsonPropertyName("name")] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] - public string Message { get; set; } + public string Name { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("tag")] + public string Tag { get; set; } private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); @@ -126,6 +120,12 @@ public System.Collections.Generic.IDictionary AdditionalProperti } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pets : System.Collections.ObjectModel.Collection + { + + } } From 1e35d1d40e2f2266b121b588c3a7381cb676be21 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 19 Jul 2022 00:59:26 +0800 Subject: [PATCH 247/424] from fork lab.specfirst --- .../Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln | 44 ++ WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml | 20 + .../doc/components/parameter - Copy (2).yaml | 112 ++++ .../doc/components/parameter - Copy.yaml | 112 ++++ .../doc/components/parameter.yaml | 112 ++++ WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml | 112 ++++ .../Lab.SpecFirst2/doc/schemas/index.yaml | 19 + .../AutoGenerated/LabSpecClient.cs | 531 ++++++++++++++++++ .../Lab.SpecFirst.Adapter.csproj | 9 + .../src/Lab.SpecFirst.Web/.dockerignore | 25 + .../AutoGenerated/Controller.cs | 137 +++++ .../Controllers/SpecFirstController.cs | 38 ++ .../src/Lab.SpecFirst.Web/Dockerfile | 20 + .../Lab.SpecFirst.Web.csproj | 14 + .../src/Lab.SpecFirst.Web/Program.cs | 29 + .../Properties/launchSettings.json | 31 + .../src/Lab.SpecFirst.Web/WeatherForecast.cs | 12 + .../appsettings.Development.json | 8 + .../src/Lab.SpecFirst.Web/appsettings.json | 9 + 19 files changed, 1394 insertions(+) create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/Lab.SpecFirst.Adapter.csproj create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/.dockerignore create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Controllers/SpecFirstController.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Dockerfile create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Program.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Properties/launchSettings.json create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/WeatherForecast.cs create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.Development.json create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.json diff --git a/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln b/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln new file mode 100644 index 00000000..70b4eaae --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln @@ -0,0 +1,44 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{1C8C1BD9-1338-47A0-963B-D35B1AD07476}" + ProjectSection(SolutionItems) = preProject + Taskfile.yml = Taskfile.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{7C405A84-132F-43F2-9F97-388CC40BED1D}" + ProjectSection(SolutionItems) = preProject + doc\index.yaml = doc\index.yaml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F900DCE0-EF45-4629-AA24-949E65BAB714}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SpecFirst.Web", "src\Lab.SpecFirst.Web\Lab.SpecFirst.Web.csproj", "{70299524-F9F4-41DF-80EF-D1CE03C2965A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SpecFirst.Adapter", "src\Lab.SpecFirst.Adapter\Lab.SpecFirst.Adapter.csproj", "{F5AAACC5-781F-41E6-8CD5-39389A00942C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "schemas", "schemas", "{087A302F-7CB9-411A-A189-33919965A007}" + ProjectSection(SolutionItems) = preProject + doc\schemas\index.yaml = doc\schemas\index.yaml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {70299524-F9F4-41DF-80EF-D1CE03C2965A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70299524-F9F4-41DF-80EF-D1CE03C2965A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70299524-F9F4-41DF-80EF-D1CE03C2965A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70299524-F9F4-41DF-80EF-D1CE03C2965A}.Release|Any CPU.Build.0 = Release|Any CPU + {F5AAACC5-781F-41E6-8CD5-39389A00942C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5AAACC5-781F-41E6-8CD5-39389A00942C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5AAACC5-781F-41E6-8CD5-39389A00942C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5AAACC5-781F-41E6-8CD5-39389A00942C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {70299524-F9F4-41DF-80EF-D1CE03C2965A} = {F900DCE0-EF45-4629-AA24-949E65BAB714} + {F5AAACC5-781F-41E6-8CD5-39389A00942C} = {F900DCE0-EF45-4629-AA24-949E65BAB714} + {087A302F-7CB9-411A-A189-33919965A007} = {7C405A84-132F-43F2-9F97-388CC40BED1D} + EndGlobalSection +EndGlobal diff --git a/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml b/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml new file mode 100644 index 00000000..61cb3a6a --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml @@ -0,0 +1,20 @@ +version: "3" + +dotenv: [ "secrets/secrets.env" ] + +tasks: + rest-codegen-code: + desc: 產生 Client / Server Code + cmds: + - task: rest-codegen-client + - task: rest-codegen-server + + rest-codegen-client: + desc: 產生 Client Code + cmds: + - nswag openapi2csclient /input:doc/index.yaml /classname:LabSpecClient /namespace:Lab.SpecFirst.Adapter /output:src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs /jsonLibrary:SystemTextJson /generateClientInterfaces:true /exposeJsonSerializerSettings:false /useBaseUrl:false + + rest-codegen-server: + desc: 產生 Server Code + cmds: + - nswag openapi2cscontroller /input:doc/index.yaml /classname:SpecFirstContract /namespace:Lab.SpecFirst.Web.Controllers /output:src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs /jsonLibrary:SystemTextJson diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml new file mode 100644 index 00000000..09458854 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml @@ -0,0 +1,112 @@ +openapi: "3.0.3" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://localhost:7087/api/ +# - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml new file mode 100644 index 00000000..09458854 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml @@ -0,0 +1,112 @@ +openapi: "3.0.3" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://localhost:7087/api/ +# - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml new file mode 100644 index 00000000..09458854 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml @@ -0,0 +1,112 @@ +openapi: "3.0.3" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://localhost:7087/api/ +# - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml new file mode 100644 index 00000000..09458854 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml @@ -0,0 +1,112 @@ +openapi: "3.0.3" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://localhost:7087/api/ +# - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml new file mode 100644 index 00000000..48e6371b --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml @@ -0,0 +1,19 @@ +parameters: + petId: + name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + +schemas: + +requestBodies: +responses: + okResponse: + type: object + properties: + data: + example: ok + type: string diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs new file mode 100644 index 00000000..55ae054c --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs @@ -0,0 +1,531 @@ +//---------------------- +// +// Generated using the NSwag toolchain v13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0)) (http://NSwag.org) +// +//---------------------- + +#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." +#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." +#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' +#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... +#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." +#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" +#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" + +namespace Lab.SpecFirst.Adapter +{ + using System = global::System; + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial interface ILabSpecClient + { + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + /// A server side error occurred. + System.Threading.Tasks.Task> ListPetsAsync(int? limit); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + /// A server side error occurred. + System.Threading.Tasks.Task> ListPetsAsync(int? limit, System.Threading.CancellationToken cancellationToken); + + /// Create a pet + /// Null response + /// A server side error occurred. + System.Threading.Tasks.Task CreatePetsAsync(); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Create a pet + /// Null response + /// A server side error occurred. + System.Threading.Tasks.Task CreatePetsAsync(System.Threading.CancellationToken cancellationToken); + + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + /// A server side error occurred. + System.Threading.Tasks.Task ShowPetByIdAsync(string petId); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + /// A server side error occurred. + System.Threading.Tasks.Task ShowPetByIdAsync(string petId, System.Threading.CancellationToken cancellationToken); + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class LabSpecClient : ILabSpecClient + { + private System.Net.Http.HttpClient _httpClient; + private System.Lazy _settings; + + public LabSpecClient(System.Net.Http.HttpClient httpClient) + { + _httpClient = httpClient; + _settings = new System.Lazy(CreateSerializerSettings); + } + + private System.Text.Json.JsonSerializerOptions CreateSerializerSettings() + { + var settings = new System.Text.Json.JsonSerializerOptions(); + UpdateJsonSerializerSettings(settings); + return settings; + } + + protected System.Text.Json.JsonSerializerOptions JsonSerializerSettings { get { return _settings.Value; } } + + partial void UpdateJsonSerializerSettings(System.Text.Json.JsonSerializerOptions settings); + + + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); + partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + /// A server side error occurred. + public System.Threading.Tasks.Task> ListPetsAsync(int? limit) + { + return ListPetsAsync(limit, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + /// A server side error occurred. + public async System.Threading.Tasks.Task> ListPetsAsync(int? limit, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append("pets?"); + if (limit != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("limit") + "=").Append(System.Uri.EscapeDataString(ConvertToString(limit, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + } + urlBuilder_.Length--; + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("unexpected error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Create a pet + /// Null response + /// A server side error occurred. + public System.Threading.Tasks.Task CreatePetsAsync() + { + return CreatePetsAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Create a pet + /// Null response + /// A server side error occurred. + public async System.Threading.Tasks.Task CreatePetsAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append("pets"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json"); + request_.Method = new System.Net.Http.HttpMethod("POST"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 201) + { + return; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("unexpected error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + /// A server side error occurred. + public System.Threading.Tasks.Task ShowPetByIdAsync(string petId) + { + return ShowPetByIdAsync(petId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + /// A server side error occurred. + public async System.Threading.Tasks.Task ShowPetByIdAsync(string petId, System.Threading.CancellationToken cancellationToken) + { + if (petId == null) + throw new System.ArgumentNullException("petId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append("pets/{petId}"); + urlBuilder_.Replace("{petId}", System.Uri.EscapeDataString(ConvertToString(petId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("unexpected error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + protected struct ObjectResponseResult + { + public ObjectResponseResult(T responseObject, string responseText) + { + this.Object = responseObject; + this.Text = responseText; + } + + public T Object { get; } + + public string Text { get; } + } + + public bool ReadResponseAsString { get; set; } + + protected virtual async System.Threading.Tasks.Task> ReadObjectResponseAsync(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Threading.CancellationToken cancellationToken) + { + if (response == null || response.Content == null) + { + return new ObjectResponseResult(default(T), string.Empty); + } + + if (ReadResponseAsString) + { + var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + var typedBody = System.Text.Json.JsonSerializer.Deserialize(responseText, JsonSerializerSettings); + return new ObjectResponseResult(typedBody, responseText); + } + catch (System.Text.Json.JsonException exception) + { + var message = "Could not deserialize the response body string as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception); + } + } + else + { + try + { + using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + { + var typedBody = await System.Text.Json.JsonSerializer.DeserializeAsync(responseStream, JsonSerializerSettings, cancellationToken).ConfigureAwait(false); + return new ObjectResponseResult(typedBody, string.Empty); + } + } + catch (System.Text.Json.JsonException exception) + { + var message = "Could not deserialize the response body stream as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception); + } + } + } + + private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo) + { + if (value == null) + { + return ""; + } + + if (value is System.Enum) + { + var name = System.Enum.GetName(value.GetType(), value); + if (name != null) + { + var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); + if (field != null) + { + var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) + as System.Runtime.Serialization.EnumMemberAttribute; + if (attribute != null) + { + return attribute.Value != null ? attribute.Value : name; + } + } + + var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo)); + return converted == null ? string.Empty : converted; + } + } + else if (value is bool) + { + return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant(); + } + else if (value is byte[]) + { + return System.Convert.ToBase64String((byte[]) value); + } + else if (value.GetType().IsArray) + { + var array = System.Linq.Enumerable.OfType((System.Array) value); + return string.Join(",", System.Linq.Enumerable.Select(array, o => ConvertToString(o, cultureInfo))); + } + + var result = System.Convert.ToString(value, cultureInfo); + return result == null ? "" : result; + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pet + { + [System.Text.Json.Serialization.JsonPropertyName("id")] + public long Id { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("tag")] + public string Tag { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pets : System.Collections.ObjectModel.Collection + { + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Error + { + [System.Text.Json.Serialization.JsonPropertyName("code")] + public int Code { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("message")] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Message { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class ApiException : System.Exception + { + public int StatusCode { get; private set; } + + public string Response { get; private set; } + + public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; } + + public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Exception innerException) + : base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + ((response == null) ? "(null)" : response.Substring(0, response.Length >= 512 ? 512 : response.Length)), innerException) + { + StatusCode = statusCode; + Response = response; + Headers = headers; + } + + public override string ToString() + { + return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString()); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class ApiException : ApiException + { + public TResult Result { get; private set; } + + public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, TResult result, System.Exception innerException) + : base(message, statusCode, response, headers, innerException) + { + Result = result; + } + } + +} + +#pragma warning restore 1591 +#pragma warning restore 1573 +#pragma warning restore 472 +#pragma warning restore 114 +#pragma warning restore 108 +#pragma warning restore 3016 \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/Lab.SpecFirst.Adapter.csproj b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/Lab.SpecFirst.Adapter.csproj new file mode 100644 index 00000000..eb2460e9 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/Lab.SpecFirst.Adapter.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/.dockerignore b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/.dockerignore new file mode 100644 index 00000000..cd967fc3 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs new file mode 100644 index 00000000..c458dd95 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs @@ -0,0 +1,137 @@ +//---------------------- +// +// Generated using the NSwag toolchain v13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0)) (http://NSwag.org) +// +//---------------------- + +#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." +#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." +#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' +#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... +#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." +#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" +#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" + +namespace Lab.SpecFirst.Web.Controllers +{ + using System = global::System; + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + public interface ISpecFirstContractController + { + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + System.Threading.Tasks.Task> ListPetsAsync(int? limit); + + /// Create a pet + /// Null response + System.Threading.Tasks.Task CreatePetsAsync(); + + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + System.Threading.Tasks.Task ShowPetByIdAsync(string petId); + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] + [Microsoft.AspNetCore.Mvc.Route("api/")] + public partial class SpecFirstContractController : Microsoft.AspNetCore.Mvc.ControllerBase + { + private ISpecFirstContractController _implementation; + + public SpecFirstContractController(ISpecFirstContractController implementation) + { + _implementation = implementation; + } + + /// List all pets + /// How many items to return at one time (max 100) + /// A paged array of pets + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pets")] + public System.Threading.Tasks.Task> ListPets([Microsoft.AspNetCore.Mvc.FromQuery] int? limit) + { + return _implementation.ListPetsAsync(limit); + } + + /// Create a pet + /// Null response + [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("pets")] + public System.Threading.Tasks.Task CreatePets() + { + return _implementation.CreatePetsAsync(); + } + + /// Info for a specific pet + /// The id of the pet to retrieve + /// Expected response to a valid request + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pets/{petId}")] + public System.Threading.Tasks.Task ShowPetById(string petId) + { + return _implementation.ShowPetByIdAsync(petId); + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pet + { + [System.Text.Json.Serialization.JsonPropertyName("id")] + public long Id { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("tag")] + public string Tag { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pets : System.Collections.ObjectModel.Collection + { + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Error + { + [System.Text.Json.Serialization.JsonPropertyName("code")] + public int Code { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("message")] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Message { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + + + } + +} + +#pragma warning restore 1591 +#pragma warning restore 1573 +#pragma warning restore 472 +#pragma warning restore 114 +#pragma warning restore 108 +#pragma warning restore 3016 \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Controllers/SpecFirstController.cs b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Controllers/SpecFirstController.cs new file mode 100644 index 00000000..eed7b89a --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Controllers/SpecFirstController.cs @@ -0,0 +1,38 @@ +namespace Lab.SpecFirst.Web.Controllers; + +class SpecFirstController : ISpecFirstContractController +{ + public async Task> ListPetsAsync(int? limit) + { + return new List() + { + new() + { + Id = 1, + Name = "yao", + Tag = "dog", + AdditionalProperties = new Dictionary() + { + } + }, + }; + } + + public async Task CreatePetsAsync() + { + + } + + public async Task ShowPetByIdAsync(string petId) + { + return new() + { + Id = 1, + Name = "yao", + Tag = "dog", + AdditionalProperties = new Dictionary() + { + } + }; + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Dockerfile b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Dockerfile new file mode 100644 index 00000000..e568de25 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Dockerfile @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj", "Lab.SpecFirst.Web/"] +RUN dotnet restore "src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj" +COPY . . +WORKDIR "/src/Lab.SpecFirst.Web" +RUN dotnet build "Lab.SpecFirst.Web.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Lab.SpecFirst.Web.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Lab.SpecFirst.Web.dll"] diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj new file mode 100644 index 00000000..0db9dec4 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Lab.SpecFirst.Web.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + Linux + + + + + + + diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Program.cs b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Program.cs new file mode 100644 index 00000000..8f8bd1ae --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Program.cs @@ -0,0 +1,29 @@ +using Lab.SpecFirst.Web.Controllers; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddScoped(); + +// Add services to the container. +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Properties/launchSettings.json b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Properties/launchSettings.json new file mode 100644 index 00000000..466bd7be --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:9860", + "sslPort": 44313 + } + }, + "profiles": { + "Lab.SpecFirst.Web": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7041;http://localhost:5041", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/WeatherForecast.cs b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/WeatherForecast.cs new file mode 100644 index 00000000..bacb51a3 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace Lab.SpecFirst.Web; + +public class WeatherForecast +{ + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.Development.json b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.json b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From df670f19a793fc26b5ee521680cc49133e14273f Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 19 Jul 2022 10:02:56 +0800 Subject: [PATCH 248/424] feat: extra schema module --- .../Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln | 8 ++ .../Lab.SpecFirst2/doc/components/index.yaml | 9 ++ .../doc/components/parameter - Copy (2).yaml | 112 ------------------ .../doc/components/parameter - Copy.yaml | 112 ------------------ .../doc/components/parameter.yaml | 112 ------------------ .../doc/components/parameters.yaml | 8 ++ .../doc/components/schemas.yaml | 30 +++++ WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml | 48 ++------ 8 files changed, 62 insertions(+), 377 deletions(-) create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/index.yaml delete mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml delete mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml delete mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameters.yaml create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/components/schemas.yaml diff --git a/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln b/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln index 70b4eaae..58347b91 100644 --- a/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln +++ b/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln @@ -21,6 +21,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "schemas", "schemas", "{087A doc\schemas\index.yaml = doc\schemas\index.yaml EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "components", "components", "{4D3DDFBF-E6C5-4E38-8FC2-AE1C10450670}" + ProjectSection(SolutionItems) = preProject + doc\components\index.yaml = doc\components\index.yaml + doc\components\parameters.yaml = doc\components\parameters.yaml + doc\components\schemas.yaml = doc\components\schemas.yaml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -40,5 +47,6 @@ Global {70299524-F9F4-41DF-80EF-D1CE03C2965A} = {F900DCE0-EF45-4629-AA24-949E65BAB714} {F5AAACC5-781F-41E6-8CD5-39389A00942C} = {F900DCE0-EF45-4629-AA24-949E65BAB714} {087A302F-7CB9-411A-A189-33919965A007} = {7C405A84-132F-43F2-9F97-388CC40BED1D} + {4D3DDFBF-E6C5-4E38-8FC2-AE1C10450670} = {7C405A84-132F-43F2-9F97-388CC40BED1D} EndGlobalSection EndGlobal diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/index.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/index.yaml new file mode 100644 index 00000000..21c814fc --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/index.yaml @@ -0,0 +1,9 @@ +parameters: + petId: + name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + example: 1 \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml deleted file mode 100644 index 09458854..00000000 --- a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy (2).yaml +++ /dev/null @@ -1,112 +0,0 @@ -openapi: "3.0.3" -info: - version: 1.0.0 - title: Swagger Petstore - license: - name: MIT -servers: - - url: http://localhost:7087/api/ -# - url: http://petstore.swagger.io/v1 -paths: - /pets: - get: - summary: List all pets - operationId: listPets - tags: - - pets - parameters: - - name: limit - in: query - description: How many items to return at one time (max 100) - required: false - schema: - type: integer - format: int32 - responses: - '200': - description: A paged array of pets - headers: - x-next: - description: A link to the next page of responses - schema: - type: string - content: - application/json: - schema: - $ref: "#/components/schemas/Pets" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - post: - summary: Create a pet - operationId: createPets - tags: - - pets - responses: - '201': - description: Null response - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - /pets/{petId}: - get: - summary: Info for a specific pet - operationId: showPetById - tags: - - pets - parameters: - - name: petId - in: path - required: true - description: The id of the pet to retrieve - schema: - type: string - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - $ref: "#/components/schemas/Pet" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" -components: - schemas: - Pet: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int64 - name: - type: string - tag: - type: string - Pets: - type: array - items: - $ref: "#/components/schemas/Pet" - Error: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml deleted file mode 100644 index 09458854..00000000 --- a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter - Copy.yaml +++ /dev/null @@ -1,112 +0,0 @@ -openapi: "3.0.3" -info: - version: 1.0.0 - title: Swagger Petstore - license: - name: MIT -servers: - - url: http://localhost:7087/api/ -# - url: http://petstore.swagger.io/v1 -paths: - /pets: - get: - summary: List all pets - operationId: listPets - tags: - - pets - parameters: - - name: limit - in: query - description: How many items to return at one time (max 100) - required: false - schema: - type: integer - format: int32 - responses: - '200': - description: A paged array of pets - headers: - x-next: - description: A link to the next page of responses - schema: - type: string - content: - application/json: - schema: - $ref: "#/components/schemas/Pets" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - post: - summary: Create a pet - operationId: createPets - tags: - - pets - responses: - '201': - description: Null response - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - /pets/{petId}: - get: - summary: Info for a specific pet - operationId: showPetById - tags: - - pets - parameters: - - name: petId - in: path - required: true - description: The id of the pet to retrieve - schema: - type: string - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - $ref: "#/components/schemas/Pet" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" -components: - schemas: - Pet: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int64 - name: - type: string - tag: - type: string - Pets: - type: array - items: - $ref: "#/components/schemas/Pet" - Error: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml deleted file mode 100644 index 09458854..00000000 --- a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameter.yaml +++ /dev/null @@ -1,112 +0,0 @@ -openapi: "3.0.3" -info: - version: 1.0.0 - title: Swagger Petstore - license: - name: MIT -servers: - - url: http://localhost:7087/api/ -# - url: http://petstore.swagger.io/v1 -paths: - /pets: - get: - summary: List all pets - operationId: listPets - tags: - - pets - parameters: - - name: limit - in: query - description: How many items to return at one time (max 100) - required: false - schema: - type: integer - format: int32 - responses: - '200': - description: A paged array of pets - headers: - x-next: - description: A link to the next page of responses - schema: - type: string - content: - application/json: - schema: - $ref: "#/components/schemas/Pets" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - post: - summary: Create a pet - operationId: createPets - tags: - - pets - responses: - '201': - description: Null response - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - /pets/{petId}: - get: - summary: Info for a specific pet - operationId: showPetById - tags: - - pets - parameters: - - name: petId - in: path - required: true - description: The id of the pet to retrieve - schema: - type: string - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - $ref: "#/components/schemas/Pet" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" -components: - schemas: - Pet: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int64 - name: - type: string - tag: - type: string - Pets: - type: array - items: - $ref: "#/components/schemas/Pet" - Error: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameters.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameters.yaml new file mode 100644 index 00000000..b8dcfa9a --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/parameters.yaml @@ -0,0 +1,8 @@ +petId: + name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + example: 1 \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/components/schemas.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/schemas.yaml new file mode 100644 index 00000000..bf11a600 --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/components/schemas.yaml @@ -0,0 +1,30 @@ +Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + +Pets: + type: array + items: + $ref: "#/Pet" + +Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml index 09458854..04dd77db 100644 --- a/WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/index.yaml @@ -33,13 +33,14 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Pets" + $ref: "components/schemas.yaml#/Pets" + default: description: unexpected error content: application/json: schema: - $ref: "#/components/schemas/Error" + $ref: 'components/schemas.yaml#/Error' post: summary: Create a pet operationId: createPets @@ -53,7 +54,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Error" + $ref: "components/schemas.yaml#/Error" /pets/{petId}: get: summary: Info for a specific pet @@ -61,52 +62,17 @@ paths: tags: - pets parameters: - - name: petId - in: path - required: true - description: The id of the pet to retrieve - schema: - type: string + - $ref: "components/parameters.yaml#/petId" responses: '200': description: Expected response to a valid request content: application/json: schema: - $ref: "#/components/schemas/Pet" + $ref: "components/schemas.yaml#/Pet" default: description: unexpected error content: application/json: schema: - $ref: "#/components/schemas/Error" -components: - schemas: - Pet: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int64 - name: - type: string - tag: - type: string - Pets: - type: array - items: - $ref: "#/components/schemas/Pet" - Error: - type: object - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string \ No newline at end of file + $ref: "components/schemas.yaml#/Error" \ No newline at end of file From 5396074d9639719bafb8cc5dde846facc17862f2 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 19 Jul 2022 23:52:09 +0800 Subject: [PATCH 249/424] feat: merge file --- .../Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln | 13 ++++++------- WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml | 8 ++++++++ .../Lab.SpecFirst2/doc/schemas/index.yaml | 19 ------------------- 3 files changed, 14 insertions(+), 26 deletions(-) delete mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml diff --git a/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln b/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln index 58347b91..37c434a3 100644 --- a/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln +++ b/WebAPI/Swagger/Lab.SpecFirst2/Lab.SpecFirst.sln @@ -16,18 +16,17 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SpecFirst.Web", "src\La EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SpecFirst.Adapter", "src\Lab.SpecFirst.Adapter\Lab.SpecFirst.Adapter.csproj", "{F5AAACC5-781F-41E6-8CD5-39389A00942C}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "schemas", "schemas", "{087A302F-7CB9-411A-A189-33919965A007}" - ProjectSection(SolutionItems) = preProject - doc\schemas\index.yaml = doc\schemas\index.yaml - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "components", "components", "{4D3DDFBF-E6C5-4E38-8FC2-AE1C10450670}" ProjectSection(SolutionItems) = preProject - doc\components\index.yaml = doc\components\index.yaml doc\components\parameters.yaml = doc\components\parameters.yaml doc\components\schemas.yaml = doc\components\schemas.yaml EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "merge", "merge", "{F28E0DEE-5774-42CF-ABE2-2654AF751215}" + ProjectSection(SolutionItems) = preProject + doc\merge\index.yaml = doc\merge\index.yaml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -46,7 +45,7 @@ Global GlobalSection(NestedProjects) = preSolution {70299524-F9F4-41DF-80EF-D1CE03C2965A} = {F900DCE0-EF45-4629-AA24-949E65BAB714} {F5AAACC5-781F-41E6-8CD5-39389A00942C} = {F900DCE0-EF45-4629-AA24-949E65BAB714} - {087A302F-7CB9-411A-A189-33919965A007} = {7C405A84-132F-43F2-9F97-388CC40BED1D} {4D3DDFBF-E6C5-4E38-8FC2-AE1C10450670} = {7C405A84-132F-43F2-9F97-388CC40BED1D} + {F28E0DEE-5774-42CF-ABE2-2654AF751215} = {7C405A84-132F-43F2-9F97-388CC40BED1D} EndGlobalSection EndGlobal diff --git a/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml b/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml index 61cb3a6a..fe24c234 100644 --- a/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml +++ b/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml @@ -18,3 +18,11 @@ tasks: desc: 產生 Server Code cmds: - nswag openapi2cscontroller /input:doc/index.yaml /classname:SpecFirstContract /namespace:Lab.SpecFirst.Web.Controllers /output:src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs /jsonLibrary:SystemTextJson + + merge-swagger-file: + desc: 合併 Swagger File + cmds: +# - speccy resolve ./doc/index.yaml -o ./doc/merge/index.yaml +# - swagger-cli bundle ./doc/index.yaml --outfile ./doc/merge/index.yaml --type yaml + - openapi-merger -i ./doc/index.yaml -o ./doc/merge/index.yaml + \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml deleted file mode 100644 index 48e6371b..00000000 --- a/WebAPI/Swagger/Lab.SpecFirst2/doc/schemas/index.yaml +++ /dev/null @@ -1,19 +0,0 @@ -parameters: - petId: - name: petId - in: path - required: true - description: The id of the pet to retrieve - schema: - type: string - -schemas: - -requestBodies: -responses: - okResponse: - type: object - properties: - data: - example: ok - type: string From be5121c9d7557c900ed86c797d571313dbb2072a Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 20 Jul 2022 09:36:15 +0800 Subject: [PATCH 250/424] modify task --- WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml | 17 +-- .../Lab.SpecFirst2/doc/merge/index.yaml | 115 ++++++++++++++++++ 2 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 WebAPI/Swagger/Lab.SpecFirst2/doc/merge/index.yaml diff --git a/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml b/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml index fe24c234..ae1af0b1 100644 --- a/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml +++ b/WebAPI/Swagger/Lab.SpecFirst2/Taskfile.yml @@ -3,23 +3,24 @@ version: "3" dotenv: [ "secrets/secrets.env" ] tasks: - rest-codegen-code: + spec-codegen: desc: 產生 Client / Server Code cmds: - - task: rest-codegen-client - - task: rest-codegen-server + - task: spec-merge-file + - task: spec-codegen-client + - task: spec-codegen-server - rest-codegen-client: + spec-codegen-client: desc: 產生 Client Code cmds: - - nswag openapi2csclient /input:doc/index.yaml /classname:LabSpecClient /namespace:Lab.SpecFirst.Adapter /output:src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs /jsonLibrary:SystemTextJson /generateClientInterfaces:true /exposeJsonSerializerSettings:false /useBaseUrl:false + - nswag openapi2csclient /input:doc/merge/index.yaml /classname:LabSpecClient /namespace:Lab.SpecFirst.Adapter /output:src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs /jsonLibrary:SystemTextJson /generateClientInterfaces:true /exposeJsonSerializerSettings:false /useBaseUrl:false - rest-codegen-server: + spec-codegen-server: desc: 產生 Server Code cmds: - - nswag openapi2cscontroller /input:doc/index.yaml /classname:SpecFirstContract /namespace:Lab.SpecFirst.Web.Controllers /output:src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs /jsonLibrary:SystemTextJson + - nswag openapi2cscontroller /input:doc/merge/index.yaml /classname:SpecFirstContract /namespace:Lab.SpecFirst.Web.Controllers /output:src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs /jsonLibrary:SystemTextJson - merge-swagger-file: + spec-merge-file: desc: 合併 Swagger File cmds: # - speccy resolve ./doc/index.yaml -o ./doc/merge/index.yaml diff --git a/WebAPI/Swagger/Lab.SpecFirst2/doc/merge/index.yaml b/WebAPI/Swagger/Lab.SpecFirst2/doc/merge/index.yaml new file mode 100644 index 00000000..8b5b6d4d --- /dev/null +++ b/WebAPI/Swagger/Lab.SpecFirst2/doc/merge/index.yaml @@ -0,0 +1,115 @@ +openapi: 3.0.3 +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: 'http://localhost:7087/api/' +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Pets' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '/pets/{petId}': + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - $ref: '#/components/parameters/petId' + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +components: + parameters: + petId: + name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + example: 1 + schemas: + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: '#/components/schemas/Pet' From bcb1a92edfaee14efd6a9952467fe8391848e83c Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 20 Jul 2022 09:44:01 +0800 Subject: [PATCH 251/424] regen --- .../AutoGenerated/LabSpecClient.cs | 38 +++++++++---------- .../AutoGenerated/Controller.cs | 38 +++++++++---------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs index 55ae054c..1aecf161 100644 --- a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Adapter/AutoGenerated/LabSpecClient.cs @@ -434,17 +434,14 @@ private string ConvertToString(object value, System.Globalization.CultureInfo cu } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Pet + public partial class Error { - [System.Text.Json.Serialization.JsonPropertyName("id")] - public long Id { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("code")] + public int Code { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.Text.Json.Serialization.JsonPropertyName("message")] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] - public string Name { get; set; } - - [System.Text.Json.Serialization.JsonPropertyName("tag")] - public string Tag { get; set; } + public string Message { get; set; } private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); @@ -459,20 +456,17 @@ public System.Collections.Generic.IDictionary AdditionalProperti } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Pets : System.Collections.ObjectModel.Collection - { - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Error + public partial class Pet { - [System.Text.Json.Serialization.JsonPropertyName("code")] - public int Code { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("id")] + public long Id { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("message")] + [System.Text.Json.Serialization.JsonPropertyName("name")] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] - public string Message { get; set; } + public string Name { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("tag")] + public string Tag { get; set; } private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); @@ -484,6 +478,12 @@ public System.Collections.Generic.IDictionary AdditionalProperti } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pets : System.Collections.ObjectModel.Collection + { + } [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.13.2.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")] diff --git a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs index c458dd95..ddb19bf6 100644 --- a/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs +++ b/WebAPI/Swagger/Lab.SpecFirst2/src/Lab.SpecFirst.Web/AutoGenerated/Controller.cs @@ -75,17 +75,14 @@ public System.Threading.Tasks.Task ShowPetById(string petId) } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Pet + public partial class Error { - [System.Text.Json.Serialization.JsonPropertyName("id")] - public long Id { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("code")] + public int Code { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("name")] + [System.Text.Json.Serialization.JsonPropertyName("message")] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] - public string Name { get; set; } - - [System.Text.Json.Serialization.JsonPropertyName("tag")] - public string Tag { get; set; } + public string Message { get; set; } private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); @@ -100,20 +97,17 @@ public System.Collections.Generic.IDictionary AdditionalProperti } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Pets : System.Collections.ObjectModel.Collection - { - - } - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] - public partial class Error + public partial class Pet { - [System.Text.Json.Serialization.JsonPropertyName("code")] - public int Code { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("id")] + public long Id { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("message")] + [System.Text.Json.Serialization.JsonPropertyName("name")] [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] - public string Message { get; set; } + public string Name { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("tag")] + public string Tag { get; set; } private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); @@ -126,6 +120,12 @@ public System.Collections.Generic.IDictionary AdditionalProperti } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Pets : System.Collections.ObjectModel.Collection + { + + } } From f2c4780b89cd13d74956006f71ef446fc6ab9a64 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 17 Aug 2022 00:06:00 +0800 Subject: [PATCH 252/424] =?UTF-8?q?feat:=20=E7=AF=84=E4=BE=8B=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs | 61 +++++++ .../Mock Server/Prism/doc/1/index.yaml | 111 ++++++++++++ .../Mock Server/Prism/doc/2/index.yaml | 169 ++++++++++++++++++ .../Mock Server/Prism/src/Lab.MockServer.sln | 26 +++ 4 files changed, 367 insertions(+) create mode 100644 WebAPI/Swagger/Mock Server/Prism/doc/1/index.yaml create mode 100644 WebAPI/Swagger/Mock Server/Prism/doc/2/index.yaml create mode 100644 WebAPI/Swagger/Mock Server/Prism/src/Lab.MockServer.sln diff --git a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs index 6a096e8b..1e2f17d0 100644 --- a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs +++ b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs @@ -1,12 +1,73 @@ +using System.Collections.Concurrent; +using System.Reflection; using Amazon; using Amazon.S3; using Amazon.S3.Model; namespace Lab.Aws.S3.MinIOS3; +public class FieldTypeAssistant +{ + private static ConcurrentDictionary> s_fieldTypeList = new(); + + public static Dictionary GetStaticFieldValues() + { + var type = typeof(T); + var fieldTypeList = s_fieldTypeList; + if (fieldTypeList.TryGetValue(type, out var results)) + { + return results; + } + + var bindingFlags = BindingFlags.Public + | BindingFlags.Static + ; + results = new Dictionary(); + var fieldInfosInfos = type.GetFields(bindingFlags); + foreach (var fieldInfo in fieldInfosInfos) + { + var key = fieldInfo.Name; + var value = fieldInfo.GetValue(null); + + results.Add(value.ToString(), key); + } + + fieldTypeList.TryAdd(type, results); + return results; + } +} + +public class ProfileFieldNames +{ + public const string BB1Name = "BB1"; + + public const string BB2Name = "BB2"; + + private static readonly Lazy> s_valueDictionary = + new(FieldTypeAssistant.GetStaticFieldValues()); + + public static IReadOnlyDictionary GetValues() + { + return s_valueDictionary.Value; + } + + public static string GetValue(string key) + { + s_valueDictionary.Value.TryGetValue(key, out var value); + return value; + } +} + [TestClass] public class UnitTest1 { + [TestMethod] + public void test() + { + var actual = ProfileFieldNames.GetValue("BB1"); + Assert.AreEqual("BB1Name", actual); + } + [TestMethod] public async Task 新增一個儲存桶() { diff --git a/WebAPI/Swagger/Mock Server/Prism/doc/1/index.yaml b/WebAPI/Swagger/Mock Server/Prism/doc/1/index.yaml new file mode 100644 index 00000000..acdeb1fd --- /dev/null +++ b/WebAPI/Swagger/Mock Server/Prism/doc/1/index.yaml @@ -0,0 +1,111 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Mock Server/Prism/doc/2/index.yaml b/WebAPI/Swagger/Mock Server/Prism/doc/2/index.yaml new file mode 100644 index 00000000..79724a80 --- /dev/null +++ b/WebAPI/Swagger/Mock Server/Prism/doc/2/index.yaml @@ -0,0 +1,169 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + example: + - id: 10 + name: "doggie" + tag: "dog" + + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + examples: + 1: + value: + id: 1 + name: "doggie" + tag: "dog" + 12: + value: + id: 12 + name: "dora" + tag: "cat" + '404': + description: Pet not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + example: + message: "Pet not found" + status: 404 + code: 404 + + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/validate: + get: + summary: Your GET endpoint + tags: [] + parameters: + - schema: + type: string + pattern: '^[A-Z]{2} [1-9]{4}$' + minLength: 0 + in: query + name: id + required: true + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + examples: + 1: + value: + id: 1 + name: "doggie" + tag: "dog" + 12: + value: + id: 12 + name: "dora" + tag: "cat" + +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/WebAPI/Swagger/Mock Server/Prism/src/Lab.MockServer.sln b/WebAPI/Swagger/Mock Server/Prism/src/Lab.MockServer.sln new file mode 100644 index 00000000..3cf1d2a9 --- /dev/null +++ b/WebAPI/Swagger/Mock Server/Prism/src/Lab.MockServer.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{3B9B5D72-1C50-43D1-8E33-FBE93C4D0FF8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1", "1", "{DA17785E-5C85-4A31-AFA6-B5BF799B3DDD}" + ProjectSection(SolutionItems) = preProject + ..\doc\1\index.yaml = ..\doc\1\index.yaml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2", "2", "{F7194878-92F6-4FE5-9841-DE99711949F6}" + ProjectSection(SolutionItems) = preProject + ..\doc\2\index.yaml = ..\doc\2\index.yaml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{A84DAC1D-5160-47E1-916B-892AB78EA2E8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {DA17785E-5C85-4A31-AFA6-B5BF799B3DDD} = {3B9B5D72-1C50-43D1-8E33-FBE93C4D0FF8} + {F7194878-92F6-4FE5-9841-DE99711949F6} = {3B9B5D72-1C50-43D1-8E33-FBE93C4D0FF8} + EndGlobalSection +EndGlobal From 2070c3ad8b61eb7f18520ec81254fc9c841dd676 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 20 Aug 2022 16:23:06 +0800 Subject: [PATCH 253/424] feat: add test --- ...iddleware.OverrideResponse.UnitTest.csproj | 23 ++++ ...errideResponseHandlerMiddlewareUnitTest.cs | 103 ++++++++++++++++++ .../Usings.cs | 1 + .../Lib.Middleware.OverrideResponse.sln | 22 ++++ .../Lib.Middleware.OverrideResponse.csproj | 13 +++ .../OverrideResponseHandlerMiddleware.cs | 59 ++++++++++ 6 files changed, 221 insertions(+) create mode 100644 AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/Lib.Middleware.OverrideResponse.UnitTest.csproj create mode 100644 AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/OverrideResponseHandlerMiddlewareUnitTest.cs create mode 100644 AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/Usings.cs create mode 100644 AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.sln create mode 100644 AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.csproj create mode 100644 AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs diff --git a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/Lib.Middleware.OverrideResponse.UnitTest.csproj b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/Lib.Middleware.OverrideResponse.UnitTest.csproj new file mode 100644 index 00000000..288baefd --- /dev/null +++ b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/Lib.Middleware.OverrideResponse.UnitTest.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + + + diff --git a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/OverrideResponseHandlerMiddlewareUnitTest.cs b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/OverrideResponseHandlerMiddlewareUnitTest.cs new file mode 100644 index 00000000..4257f7f0 --- /dev/null +++ b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/OverrideResponseHandlerMiddlewareUnitTest.cs @@ -0,0 +1,103 @@ +using System.ComponentModel; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Unicode; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lib.Middleware.OverrideResponse.UnitTest; + +[TestClass] +public class OverrideResponseHandlerMiddlewareUnitTest +{ + [TestMethod] + public async Task 不模糊內部訊息() + { + var expected = @"{""code"":""9527""}"; + + var serviceProvider = CreateServiceProvider(); + var jsonSerializerOptions = serviceProvider.GetService(); + var logger = serviceProvider.GetService>(); + var target = new OverrideResponseHandlerMiddleware(nextContext => + CreateFakeNextContext(nextContext, new { Code = "9527" }, StatusCodes.Status200OK)); + + var httpContext = new DefaultHttpContext + { + Response = { Body = new MemoryStream() } + }; + + await target.InvokeAsync(httpContext, logger, jsonSerializerOptions); + var response = httpContext.Response; + var stream = response.Body; + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + var actual = await new StreamReader(stream).ReadToEndAsync(); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public async Task 模糊化未驗證訊息() + { + var expected = @"{""errorCode"":""NoAuthentication"",""errorMessage"":""Please contact your administrator""}"; + var httpContext = new DefaultHttpContext + { + Response = { Body = new MemoryStream() } + }; + var serviceProvider = CreateServiceProvider(); + var jsonSerializerOptions = serviceProvider.GetService(); + var logger = serviceProvider.GetService>(); + + var target = new OverrideResponseHandlerMiddleware(nextContext => + CreateFakeNextContext(nextContext, new + { + ErrorCode = "NoAuthentication", + ErrorMessage = "Invalid userid or password" + }, StatusCodes.Status401Unauthorized)); + + await target.InvokeAsync(httpContext, logger, jsonSerializerOptions); + + var response = httpContext.Response; + var stream = response.Body; + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + var actual = await new StreamReader(stream).ReadToEndAsync(); + Assert.AreEqual(expected, actual); + } + + private static Task CreateFakeNextContext(HttpContext context, object detailFailure, int statusCode) + { + context.Response.StatusCode = statusCode; + context.Response.WriteAsJsonAsync(detailFailure); + return Task.CompletedTask; + } + + private static JsonSerializerOptions CreateJsonSerializerOptions() + { + return new() + { + Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, + UnicodeRanges.CjkUnifiedIdeographs), + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + } + + private static IServiceProvider CreateServiceProvider() + { + var services = new ServiceCollection(); + services.AddSingleton(p => CreateJsonSerializerOptions()); + services.AddSingleton(p => LoggerFactory.Create(builder => { builder.AddConsole(); })); + services.AddSingleton(p => p.GetService().CreateLogger()); + return services.BuildServiceProvider(); + } +} \ No newline at end of file diff --git a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/Usings.cs b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.sln b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.sln new file mode 100644 index 00000000..8868720c --- /dev/null +++ b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lib.Middleware.OverrideResponse", "Lib.Middleware.OverrideResponse\Lib.Middleware.OverrideResponse.csproj", "{F696FEA1-4126-42F0-8D2F-6F7BE99DF418}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lib.Middleware.OverrideResponse.UnitTest", "Lib.Middleware.OverrideResponse.UnitTest\Lib.Middleware.OverrideResponse.UnitTest.csproj", "{F5FD889F-037A-476E-B1F2-A01769A34674}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F696FEA1-4126-42F0-8D2F-6F7BE99DF418}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F696FEA1-4126-42F0-8D2F-6F7BE99DF418}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F696FEA1-4126-42F0-8D2F-6F7BE99DF418}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F696FEA1-4126-42F0-8D2F-6F7BE99DF418}.Release|Any CPU.Build.0 = Release|Any CPU + {F5FD889F-037A-476E-B1F2-A01769A34674}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5FD889F-037A-476E-B1F2-A01769A34674}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5FD889F-037A-476E-B1F2-A01769A34674}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5FD889F-037A-476E-B1F2-A01769A34674}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.csproj b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.csproj new file mode 100644 index 00000000..784167a0 --- /dev/null +++ b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs new file mode 100644 index 00000000..4007c220 --- /dev/null +++ b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs @@ -0,0 +1,59 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Lib.Middleware.OverrideResponse; + +public class OverrideResponseHandlerMiddleware +{ + private readonly RequestDelegate _next; + + public OverrideResponseHandlerMiddleware(RequestDelegate next) + { + this._next = next; + } + + public async Task InvokeAsync(HttpContext context, + ILogger logger, + JsonSerializerOptions jsonSerializerOptions) + { + var srcStream = context.Response.Body; + await using var destStream = new MemoryStream(); + context.Response.Body = destStream; + + await this._next(context); + + destStream.Seek(0, SeekOrigin.Begin); + + var realResponseBody = await new StreamReader(destStream).ReadToEndAsync(); + destStream.Seek(0, SeekOrigin.Begin); + // await destStream.CopyToAsync(srcStream); + context.Response.Body = srcStream; + + var fuzzyBody = context.Response.StatusCode switch + { + 401 => CreateFuzzyBody("NoAuthentication"), + 403 => CreateFuzzyBody("NoAuthorization"), + _ => null + }; + + if (fuzzyBody != null) + { + var json = JsonSerializer.Serialize(fuzzyBody, jsonSerializerOptions); + await context.Response.WriteAsync(json); + } + else + { + await context.Response.WriteAsync(realResponseBody); + } + } + + private static object CreateFuzzyBody(string failureCode) + { + return new + { + ErrorCode = failureCode, + ErrorMessage = "Please contact your administrator" + }; + } +} \ No newline at end of file From 3249bc1911dfdef17d9d7abeb91747d313b581b3 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 20 Aug 2022 16:54:02 +0800 Subject: [PATCH 254/424] refactor: add test case --- ...errideResponseHandlerMiddlewareUnitTest.cs | 32 +++++++++++++++++++ .../OverrideResponseHandlerMiddleware.cs | 24 +++++++------- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/OverrideResponseHandlerMiddlewareUnitTest.cs b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/OverrideResponseHandlerMiddlewareUnitTest.cs index 4257f7f0..fe05535b 100644 --- a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/OverrideResponseHandlerMiddlewareUnitTest.cs +++ b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/OverrideResponseHandlerMiddlewareUnitTest.cs @@ -72,6 +72,38 @@ public async Task 模糊化未驗證訊息() Assert.AreEqual(expected, actual); } + [TestMethod] + public async Task 模糊化未授權訊息() + { + var expected = @"{""errorCode"":""NoAuthorization"",""errorMessage"":""Please contact your administrator""}"; + var httpContext = new DefaultHttpContext + { + Response = { Body = new MemoryStream() } + }; + var serviceProvider = CreateServiceProvider(); + var jsonSerializerOptions = serviceProvider.GetService(); + var logger = serviceProvider.GetService>(); + + var target = new OverrideResponseHandlerMiddleware(nextContext => + CreateFakeNextContext(nextContext, new + { + ErrorCode = "NoAuthorization", + ErrorMessage = "No permission" + }, StatusCodes.Status403Forbidden)); + + await target.InvokeAsync(httpContext, logger, jsonSerializerOptions); + + var response = httpContext.Response; + var stream = response.Body; + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + var actual = await new StreamReader(stream).ReadToEndAsync(); + Assert.AreEqual(expected, actual); + } + private static Task CreateFakeNextContext(HttpContext context, object detailFailure, int statusCode) { context.Response.StatusCode = statusCode; diff --git a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs index 4007c220..71c80a7f 100644 --- a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs +++ b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Net; +using System.Text.Json; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -17,20 +18,15 @@ public async Task InvokeAsync(HttpContext context, ILogger logger, JsonSerializerOptions jsonSerializerOptions) { - var srcStream = context.Response.Body; - await using var destStream = new MemoryStream(); - context.Response.Body = destStream; + var originalResponseBodyStream = context.Response.Body; + await using var newResponseBodyStream = new MemoryStream(); + context.Response.Body = newResponseBodyStream; await this._next(context); - destStream.Seek(0, SeekOrigin.Begin); - - var realResponseBody = await new StreamReader(destStream).ReadToEndAsync(); - destStream.Seek(0, SeekOrigin.Begin); - // await destStream.CopyToAsync(srcStream); - context.Response.Body = srcStream; - - var fuzzyBody = context.Response.StatusCode switch + newResponseBodyStream.Seek(0, SeekOrigin.Begin); + var statusCode = context.Response.StatusCode; + var fuzzyBody = statusCode switch { 401 => CreateFuzzyBody("NoAuthentication"), 403 => CreateFuzzyBody("NoAuthorization"), @@ -40,11 +36,13 @@ public async Task InvokeAsync(HttpContext context, if (fuzzyBody != null) { var json = JsonSerializer.Serialize(fuzzyBody, jsonSerializerOptions); + context.Response.Body = originalResponseBodyStream; await context.Response.WriteAsync(json); } else { - await context.Response.WriteAsync(realResponseBody); + await newResponseBodyStream.CopyToAsync(originalResponseBodyStream); + context.Response.Body = originalResponseBodyStream; } } From 5818b9e797bba6748b0ec418dc9ce935cc6acf40 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 20 Aug 2022 17:06:56 +0800 Subject: [PATCH 255/424] refactor --- .../OverrideResponseHandlerMiddleware.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs index 71c80a7f..9d26a89e 100644 --- a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs +++ b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs @@ -35,9 +35,14 @@ public async Task InvokeAsync(HttpContext context, if (fuzzyBody != null) { - var json = JsonSerializer.Serialize(fuzzyBody, jsonSerializerOptions); + var fuzzyData = JsonSerializer.Serialize(fuzzyBody, jsonSerializerOptions); + logger.LogInformation("Fuzzy data:{FuzzyData}", fuzzyData); + + var realData = await new StreamReader(newResponseBodyStream).ReadToEndAsync(); + logger.LogInformation("Read data:{RealData}", realData); + context.Response.Body = originalResponseBodyStream; - await context.Response.WriteAsync(json); + await context.Response.WriteAsync(fuzzyData); } else { From ab9d435ced406f683aa6ea5d1182cd2d95e07823 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 20 Aug 2022 17:15:48 +0800 Subject: [PATCH 256/424] =?UTF-8?q?refactor:=20=E4=BD=BF=E7=94=A8=E5=BF=85?= =?UTF-8?q?=E8=A6=81=E5=A5=97=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Lib.Middleware.OverrideResponse.csproj | 6 +++++- .../OverrideResponseHandlerMiddleware.cs | 3 +-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.csproj b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.csproj index 784167a0..061f9081 100644 --- a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.csproj +++ b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.csproj @@ -7,7 +7,11 @@ - + + + + + diff --git a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs index 9d26a89e..22de1636 100644 --- a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs +++ b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/OverrideResponseHandlerMiddleware.cs @@ -1,5 +1,4 @@ -using System.Net; -using System.Text.Json; +using System.Text.Json; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; From 840b4c5df43df03f14f39d5b5747e3ca91612a75 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 20 Aug 2022 17:44:48 +0800 Subject: [PATCH 257/424] refactor --- ...iddleware.OverrideResponse.UnitTest.csproj | 12 ++++----- ...errideResponseHandlerMiddlewareUnitTest.cs | 25 +++++++++---------- .../Lib.Middleware.OverrideResponse.csproj | 8 +++--- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/Lib.Middleware.OverrideResponse.UnitTest.csproj b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/Lib.Middleware.OverrideResponse.UnitTest.csproj index 288baefd..b08e38ae 100644 --- a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/Lib.Middleware.OverrideResponse.UnitTest.csproj +++ b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/Lib.Middleware.OverrideResponse.UnitTest.csproj @@ -9,15 +9,15 @@ - - - - - + + + + + - + diff --git a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/OverrideResponseHandlerMiddlewareUnitTest.cs b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/OverrideResponseHandlerMiddlewareUnitTest.cs index fe05535b..9527bcc9 100644 --- a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/OverrideResponseHandlerMiddlewareUnitTest.cs +++ b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.UnitTest/OverrideResponseHandlerMiddlewareUnitTest.cs @@ -1,4 +1,3 @@ -using System.ComponentModel; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; @@ -13,7 +12,7 @@ namespace Lib.Middleware.OverrideResponse.UnitTest; public class OverrideResponseHandlerMiddlewareUnitTest { [TestMethod] - public async Task 不模糊內部訊息() + public async Task 不模糊訊息() { var expected = @"{""code"":""9527""}"; @@ -41,9 +40,9 @@ public async Task 不模糊內部訊息() } [TestMethod] - public async Task 模糊化未驗證訊息() + public async Task 模糊化未授權訊息() { - var expected = @"{""errorCode"":""NoAuthentication"",""errorMessage"":""Please contact your administrator""}"; + var expected = @"{""errorCode"":""NoAuthorization"",""errorMessage"":""Please contact your administrator""}"; var httpContext = new DefaultHttpContext { Response = { Body = new MemoryStream() } @@ -55,9 +54,9 @@ public async Task 模糊化未驗證訊息() var target = new OverrideResponseHandlerMiddleware(nextContext => CreateFakeNextContext(nextContext, new { - ErrorCode = "NoAuthentication", - ErrorMessage = "Invalid userid or password" - }, StatusCodes.Status401Unauthorized)); + ErrorCode = "NoAuthorization", + ErrorMessage = "No permission" + }, StatusCodes.Status403Forbidden)); await target.InvokeAsync(httpContext, logger, jsonSerializerOptions); @@ -73,9 +72,9 @@ public async Task 模糊化未驗證訊息() } [TestMethod] - public async Task 模糊化未授權訊息() + public async Task 模糊化未驗證訊息() { - var expected = @"{""errorCode"":""NoAuthorization"",""errorMessage"":""Please contact your administrator""}"; + var expected = @"{""errorCode"":""NoAuthentication"",""errorMessage"":""Please contact your administrator""}"; var httpContext = new DefaultHttpContext { Response = { Body = new MemoryStream() } @@ -87,9 +86,9 @@ public async Task 模糊化未授權訊息() var target = new OverrideResponseHandlerMiddleware(nextContext => CreateFakeNextContext(nextContext, new { - ErrorCode = "NoAuthorization", - ErrorMessage = "No permission" - }, StatusCodes.Status403Forbidden)); + ErrorCode = "NoAuthentication", + ErrorMessage = "Invalid userid or password" + }, StatusCodes.Status401Unauthorized)); await target.InvokeAsync(httpContext, logger, jsonSerializerOptions); @@ -113,7 +112,7 @@ private static Task CreateFakeNextContext(HttpContext context, object detailFail private static JsonSerializerOptions CreateJsonSerializerOptions() { - return new() + return new JsonSerializerOptions { Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs), diff --git a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.csproj b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.csproj index 061f9081..b6df6955 100644 --- a/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.csproj +++ b/AspNetCore/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse/Lib.Middleware.OverrideResponse.csproj @@ -7,10 +7,10 @@ - - - - + + + + From 2fdf8d54b1592a97022d49f959c2d20ac6c9f7f9 Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 26 Aug 2022 13:58:07 +0800 Subject: [PATCH 258/424] add project --- .../Lab.Host.Env.ConsoleApp.csproj | 10 +++++++ .../src/Lab.Host.Env.ConsoleApp/Program.cs | 3 ++ .../Controllers/DemoController.cs | 28 +++++++++++++++++++ .../Lab.Host.Env.WebApi.csproj | 13 +++++++++ .../src/Lab.Host.Env.WebApi/Program.cs | 26 +++++++++++++++++ .../ServiceModels/EnvironmentResponse.cs | 5 ++++ .../appsettings.Development.json | 8 ++++++ .../src/Lab.Host.Env.WebApi/appsettings.json | 9 ++++++ Host/Lab.Host.Env/src/Lab.Host.Env.sln | 22 +++++++++++++++ 9 files changed, 124 insertions(+) create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Lab.Host.Env.ConsoleApp.csproj create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Program.cs create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Controllers/DemoController.cs create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Lab.Host.Env.WebApi.csproj create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Program.cs create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/ServiceModels/EnvironmentResponse.cs create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Development.json create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.json create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.sln diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Lab.Host.Env.ConsoleApp.csproj b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Lab.Host.Env.ConsoleApp.csproj new file mode 100644 index 00000000..b9de0634 --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Lab.Host.Env.ConsoleApp.csproj @@ -0,0 +1,10 @@ + + + + Exe + net6.0 + enable + enable + + + diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Program.cs b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Program.cs new file mode 100644 index 00000000..e5dff12b --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Program.cs @@ -0,0 +1,3 @@ +// See https://aka.ms/new-console-template for more information + +Console.WriteLine("Hello, World!"); \ No newline at end of file diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Controllers/DemoController.cs b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Controllers/DemoController.cs new file mode 100644 index 00000000..6449d88b --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Controllers/DemoController.cs @@ -0,0 +1,28 @@ +using Lab.Host.Env.WebApi.ServiceModels; +using Microsoft.AspNetCore.Mvc; + +namespace Lab.Host.Env.WebApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class DemoController : ControllerBase +{ + private IWebHostEnvironment _host; + private readonly ILogger _logger; + + public DemoController(ILogger logger, IWebHostEnvironment host) + { + this._logger = logger; + this._host = host; + } + + [HttpGet] + public async Task> Get(CancellationToken cancel = default) + { + return this.Ok(new + { + this._host.ApplicationName, + this._host.EnvironmentName + }); + } +} \ No newline at end of file diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Lab.Host.Env.WebApi.csproj b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Lab.Host.Env.WebApi.csproj new file mode 100644 index 00000000..b9baca3e --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Lab.Host.Env.WebApi.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Program.cs b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Program.cs new file mode 100644 index 00000000..329fe361 --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Program.cs @@ -0,0 +1,26 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/ServiceModels/EnvironmentResponse.cs b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/ServiceModels/EnvironmentResponse.cs new file mode 100644 index 00000000..3c9d2e75 --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/ServiceModels/EnvironmentResponse.cs @@ -0,0 +1,5 @@ +namespace Lab.Host.Env.WebApi.ServiceModels; + +public class EnvironmentResponse +{ +} \ No newline at end of file diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Development.json b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.json b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.sln b/Host/Lab.Host.Env/src/Lab.Host.Env.sln new file mode 100644 index 00000000..e4b7fff7 --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Host.Env.ConsoleApp", "Lab.Host.Env.ConsoleApp\Lab.Host.Env.ConsoleApp.csproj", "{B7D0D873-72F9-455E-8012-2317E88A20A7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Host.Env.WebApi", "Lab.Host.Env.WebApi\Lab.Host.Env.WebApi.csproj", "{C290A555-D176-45BF-B037-1BA36AE7F776}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B7D0D873-72F9-455E-8012-2317E88A20A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7D0D873-72F9-455E-8012-2317E88A20A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7D0D873-72F9-455E-8012-2317E88A20A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7D0D873-72F9-455E-8012-2317E88A20A7}.Release|Any CPU.Build.0 = Release|Any CPU + {C290A555-D176-45BF-B037-1BA36AE7F776}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C290A555-D176-45BF-B037-1BA36AE7F776}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C290A555-D176-45BF-B037-1BA36AE7F776}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C290A555-D176-45BF-B037-1BA36AE7F776}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From af30d60b739800e34ba3a56f14de48f9c73d6248 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 27 Aug 2022 00:20:11 +0800 Subject: [PATCH 259/424] =?UTF-8?q?=E5=88=87=E6=8F=9B=E8=A8=AD=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Program.cs | 12 +++++++++++- .../Lab.Host.Env.WebApi/appsettings.Development.json | 7 ++----- .../src/Lab.Host.Env.WebApi/appsettings.Staging.json | 5 +++++ .../src/Lab.Host.Env.WebApi/appsettings.json | 5 ++++- 4 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Staging.json diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Program.cs b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Program.cs index 329fe361..6b10260d 100644 --- a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Program.cs +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Program.cs @@ -1,13 +1,22 @@ var builder = WebApplication.CreateBuilder(args); // Add services to the container. - builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); +var environmentName = builder.Environment.EnvironmentName; +var configRoot = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile($"appsettings.{environmentName}.json", optional: true, reloadOnChange: true) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .Build(); + ; + +builder.Configuration.AddConfiguration(configRoot); var app = builder.Build(); // Configure the HTTP request pipeline. @@ -17,6 +26,7 @@ app.UseSwaggerUI(); } +var extension = app.Configuration.GetSection("Extension:Version"); app.UseHttpsRedirection(); app.UseAuthorization(); diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Development.json b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Development.json index 0c208ae9..5fcf250c 100644 --- a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Development.json +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Development.json @@ -1,8 +1,5 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } + "Extension": { + "Version": "Development" } } diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Staging.json b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Staging.json new file mode 100644 index 00000000..d7c762b5 --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Staging.json @@ -0,0 +1,5 @@ +{ + "Extension": { + "Version": "Staging" + } +} \ No newline at end of file diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.json b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.json index 10f68b8c..fe523927 100644 --- a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.json +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.json @@ -5,5 +5,8 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "Extension": { + "Version": "Default" + } } From 38e7cb07ce6e1ee14890ba9f7c51001e48676513 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 28 Aug 2022 10:59:51 +0800 Subject: [PATCH 260/424] add file --- Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Program.cs | 8 +++++--- .../src/Lab.Host.Env.WebApi/appsettings.Production.json | 5 +++++ Host/Lab.Host.Env/src/Lab.Host.Env.sln | 6 ++++++ 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Production.json diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Program.cs b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Program.cs index 6b10260d..7e2863ed 100644 --- a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Program.cs +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/Program.cs @@ -11,10 +11,9 @@ var environmentName = builder.Environment.EnvironmentName; var configRoot = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile($"appsettings.{environmentName}.json", optional: true, reloadOnChange: true) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{environmentName}.json", optional: true, reloadOnChange: true) .Build(); - ; builder.Configuration.AddConfiguration(configRoot); var app = builder.Build(); @@ -26,7 +25,10 @@ app.UseSwaggerUI(); } -var extension = app.Configuration.GetSection("Extension:Version"); +var version = app.Configuration.GetSection("Extension:Version").Value; +Console.WriteLine($"Environment: {app.Environment.EnvironmentName}"); +Console.WriteLine($"Extension.Version: {version}"); + app.UseHttpsRedirection(); app.UseAuthorization(); diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Production.json b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Production.json new file mode 100644 index 00000000..7d4ee65a --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.WebApi/appsettings.Production.json @@ -0,0 +1,5 @@ +{ + "Extension": { + "Version": "Production" + } +} diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.sln b/Host/Lab.Host.Env/src/Lab.Host.Env.sln index e4b7fff7..5e559ea5 100644 --- a/Host/Lab.Host.Env/src/Lab.Host.Env.sln +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.sln @@ -4,6 +4,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Host.Env.ConsoleApp", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Host.Env.WebApi", "Lab.Host.Env.WebApi\Lab.Host.Env.WebApi.csproj", "{C290A555-D176-45BF-B037-1BA36AE7F776}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{4601CB1B-259A-415E-B349-76EA4759821A}" + ProjectSection(SolutionItems) = preProject + ..\Taskfile.yml = ..\Taskfile.yml + ..\.env = ..\.env + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU From 7d3e8caf0b601425f02a9058ca4481353f285a9f Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 28 Aug 2022 11:00:30 +0800 Subject: [PATCH 261/424] add file --- Host/Lab.Host.Env/.env | 1 + Host/Lab.Host.Env/Taskfile.yml | 132 +++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 Host/Lab.Host.Env/.env create mode 100644 Host/Lab.Host.Env/Taskfile.yml diff --git a/Host/Lab.Host.Env/.env b/Host/Lab.Host.Env/.env new file mode 100644 index 00000000..403721d0 --- /dev/null +++ b/Host/Lab.Host.Env/.env @@ -0,0 +1 @@ +ASPNETCORE_ENVIRONMENT=Staging \ No newline at end of file diff --git a/Host/Lab.Host.Env/Taskfile.yml b/Host/Lab.Host.Env/Taskfile.yml new file mode 100644 index 00000000..5eb0571a --- /dev/null +++ b/Host/Lab.Host.Env/Taskfile.yml @@ -0,0 +1,132 @@ +# Taskfile.yml + +version: "3" + +dotenv: [ "secrets/secrets.env" ] + +tasks: + + api-dev: + desc: WebApi Development + dir: "src/NineYi.Msa.MemberService.WebAPI" + cmds: + - dotnet watch run --local {{.CLI_ARGS}} | jq -rR '. as $line | try (fromjson | "\(._ts) | \(._lvl) | \(._hid)-\(._ver) | \(._srctx)", ._msg, ._props) catch $line' + + dm-dev: + desc: DataMigration Development. Usage => task dm-dev -- diff -s 2001-01-01 -e 2022-01-01 + dir: "src/NineYi.Msa.MemberService.DataMigration.ConsoleApp" + cmds: + - dotnet watch run {{.CLI_ARGS}} --local true | jq -rR '. as $line | try (fromjson | "\(._ts) | \(._lvl) | \(._hid)-\(._ver) | \(._srctx)", ._msg, ._props) catch $line' + + dm-run: + desc: DataMigration run. Usage => task dm-run -- diff -s 2001-01-01 -e 2022-01-01 + dir: "src/NineYi.Msa.MemberService.DataMigration.ConsoleApp" + cmds: + - dotnet run {{.CLI_ARGS}} --local true + + db-start: + desc: start PostgreSQL at local + cmds: + - docker-compose up -d --remove-orphans db + + db-update: + desc: apply db migration on local PostgreSQL + cmds: + - dotnet ef database update {{.CLI_ARGS}} --project src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext + + redis-start: + desc: start redis 5.X version + cmds: + - docker-compose up -d redis + + redis-admin-start: + desc: admin ui to manage redis + cmds: + - docker-compose up -d redis-admin + + db-otm-db-start: + dir: test/NineYi.Msa.MemberService.NMQ3.Worker.Infrastructure.Tests + cmds: + - docker run --privileged -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=pass@w0rd1~' -p 1433:1433 -d datagrip/mssql-server-linux + + db-otm-db-update: + dir: test/NineYi.Msa.MemberService.NMQ3.Worker.Infrastructure.Tests + cmds: + - dotnet build + - dotnet ef database update --context CrmDbContext --no-build + - dotnet ef database update --context WebStoreDbContext --no-build + + db-mssql-start: + desc: start MSSQL at local + cmds: + - docker-compose up -d db-sql + + db-mssql-update: + desc: db update on local MSSQL + dir: src/NineYi.Msa.MemberService.DataMigration.Infrastructure + cmds: + - dotnet build + - dotnet ef database update --context CrmDbContext --no-build + - dotnet ef database update --context WebStoreDbContext --no-build + + db-list-migrations: + desc: member-service list migrations + cmds: + - dotnet ef migrations list {{.CLI_ARGS}} -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext + + db-add-migration: + desc: member-service add migration + cmds: + - dotnet ef migrations add {{.CLI_ARGS}} -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext + + db-remove-migration: + desc: member-service remove migration + cmds: + - dotnet ef migrations remove -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext + + db-script: + desc: generate member-service db script + cmds: + - dotnet ef migrations script 0 -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext + + ddb-start: + desc: start dynamodb at local + cmds: + - docker-compose up -d ddb + + ddb-clear: + desc: clear ddb table + cmds: + - docker-compose restart ddb + - aws dynamodb create-table --endpoint-url http://localhost:8000 --cli-input-json file://./ddb.json + + ddb-init: + desc: init dynamodb table at local + cmds: + - aws dynamodb create-table --endpoint-url http://localhost:8000 --cli-input-json file://./ddb.json + + ddb-list: + desc: list dynamodb tables at local + cmds: + - aws dynamodb list-tables --endpoint-url http://localhost:8000 + + ddb-logs: + cmds: + - docker-compose logs -f ddb + + ddb-admin-start: + desc: start dynamodb admin + cmds: + - docker-compose up -d ddb-admin + + s3-minio-start: + desc: start s3-minio at local + cmds: + - docker-compose up -d s3-minio + + gen-k8s: + desc: generate k8s + cmds: + - dotnet run --project src/NineYi.Msa.K8S.Template.GeneratorApp -- -s $(pwd)/env -d $(pwd)/env/k8s -v 1.0.0.1 + + From dc9d588f81e757c8ca13d2bf11e0aa68d3b86331 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 28 Aug 2022 23:24:29 +0800 Subject: [PATCH 262/424] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20webapi=20task?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Host/Lab.Host.Env/Taskfile.yml | 126 +-------------------------------- 1 file changed, 3 insertions(+), 123 deletions(-) diff --git a/Host/Lab.Host.Env/Taskfile.yml b/Host/Lab.Host.Env/Taskfile.yml index 5eb0571a..b410b262 100644 --- a/Host/Lab.Host.Env/Taskfile.yml +++ b/Host/Lab.Host.Env/Taskfile.yml @@ -5,128 +5,8 @@ version: "3" dotenv: [ "secrets/secrets.env" ] tasks: - - api-dev: + webapi: desc: WebApi Development - dir: "src/NineYi.Msa.MemberService.WebAPI" - cmds: - - dotnet watch run --local {{.CLI_ARGS}} | jq -rR '. as $line | try (fromjson | "\(._ts) | \(._lvl) | \(._hid)-\(._ver) | \(._srctx)", ._msg, ._props) catch $line' - - dm-dev: - desc: DataMigration Development. Usage => task dm-dev -- diff -s 2001-01-01 -e 2022-01-01 - dir: "src/NineYi.Msa.MemberService.DataMigration.ConsoleApp" - cmds: - - dotnet watch run {{.CLI_ARGS}} --local true | jq -rR '. as $line | try (fromjson | "\(._ts) | \(._lvl) | \(._hid)-\(._ver) | \(._srctx)", ._msg, ._props) catch $line' - - dm-run: - desc: DataMigration run. Usage => task dm-run -- diff -s 2001-01-01 -e 2022-01-01 - dir: "src/NineYi.Msa.MemberService.DataMigration.ConsoleApp" - cmds: - - dotnet run {{.CLI_ARGS}} --local true - - db-start: - desc: start PostgreSQL at local - cmds: - - docker-compose up -d --remove-orphans db - - db-update: - desc: apply db migration on local PostgreSQL - cmds: - - dotnet ef database update {{.CLI_ARGS}} --project src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext - - redis-start: - desc: start redis 5.X version - cmds: - - docker-compose up -d redis - - redis-admin-start: - desc: admin ui to manage redis - cmds: - - docker-compose up -d redis-admin - - db-otm-db-start: - dir: test/NineYi.Msa.MemberService.NMQ3.Worker.Infrastructure.Tests - cmds: - - docker run --privileged -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=pass@w0rd1~' -p 1433:1433 -d datagrip/mssql-server-linux - - db-otm-db-update: - dir: test/NineYi.Msa.MemberService.NMQ3.Worker.Infrastructure.Tests - cmds: - - dotnet build - - dotnet ef database update --context CrmDbContext --no-build - - dotnet ef database update --context WebStoreDbContext --no-build - - db-mssql-start: - desc: start MSSQL at local - cmds: - - docker-compose up -d db-sql - - db-mssql-update: - desc: db update on local MSSQL - dir: src/NineYi.Msa.MemberService.DataMigration.Infrastructure - cmds: - - dotnet build - - dotnet ef database update --context CrmDbContext --no-build - - dotnet ef database update --context WebStoreDbContext --no-build - - db-list-migrations: - desc: member-service list migrations + dir: "src/Lab.Host.Env.WebApi" cmds: - - dotnet ef migrations list {{.CLI_ARGS}} -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext - - db-add-migration: - desc: member-service add migration - cmds: - - dotnet ef migrations add {{.CLI_ARGS}} -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext - - db-remove-migration: - desc: member-service remove migration - cmds: - - dotnet ef migrations remove -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext - - db-script: - desc: generate member-service db script - cmds: - - dotnet ef migrations script 0 -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext - - ddb-start: - desc: start dynamodb at local - cmds: - - docker-compose up -d ddb - - ddb-clear: - desc: clear ddb table - cmds: - - docker-compose restart ddb - - aws dynamodb create-table --endpoint-url http://localhost:8000 --cli-input-json file://./ddb.json - - ddb-init: - desc: init dynamodb table at local - cmds: - - aws dynamodb create-table --endpoint-url http://localhost:8000 --cli-input-json file://./ddb.json - - ddb-list: - desc: list dynamodb tables at local - cmds: - - aws dynamodb list-tables --endpoint-url http://localhost:8000 - - ddb-logs: - cmds: - - docker-compose logs -f ddb - - ddb-admin-start: - desc: start dynamodb admin - cmds: - - docker-compose up -d ddb-admin - - s3-minio-start: - desc: start s3-minio at local - cmds: - - docker-compose up -d s3-minio - - gen-k8s: - desc: generate k8s - cmds: - - dotnet run --project src/NineYi.Msa.K8S.Template.GeneratorApp -- -s $(pwd)/env -d $(pwd)/env/k8s -v 1.0.0.1 - - + - dotnet run --environment Staging \ No newline at end of file From 6b9b3b1e35a75d9cf0abd4a6ba1790a36ba724f7 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 29 Aug 2022 00:15:13 +0800 Subject: [PATCH 263/424] add console app --- Host/Lab.Host.Env/Taskfile.yml | 7 ++++- .../Lab.Host.Env.ConsoleApp.csproj | 27 +++++++++++++++++++ .../src/Lab.Host.Env.ConsoleApp/Program.cs | 22 +++++++++++++++ .../appsettings.Development.json | 5 ++++ .../appsettings.Production.json | 5 ++++ .../appsettings.Staging.json | 5 ++++ .../Lab.Host.Env.ConsoleApp/appsettings.json | 12 +++++++++ 7 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.Development.json create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.Production.json create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.Staging.json create mode 100644 Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.json diff --git a/Host/Lab.Host.Env/Taskfile.yml b/Host/Lab.Host.Env/Taskfile.yml index b410b262..e0166fd1 100644 --- a/Host/Lab.Host.Env/Taskfile.yml +++ b/Host/Lab.Host.Env/Taskfile.yml @@ -9,4 +9,9 @@ tasks: desc: WebApi Development dir: "src/Lab.Host.Env.WebApi" cmds: - - dotnet run --environment Staging \ No newline at end of file + - dotnet run --environment Staging + app: + desc: WebApi Development + dir: "src/Lab.Host.Env.ConsoleApp" + cmds: + - dotnet run --environment Production \ No newline at end of file diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Lab.Host.Env.ConsoleApp.csproj b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Lab.Host.Env.ConsoleApp.csproj index b9de0634..100dd407 100644 --- a/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Lab.Host.Env.ConsoleApp.csproj +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Lab.Host.Env.ConsoleApp.csproj @@ -7,4 +7,31 @@ enable + + + + + + + true + PreserveNewest + PreserveNewest + + + true + PreserveNewest + PreserveNewest + + + true + PreserveNewest + PreserveNewest + + + true + PreserveNewest + PreserveNewest + + + diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Program.cs b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Program.cs index e5dff12b..5f110bd6 100644 --- a/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Program.cs +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/Program.cs @@ -1,3 +1,25 @@ // See https://aka.ms/new-console-template for more information +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +var hostBuilder = Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((hostContext, config) => + { + var environmentName = hostContext.HostingEnvironment.EnvironmentName; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); + config.AddJsonFile($"appsettings.{environmentName}.json", optional: true, reloadOnChange: true); + }) + ; +var host = hostBuilder.Build(); +var environment = host.Services.GetService(); +Console.WriteLine($"Environment: {environment.EnvironmentName}"); + +var configuration = host.Services.GetService(); +var version = configuration.GetSection("Extension:Version").Value; +Console.WriteLine($"Extension.Version: {version}"); + +await host.StartAsync(); +await host.StopAsync(); Console.WriteLine("Hello, World!"); \ No newline at end of file diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.Development.json b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.Development.json new file mode 100644 index 00000000..5fcf250c --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.Development.json @@ -0,0 +1,5 @@ +{ + "Extension": { + "Version": "Development" + } +} diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.Production.json b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.Production.json new file mode 100644 index 00000000..7d4ee65a --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.Production.json @@ -0,0 +1,5 @@ +{ + "Extension": { + "Version": "Production" + } +} diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.Staging.json b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.Staging.json new file mode 100644 index 00000000..d7c762b5 --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.Staging.json @@ -0,0 +1,5 @@ +{ + "Extension": { + "Version": "Staging" + } +} \ No newline at end of file diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.json b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.json new file mode 100644 index 00000000..fe523927 --- /dev/null +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.ConsoleApp/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Extension": { + "Version": "Default" + } +} From 07a1e4bcbbbfb99f773b914c69c039943ae3dc99 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 29 Aug 2022 13:27:40 +0800 Subject: [PATCH 264/424] add file --- Host/Lab.Host.Env/.gitignore | 350 +++++++++++++++++++++++++ Host/Lab.Host.Env/src/Lab.Host.Env.sln | 1 + 2 files changed, 351 insertions(+) create mode 100644 Host/Lab.Host.Env/.gitignore diff --git a/Host/Lab.Host.Env/.gitignore b/Host/Lab.Host.Env/.gitignore new file mode 100644 index 00000000..81c554f7 --- /dev/null +++ b/Host/Lab.Host.Env/.gitignore @@ -0,0 +1,350 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +#vistual studio extension +.localhistory + +# stress test output +stress/output + +# specflow feature.cs +**/*.feature.cs + +# secrets +secrets + +.DS_Store +*.zip + +deployments + +# minio local s3 +minio \ No newline at end of file diff --git a/Host/Lab.Host.Env/src/Lab.Host.Env.sln b/Host/Lab.Host.Env/src/Lab.Host.Env.sln index 5e559ea5..3a78e837 100644 --- a/Host/Lab.Host.Env/src/Lab.Host.Env.sln +++ b/Host/Lab.Host.Env/src/Lab.Host.Env.sln @@ -8,6 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{4601CB ProjectSection(SolutionItems) = preProject ..\Taskfile.yml = ..\Taskfile.yml ..\.env = ..\.env + ..\.gitignore = ..\.gitignore EndProjectSection EndProject Global From 776be876b630456849117177f862b8b22cca258b Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 29 Aug 2022 13:30:02 +0800 Subject: [PATCH 265/424] app project --- StructLog/Lab.SerilogProject/.gitignore | 350 ++++++++++++++++++ StructLog/Lab.SerilogProject/Taskfile.yml | 17 + .../Controllers/WeatherForecastController.cs | 32 ++ .../Lab.SerilogProject.WebApi.csproj | 13 + .../Lab.SerilogProject.WebApi/Program.cs | 26 ++ .../WeatherForecast.cs | 12 + .../appsettings.Development.json | 8 + .../appsettings.json | 9 + .../Lab.SerilogProject/Lab.SerilogProject.sln | 22 ++ 9 files changed, 489 insertions(+) create mode 100644 StructLog/Lab.SerilogProject/.gitignore create mode 100644 StructLog/Lab.SerilogProject/Taskfile.yml create mode 100644 StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Controllers/WeatherForecastController.cs create mode 100644 StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Lab.SerilogProject.WebApi.csproj create mode 100644 StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs create mode 100644 StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/WeatherForecast.cs create mode 100644 StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/appsettings.Development.json create mode 100644 StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/appsettings.json create mode 100644 StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.sln diff --git a/StructLog/Lab.SerilogProject/.gitignore b/StructLog/Lab.SerilogProject/.gitignore new file mode 100644 index 00000000..81c554f7 --- /dev/null +++ b/StructLog/Lab.SerilogProject/.gitignore @@ -0,0 +1,350 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +#vistual studio extension +.localhistory + +# stress test output +stress/output + +# specflow feature.cs +**/*.feature.cs + +# secrets +secrets + +.DS_Store +*.zip + +deployments + +# minio local s3 +minio \ No newline at end of file diff --git a/StructLog/Lab.SerilogProject/Taskfile.yml b/StructLog/Lab.SerilogProject/Taskfile.yml new file mode 100644 index 00000000..3ce2218e --- /dev/null +++ b/StructLog/Lab.SerilogProject/Taskfile.yml @@ -0,0 +1,17 @@ +# Taskfile.yml + +version: "3" + +dotenv: [ "secrets/secrets.env" ] + +tasks: + webapi: + desc: WebApi Development + dir: "src/Lab.SerilogProject.WebApi" + cmds: + - dotnet run --environment Staging + app: + desc: WebApi Development + dir: "src/Lab.SerilogProject.ConsoleApp" + cmds: + - dotnet run --environment Production \ No newline at end of file diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Controllers/WeatherForecastController.cs b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..7bb79242 --- /dev/null +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Controllers/WeatherForecastController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.SerilogProject.WebApi.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } +} \ No newline at end of file diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Lab.SerilogProject.WebApi.csproj b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Lab.SerilogProject.WebApi.csproj new file mode 100644 index 00000000..6108b7b2 --- /dev/null +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Lab.SerilogProject.WebApi.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs new file mode 100644 index 00000000..329fe361 --- /dev/null +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs @@ -0,0 +1,26 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/WeatherForecast.cs b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/WeatherForecast.cs new file mode 100644 index 00000000..6bf80410 --- /dev/null +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace Lab.SerilogProject.WebApi; + +public class WeatherForecast +{ + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/appsettings.Development.json b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/appsettings.json b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.sln b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.sln new file mode 100644 index 00000000..263368a0 --- /dev/null +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SerilogProject.WebApi", "Lab.SerilogProject.WebApi\Lab.SerilogProject.WebApi.csproj", "{D58F5F77-C34C-4971-B044-565BED8C9A5D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{57242756-8814-45F1-A9E3-474721C382BC}" + ProjectSection(SolutionItems) = preProject + ..\..\.gitignore = ..\..\.gitignore + ..\..\Taskfile.yml = ..\..\Taskfile.yml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D58F5F77-C34C-4971-B044-565BED8C9A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D58F5F77-C34C-4971-B044-565BED8C9A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D58F5F77-C34C-4971-B044-565BED8C9A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D58F5F77-C34C-4971-B044-565BED8C9A5D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From 78c5f311b380531f8280704f51db94b35572514d Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 4 Sep 2022 09:36:49 +0800 Subject: [PATCH 266/424] set serilog config --- StructLog/Lab.SerilogProject/Taskfile.yml | 7 +- .../Lab.SerilogProject.WebApi.csproj | 8 ++- .../Lab.SerilogProject.WebApi/Program.cs | 69 ++++++++++++++----- 3 files changed, 65 insertions(+), 19 deletions(-) diff --git a/StructLog/Lab.SerilogProject/Taskfile.yml b/StructLog/Lab.SerilogProject/Taskfile.yml index 3ce2218e..e5b8cef3 100644 --- a/StructLog/Lab.SerilogProject/Taskfile.yml +++ b/StructLog/Lab.SerilogProject/Taskfile.yml @@ -14,4 +14,9 @@ tasks: desc: WebApi Development dir: "src/Lab.SerilogProject.ConsoleApp" cmds: - - dotnet run --environment Production \ No newline at end of file + - dotnet run --environment Production + + seq-start: + desc: start seq service + cmds: + - docker run --name seq -d --restart unless-stopped -e ACCEPT_EULA=Y -p 5341:80 datalust/seq:latest \ No newline at end of file diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Lab.SerilogProject.WebApi.csproj b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Lab.SerilogProject.WebApi.csproj index 6108b7b2..458e3800 100644 --- a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Lab.SerilogProject.WebApi.csproj +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Lab.SerilogProject.WebApi.csproj @@ -7,7 +7,13 @@ - + + + + + + + diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs index 329fe361..bebb259c 100644 --- a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs @@ -1,26 +1,61 @@ -var builder = WebApplication.CreateBuilder(args); +using Serilog; +using Serilog.Events; + +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.File("logs/app-.txt", rollingInterval: RollingInterval.Day) + .CreateLogger(); +try +{ + Log.Information("Starting web host"); -// Add services to the container. + var builder = WebApplication.CreateBuilder(args); -builder.Services.AddControllers(); + // builder.Host.UseSerilog(); //<=== 讓 Host 使用 Serilog + builder.Host.UseSerilog((context, services, config) => + config.ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq("http://localhost:5341") + // .WriteTo.File("logs/aspnet-.txt", rollingInterval: RollingInterval.Minute) + ); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); + // Add services to the container. -var app = builder.Build(); + builder.Services.AddControllers(); -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + + var app = builder.Build(); -app.UseHttpsRedirection(); + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } -app.UseAuthorization(); + app.UseHttpsRedirection(); + app.UseSerilogRequestLogging(); //<=== 每一個 Request 使用 Serilog 記錄下來 + app.UseAuthorization(); -app.MapControllers(); + app.MapControllers(); -app.Run(); \ No newline at end of file + app.Run(); + return 0; +} +catch (Exception ex) +{ + Log.Fatal(ex, "Host terminated unexpectedly"); + return 1; +} +finally +{ + Log.CloseAndFlush(); +} \ No newline at end of file From 0ffaef19627ef757883e6b32a55b4b824aee0af3 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 4 Sep 2022 11:44:11 +0800 Subject: [PATCH 267/424] add Serilog.Expressions package --- .../Controllers/WeatherForecastController.cs | 2 ++ .../Lab.SerilogProject.WebApi.csproj | 1 + .../Lab.SerilogProject.WebApi/Program.cs | 32 +++++++++++++------ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Controllers/WeatherForecastController.cs b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Controllers/WeatherForecastController.cs index 7bb79242..cabf5eac 100644 --- a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Controllers/WeatherForecastController.cs +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Controllers/WeatherForecastController.cs @@ -21,6 +21,8 @@ public WeatherForecastController(ILogger logger) [HttpGet(Name = "GetWeatherForecast")] public IEnumerable Get() { + this._logger.LogInformation(new EventId(2000, "Trace"), "Start {ControllerName}.{MethodName}...", + nameof(WeatherForecastController), nameof(Get)); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Lab.SerilogProject.WebApi.csproj b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Lab.SerilogProject.WebApi.csproj index 458e3800..1524f55b 100644 --- a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Lab.SerilogProject.WebApi.csproj +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Lab.SerilogProject.WebApi.csproj @@ -8,6 +8,7 @@ + diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs index bebb259c..68b44832 100644 --- a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs @@ -1,13 +1,19 @@ using Serilog; using Serilog.Events; +using Serilog.Formatting.Compact; +using Serilog.Formatting.Display; +using Serilog.Formatting.Json; +using Serilog.Formatting.Raw; +using Serilog.Templates; Log.Logger = new LoggerConfiguration() - .MinimumLevel.Information() - .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) - .Enrich.FromLogContext() - .WriteTo.Console() - .WriteTo.File("logs/app-.txt", rollingInterval: RollingInterval.Day) - .CreateLogger(); + .MinimumLevel.Information() + .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.File("logs/host-.txt", rollingInterval: RollingInterval.Day) + .CreateBootstrapLogger() + ; try { Log.Information("Starting web host"); @@ -15,14 +21,22 @@ var builder = WebApplication.CreateBuilder(args); // builder.Host.UseSerilog(); //<=== 讓 Host 使用 Serilog + // var formatter = new JsonFormatter(); + // var formatter = new MessageTemplateTextFormatter(); + // var formatter = new RawFormatter(); + // var formatter = new RenderedCompactJsonFormatter(); + var formatter = new CompactJsonFormatter(); + // var formatter = new ExpressionTemplate( + // "{ {_t: @t, _msg: @m, _props: @p} }\n"); builder.Host.UseSerilog((context, services, config) => + { config.ReadFrom.Configuration(context.Configuration) .ReadFrom.Services(services) .Enrich.FromLogContext() - .WriteTo.Console() + .WriteTo.Console(formatter) .WriteTo.Seq("http://localhost:5341") - // .WriteTo.File("logs/aspnet-.txt", rollingInterval: RollingInterval.Minute) - ); + .WriteTo.File(formatter, "logs/aspnet-.txt", rollingInterval: RollingInterval.Minute); + }); // Add services to the container. From 1d8a67101901c74be8477c7ee4c92cc3a4f0138c Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 4 Sep 2022 13:12:21 +0800 Subject: [PATCH 268/424] add TraceMiddleware --- .../Controllers/WeatherForecastController.cs | 15 ++++++++-- .../Lab.SerilogProject.WebApi/Program.cs | 16 +++++++++-- .../TraceMiddleware.cs | 28 +++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/TraceMiddleware.cs diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Controllers/WeatherForecastController.cs b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Controllers/WeatherForecastController.cs index cabf5eac..2a894e7d 100644 --- a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Controllers/WeatherForecastController.cs +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Controllers/WeatherForecastController.cs @@ -15,14 +15,25 @@ public class WeatherForecastController : ControllerBase public WeatherForecastController(ILogger logger) { - _logger = logger; + this._logger = logger; } [HttpGet(Name = "GetWeatherForecast")] public IEnumerable Get() { + // using var scope = this._logger.BeginScope(new Dictionary + // { + // ["UserId"] = "svrooij", + // ["OperationType"] = "update", + // }); + + // UserId and OperationType are set for all logging events in these brackets this._logger.LogInformation(new EventId(2000, "Trace"), "Start {ControllerName}.{MethodName}...", - nameof(WeatherForecastController), nameof(Get)); + nameof(WeatherForecastController), nameof(this.Get)); + + var sensorInput = new { Latitude = 25, Longitude = 134 }; + this._logger.LogInformation("Processing {@SensorInput}", sensorInput); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs index 68b44832..6f997491 100644 --- a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs @@ -1,3 +1,4 @@ +using Lab.SerilogProject.WebApi; using Serilog; using Serilog.Events; using Serilog.Formatting.Compact; @@ -21,11 +22,11 @@ var builder = WebApplication.CreateBuilder(args); // builder.Host.UseSerilog(); //<=== 讓 Host 使用 Serilog - // var formatter = new JsonFormatter(); + var formatter = new JsonFormatter(); // var formatter = new MessageTemplateTextFormatter(); // var formatter = new RawFormatter(); // var formatter = new RenderedCompactJsonFormatter(); - var formatter = new CompactJsonFormatter(); + // var formatter = new CompactJsonFormatter(); // var formatter = new ExpressionTemplate( // "{ {_t: @t, _msg: @m, _props: @p} }\n"); builder.Host.UseSerilog((context, services, config) => @@ -55,8 +56,17 @@ app.UseSwaggerUI(); } + app.UseMiddleware(); app.UseHttpsRedirection(); - app.UseSerilogRequestLogging(); //<=== 每一個 Request 使用 Serilog 記錄下來 + // app.UseSerilogRequestLogging(); //<=== 每一個 Request 使用 Serilog 記錄下來 + app.UseSerilogRequestLogging(options => + { + options.EnrichDiagnosticContext = (diagnosticContext, httpContext) => + { + diagnosticContext.Set("UserId", "svrooij"); + diagnosticContext.Set("OperationType", "update"); + }; + }); app.UseAuthorization(); app.MapControllers(); diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/TraceMiddleware.cs b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/TraceMiddleware.cs new file mode 100644 index 00000000..8438d65c --- /dev/null +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/TraceMiddleware.cs @@ -0,0 +1,28 @@ +namespace Lab.SerilogProject.WebApi; + +public class TraceMiddleware +{ + private readonly RequestDelegate _next; + + public TraceMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext context, ILogger logger) + { + using (logger.BeginScope(new Dictionary + { + ["UserId"] = "svrooij", + ["OperationType"] = "update", + })) + { + await this._next.Invoke(context); + } + + // using (logger.BeginScope("{_rid}", Guid.NewGuid())) + // { + // await this._next.Invoke(context); + // } + } +} From 9ce85bd504f4e03fd8c3ac3a6d6a06e96b0a3a5c Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 4 Sep 2022 15:07:59 +0800 Subject: [PATCH 269/424] add console project --- .../Lab.SerilogProject.ConsoleApp.csproj | 20 ++++++++ .../LabBackgroundService.cs | 23 +++++++++ .../Lab.SerilogProject.ConsoleApp/Program.cs | 49 +++++++++++++++++++ .../Lab.SerilogProject.WebApi/Program.cs | 4 -- .../Lab.SerilogProject/Lab.SerilogProject.sln | 6 +++ 5 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.ConsoleApp/Lab.SerilogProject.ConsoleApp.csproj create mode 100644 StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.ConsoleApp/LabBackgroundService.cs create mode 100644 StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.ConsoleApp/Program.cs diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.ConsoleApp/Lab.SerilogProject.ConsoleApp.csproj b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.ConsoleApp/Lab.SerilogProject.ConsoleApp.csproj new file mode 100644 index 00000000..f2c6b3cb --- /dev/null +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.ConsoleApp/Lab.SerilogProject.ConsoleApp.csproj @@ -0,0 +1,20 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + + + diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.ConsoleApp/LabBackgroundService.cs b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.ConsoleApp/LabBackgroundService.cs new file mode 100644 index 00000000..10ed3f86 --- /dev/null +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.ConsoleApp/LabBackgroundService.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Lab.SerilogProject.ConsoleApp; + +public class LabBackgroundService : BackgroundService +{ + private readonly ILogger _logger; + + public LabBackgroundService(ILogger logger) + { + this._logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + this._logger.LogInformation(new EventId(2000, "Trace"), "Start {ClassName}.{MethodName}...", + nameof(LabBackgroundService), nameof(this.ExecuteAsync)); + + var sensorInput = new { Latitude = 25, Longitude = 134 }; + this._logger.LogInformation("Processing {@SensorInput}", sensorInput); + } +} \ No newline at end of file diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.ConsoleApp/Program.cs b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.ConsoleApp/Program.cs new file mode 100644 index 00000000..3c93f5fa --- /dev/null +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.ConsoleApp/Program.cs @@ -0,0 +1,49 @@ +using Lab.SerilogProject.ConsoleApp; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Serilog; +using Serilog.Formatting.Json; + +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.File("logs/host-.txt", rollingInterval: RollingInterval.Day) + .CreateBootstrapLogger() + ; + +try +{ + Log.Information("Starting host"); + + var builder = Host.CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + services.AddHostedService(); + }) + .UseSerilog((context, services, config) => + { + var formatter = new JsonFormatter(); + + config.ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext() + .WriteTo.Console(formatter) + .WriteTo.Seq("http://localhost:5341") + .WriteTo.File(formatter, "logs/app-.txt", rollingInterval: RollingInterval.Minute); + }); + ; + var host = builder.Build(); + host.StartAsync(); + host.StopAsync(); + Console.WriteLine("Bye~~~"); +} +catch (Exception ex) +{ + Log.Fatal(ex, "Host terminated unexpectedly"); + throw; +} +finally +{ + Log.CloseAndFlush(); +} \ No newline at end of file diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs index 6f997491..111d31f7 100644 --- a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.WebApi/Program.cs @@ -1,11 +1,7 @@ using Lab.SerilogProject.WebApi; using Serilog; using Serilog.Events; -using Serilog.Formatting.Compact; -using Serilog.Formatting.Display; using Serilog.Formatting.Json; -using Serilog.Formatting.Raw; -using Serilog.Templates; Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() diff --git a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.sln b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.sln index 263368a0..ba64d262 100644 --- a/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.sln +++ b/StructLog/Lab.SerilogProject/src/Lab.SerilogProject/Lab.SerilogProject.sln @@ -8,6 +8,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{572427 ..\..\Taskfile.yml = ..\..\Taskfile.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SerilogProject.ConsoleApp", "Lab.SerilogProject.ConsoleApp\Lab.SerilogProject.ConsoleApp.csproj", "{6BFCDC2D-787D-466B-BB43-70DABD0E9B1F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {D58F5F77-C34C-4971-B044-565BED8C9A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU {D58F5F77-C34C-4971-B044-565BED8C9A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU {D58F5F77-C34C-4971-B044-565BED8C9A5D}.Release|Any CPU.Build.0 = Release|Any CPU + {6BFCDC2D-787D-466B-BB43-70DABD0E9B1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6BFCDC2D-787D-466B-BB43-70DABD0E9B1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BFCDC2D-787D-466B-BB43-70DABD0E9B1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6BFCDC2D-787D-466B-BB43-70DABD0E9B1F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From bd405ad5135923aee4e8efe1474d7b0d7906fb30 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 14 Sep 2022 02:18:09 +0800 Subject: [PATCH 270/424] add health check project --- Health Check/Lab.HealthCheck/.gitignore | 350 ++++++++++++++++++ .../Controllers/WeatherForecastController.cs | 32 ++ .../Lab.HealthCheck.WebApi.csproj | 17 + .../src/Lab.HealthCheck.WebApi/Program.cs | 70 ++++ .../Lab.HealthCheck.WebApi/WeatherForecast.cs | 12 + .../appsettings.Development.json | 8 + .../Lab.HealthCheck.WebApi/appsettings.json | 19 + .../Lab.HealthCheck/src/Lab.HealthCheck.sln | 21 ++ 8 files changed, 529 insertions(+) create mode 100644 Health Check/Lab.HealthCheck/.gitignore create mode 100644 Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Controllers/WeatherForecastController.cs create mode 100644 Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Lab.HealthCheck.WebApi.csproj create mode 100644 Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Program.cs create mode 100644 Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/WeatherForecast.cs create mode 100644 Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/appsettings.Development.json create mode 100644 Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/appsettings.json create mode 100644 Health Check/Lab.HealthCheck/src/Lab.HealthCheck.sln diff --git a/Health Check/Lab.HealthCheck/.gitignore b/Health Check/Lab.HealthCheck/.gitignore new file mode 100644 index 00000000..81c554f7 --- /dev/null +++ b/Health Check/Lab.HealthCheck/.gitignore @@ -0,0 +1,350 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +#vistual studio extension +.localhistory + +# stress test output +stress/output + +# specflow feature.cs +**/*.feature.cs + +# secrets +secrets + +.DS_Store +*.zip + +deployments + +# minio local s3 +minio \ No newline at end of file diff --git a/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Controllers/WeatherForecastController.cs b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..0c74e752 --- /dev/null +++ b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Controllers/WeatherForecastController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.HealthCheck.WebApi.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } +} \ No newline at end of file diff --git a/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Lab.HealthCheck.WebApi.csproj b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Lab.HealthCheck.WebApi.csproj new file mode 100644 index 00000000..ab249abb --- /dev/null +++ b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Lab.HealthCheck.WebApi.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Program.cs b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Program.cs new file mode 100644 index 00000000..c9e7ff0d --- /dev/null +++ b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Program.cs @@ -0,0 +1,70 @@ +using System.Net.Mime; +using System.Text.Json; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// builder.Services.AddHealthChecks(); + +builder.Services.AddHealthChecksUI() + .AddInMemoryStorage(); +builder.Services.AddHealthChecks() + .AddUrlGroup(new Uri("https://www.google.com1"), "3rd api health check", tags: new[] { "3rd" }); +; +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); +app.MapHealthChecks("/_hc", + new HealthCheckOptions() + { + // ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + + // ResponseWriter = (context, report) => + // { + // context.Response.ContentType = MediaTypeNames.Text.Plain; + // return context.Response.WriteAsync("OK"); + // } + + ResponseWriter = async (context, report) => + { + var result = JsonSerializer.Serialize( + new + { + status = report.Status.ToString(), + errors = report.Entries + .Select(e => + new + { + key = e.Key, + value = Enum.GetName(typeof(HealthStatus), e.Value.Status) + }) + }); + context.Response.ContentType = MediaTypeNames.Application.Json; + await context.Response.WriteAsync(result); + } + }); + +app.UseHealthChecksUI(options => { options.UIPath = "/_hc-ui"; }); + +app.Run(); \ No newline at end of file diff --git a/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/WeatherForecast.cs b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/WeatherForecast.cs new file mode 100644 index 00000000..a58e9e2b --- /dev/null +++ b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace Lab.HealthCheck.WebApi; + +public class WeatherForecast +{ + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/appsettings.Development.json b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/appsettings.json b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/appsettings.json new file mode 100644 index 00000000..70302d43 --- /dev/null +++ b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/appsettings.json @@ -0,0 +1,19 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "HealthChecks-UI": { + "HealthChecks": [ + { + "Name": "Health Check Demo", + "Uri": "_hc" + } + ], + "EvaluationTimeOnSeconds": 10, + "MinimumSecondsBetweenFailureNotifications": 60 + } +} diff --git a/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.sln b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.sln new file mode 100644 index 00000000..23b90e0f --- /dev/null +++ b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.sln @@ -0,0 +1,21 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.HealthCheck.WebApi", "Lab.HealthCheck.WebApi\Lab.HealthCheck.WebApi.csproj", "{2DF0910D-04FC-4EF7-995C-3708D2BB5F4C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{2695B84D-A033-4A82-853A-3A28997D52B9}" + ProjectSection(SolutionItems) = preProject + ..\.gitignore = ..\.gitignore + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2DF0910D-04FC-4EF7-995C-3708D2BB5F4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DF0910D-04FC-4EF7-995C-3708D2BB5F4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DF0910D-04FC-4EF7-995C-3708D2BB5F4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DF0910D-04FC-4EF7-995C-3708D2BB5F4C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From 9209d1a273427a019a2a39fdf927562cbb0fca88 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 14 Sep 2022 23:45:49 +0800 Subject: [PATCH 271/424] add npgsql check --- .../Lab.HealthCheck.WebApi.csproj | 1 + .../src/Lab.HealthCheck.WebApi/Program.cs | 64 ++++++++++++------- .../Lab.HealthCheck.WebApi/appsettings.json | 10 ++- 3 files changed, 49 insertions(+), 26 deletions(-) diff --git a/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Lab.HealthCheck.WebApi.csproj b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Lab.HealthCheck.WebApi.csproj index ab249abb..daf2cb87 100644 --- a/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Lab.HealthCheck.WebApi.csproj +++ b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Lab.HealthCheck.WebApi.csproj @@ -7,6 +7,7 @@ + diff --git a/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Program.cs b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Program.cs index c9e7ff0d..9d4db86c 100644 --- a/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Program.cs +++ b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/Program.cs @@ -16,11 +16,22 @@ // builder.Services.AddHealthChecks(); -builder.Services.AddHealthChecksUI() +builder.Services.AddHealthChecksUI(p=> + { + p.AddHealthCheckEndpoint("Readiness", "/_readiness"); + p.AddHealthCheckEndpoint("Liveness", "/_liveness"); + }) .AddInMemoryStorage(); + builder.Services.AddHealthChecks() - .AddUrlGroup(new Uri("https://www.google.com1"), "3rd api health check", tags: new[] { "3rd" }); -; + .AddUrlGroup(new Uri("https://www.google.com1"), "3rd API", tags: new[] { "3rd API", "google" }) + .AddNpgSql( + npgsqlConnectionString: "Host=localhost;Port=5432;Database=member_service;Username=postgres;Password=guest", + healthQuery: "SELECT 1;", + name: "db", + failureStatus: HealthStatus.Unhealthy, + tags: new[] { "db", "sql", "PostgreSQL" }) + ; var app = builder.Build(); // Configure the HTTP request pipeline. @@ -35,10 +46,17 @@ app.UseAuthorization(); app.MapControllers(); -app.MapHealthChecks("/_hc", +app.MapHealthChecks("/_liveness", new HealthCheckOptions() +{ + Predicate = _ => false, //只檢查應用程式本身 + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse +}); + +app.MapHealthChecks("/_readiness", new HealthCheckOptions() { - // ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + //檢查應用程式所依賴的服務 + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse // ResponseWriter = (context, report) => // { @@ -46,25 +64,25 @@ // return context.Response.WriteAsync("OK"); // } - ResponseWriter = async (context, report) => - { - var result = JsonSerializer.Serialize( - new - { - status = report.Status.ToString(), - errors = report.Entries - .Select(e => - new - { - key = e.Key, - value = Enum.GetName(typeof(HealthStatus), e.Value.Status) - }) - }); - context.Response.ContentType = MediaTypeNames.Application.Json; - await context.Response.WriteAsync(result); - } + // ResponseWriter = async (context, report) => + // { + // var result = JsonSerializer.Serialize( + // new + // { + // status = report.Status.ToString(), + // errors = report.Entries + // .Select(e => + // new + // { + // key = e.Key, + // value = Enum.GetName(typeof(HealthStatus), e.Value.Status) + // }) + // }); + // context.Response.ContentType = MediaTypeNames.Application.Json; + // await context.Response.WriteAsync(result); + // } }); -app.UseHealthChecksUI(options => { options.UIPath = "/_hc-ui"; }); +app.UseHealthChecksUI(options => { options.UIPath = "/_hc"; }); app.Run(); \ No newline at end of file diff --git a/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/appsettings.json b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/appsettings.json index 70302d43..4af1a8c6 100644 --- a/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/appsettings.json +++ b/Health Check/Lab.HealthCheck/src/Lab.HealthCheck.WebApi/appsettings.json @@ -6,11 +6,15 @@ } }, "AllowedHosts": "*", - "HealthChecks-UI": { + "//HealthChecks-UI": { "HealthChecks": [ { - "Name": "Health Check Demo", - "Uri": "_hc" + "Name": "Readiness1", + "Uri": "_readiness" + }, + { + "Name": "Liveness", + "Uri": "_liveness" } ], "EvaluationTimeOnSeconds": 10, From 02b2abf6fc2ec77a1618d053224c4f12d92bfeaf Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 21 Sep 2022 00:22:45 +0800 Subject: [PATCH 272/424] feat: add k6 sample --- Test/Lab k6 sample/1.js | 7 +++++++ Test/Lab k6 sample/data/users.json | 14 ++++++++++++++ Test/Lab k6 sample/read-json-file.js | 16 ++++++++++++++++ Test/Lab k6 sample/set-env.js | 8 ++++++++ Test/Lab k6 sample/set-sys-env.js | 8 ++++++++ Test/Lab k6 sample/to-html-report.js | 18 ++++++++++++++++++ 6 files changed, 71 insertions(+) create mode 100644 Test/Lab k6 sample/1.js create mode 100644 Test/Lab k6 sample/data/users.json create mode 100644 Test/Lab k6 sample/read-json-file.js create mode 100644 Test/Lab k6 sample/set-env.js create mode 100644 Test/Lab k6 sample/set-sys-env.js create mode 100644 Test/Lab k6 sample/to-html-report.js diff --git a/Test/Lab k6 sample/1.js b/Test/Lab k6 sample/1.js new file mode 100644 index 00000000..0f24eabc --- /dev/null +++ b/Test/Lab k6 sample/1.js @@ -0,0 +1,7 @@ +import http from 'k6/http'; +import {sleep} from 'k6'; + +export default function () { + http.get('http://test.k6.io'); + sleep(1); +} \ No newline at end of file diff --git a/Test/Lab k6 sample/data/users.json b/Test/Lab k6 sample/data/users.json new file mode 100644 index 00000000..c3bda56a --- /dev/null +++ b/Test/Lab k6 sample/data/users.json @@ -0,0 +1,14 @@ +[ + { + "username": "user1", + "password": "password1" + }, + { + "username": "user2", + "password": "password2" + }, + { + "username": "user3", + "password": "password3" + } +] diff --git a/Test/Lab k6 sample/read-json-file.js b/Test/Lab k6 sample/read-json-file.js new file mode 100644 index 00000000..ed03a89e --- /dev/null +++ b/Test/Lab k6 sample/read-json-file.js @@ -0,0 +1,16 @@ +import {SharedArray} from 'k6/data'; +import {sleep} from 'k6'; + +const data = new SharedArray('users', function () { + // const d = open('D:\\src\\sample.dotblog\\Test\\Lab k6 sample\\users.json'); + // here you can open files, and then do additional processing or generate the array with data dynamically + const f = JSON.parse(open('./data/users.json')); + return f; // f must be an array[] +}); + +export default () => { + console.log('Getting random user...'); + const randomUser = data[Math.floor(Math.random() * data.length)]; + console.log(`${randomUser.username}, ${randomUser.password}`); + sleep(3); +}; diff --git a/Test/Lab k6 sample/set-env.js b/Test/Lab k6 sample/set-env.js new file mode 100644 index 00000000..4f867c7b --- /dev/null +++ b/Test/Lab k6 sample/set-env.js @@ -0,0 +1,8 @@ +import http from 'k6/http'; +import { sleep } from 'k6'; + +export default function () { + console.log(`User agent is '${__ENV.MY_USER_AGENT}'`); + http.get('http://test.k6.io'); + sleep(1); +} diff --git a/Test/Lab k6 sample/set-sys-env.js b/Test/Lab k6 sample/set-sys-env.js new file mode 100644 index 00000000..201967e1 --- /dev/null +++ b/Test/Lab k6 sample/set-sys-env.js @@ -0,0 +1,8 @@ +import http from 'k6/http'; +import { sleep } from 'k6'; + +export default function () { + console.log(`User agent is '${__ENV.ASPNETCORE_ENVIRONMENT }'`); + http.get('http://test.k6.io'); + sleep(1); +} diff --git a/Test/Lab k6 sample/to-html-report.js b/Test/Lab k6 sample/to-html-report.js new file mode 100644 index 00000000..2597bbef --- /dev/null +++ b/Test/Lab k6 sample/to-html-report.js @@ -0,0 +1,18 @@ +import {htmlReport} from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js"; +import {textSummary} from "https://jslib.k6.io/k6-summary/0.0.1/index.js"; + +import http from 'k6/http'; +import {sleep} from 'k6'; + +export default function () { + console.log(`User agent is '${__ENV.MY_USER_AGENT}'`); + http.get('http://test.k6.io'); + sleep(1); +} + +export function handleSummary(data) { + return { + "result.html": htmlReport(data), + stdout: textSummary(data, { indent: " ", enableColors: true }), + }; +} \ No newline at end of file From c38dcadd9ddf2db652921f02d1a181e28a17c139 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 25 Sep 2022 21:34:22 +0800 Subject: [PATCH 273/424] feat: add project --- .../Controllers/DemoController.cs | 22 ++++ ...re.Security.MultiAuthenticationSite.csproj | 13 ++ .../Program.cs | 34 ++++++ .../BasicAuthenticationDefaults.cs | 6 + .../BasicAuthenticationExtensions.cs | 39 ++++++ .../BasicAuthenticationHandler.cs | 112 ++++++++++++++++++ .../BasicAuthenticationOptions.cs | 8 ++ ...BasicAuthenticationPostConfigureOptions.cs | 14 +++ .../BasicAuthenticationProvider.cs | 24 ++++ .../IBasicAuthenticationProvider.cs | 6 + .../appsettings.Development.json | 8 ++ .../appsettings.json | 9 ++ .../Lab.AspNetCore.Security.sln | 6 + 13 files changed, 301 insertions(+) create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Controllers/DemoController.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Lab.AspNetCore.Security.MultiAuthenticationSite.csproj create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/appsettings.Development.json create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/appsettings.json diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Controllers/DemoController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Controllers/DemoController.cs new file mode 100644 index 00000000..421c0caa --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Controllers/DemoController.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Controllers; + +[ApiController] +[Route("[controller]")] +public class DemoController : ControllerBase +{ + private readonly ILogger _logger; + + public DemoController(ILogger logger) + { + _logger = logger; + } + + [Authorize] + public ActionResult Get() + { + return this.Ok("OK~好"); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Lab.AspNetCore.Security.MultiAuthenticationSite.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Lab.AspNetCore.Security.MultiAuthenticationSite.csproj new file mode 100644 index 00000000..b9baca3e --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Lab.AspNetCore.Security.MultiAuthenticationSite.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs new file mode 100644 index 00000000..4dc58c31 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs @@ -0,0 +1,34 @@ +using Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme) + .AddBasic(o => { o.Realm = "My App"; }); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseStaticFiles(); +app.UseStatusCodePages(); +app.UseHttpsRedirection(); +app.UseRouting(); + +// app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs new file mode 100644 index 00000000..0b345dc1 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs @@ -0,0 +1,6 @@ +namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; + +public static class BasicAuthenticationDefaults +{ + public const string AuthenticationScheme = "Basic"; +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs new file mode 100644 index 00000000..00c36f85 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Options; + +namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; + +public static class BasicAuthenticationExtensions +{ + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder) + where TAuthService : class, IBasicAuthenticationProvider + { + return AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, _ => { }); + } + + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, + string authenticationScheme) + where TAuthService : class, IBasicAuthenticationProvider + { + return AddBasic(builder, authenticationScheme, _ => { }); + } + + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, + Action configureOptions) + where TAuthService : class, IBasicAuthenticationProvider + { + return AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, configureOptions); + } + + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, + string authenticationScheme, Action configureOptions) + where TAuthService : class, IBasicAuthenticationProvider + { + builder.Services + .AddSingleton, BasicAuthenticationPostConfigureOptions>(); + builder.Services.AddTransient(); + + return builder.AddScheme( + authenticationScheme, configureOptions); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs new file mode 100644 index 00000000..1450a13f --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs @@ -0,0 +1,112 @@ +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; + +namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; + +public class BasicAuthenticationHandler : AuthenticationHandler +{ + private readonly IBasicAuthenticationProvider _authenticationProvider; + + public BasicAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock, + IBasicAuthenticationProvider authenticationProvider) + : base(options, logger, encoder, clock) + { + this._authenticationProvider = authenticationProvider; + } + + private string _failReason; + + protected override async Task HandleAuthenticateAsync() + { + var schemeName = this.Scheme.Name; //由外部注入 + var endpoint = this.Context.GetEndpoint(); + if (endpoint?.Metadata?.GetMetadata() != null) + { + return AuthenticateResult.NoResult(); + } + + if (!this.Request.Headers.ContainsKey(HeaderNames.Authorization)) + { + this._failReason = "Invalid basic authentication header"; + return AuthenticateResult.Fail(this._failReason); + } + + if (!AuthenticationHeaderValue.TryParse(this.Request.Headers[HeaderNames.Authorization], + out var authHeaderValue)) + { + this._failReason = "Invalid authorization Header"; + return AuthenticateResult.Fail(this._failReason); + } + + if (authHeaderValue.Scheme.StartsWith(schemeName, StringComparison.InvariantCultureIgnoreCase) == false) + { + this._failReason = "Invalid authorization scheme name"; + return AuthenticateResult.Fail("Invalid authorization scheme name"); + } + + var credentialBytes = Convert.FromBase64String(authHeaderValue.Parameter); + var userAndPassword = Encoding.UTF8.GetString(credentialBytes); + var credentials = userAndPassword.Split(':'); + if (credentials.Length != 2) + { + this._failReason = "Invalid basic authentication header"; + return AuthenticateResult.Fail(this._failReason); + } + + var user = credentials[0]; + var password = credentials[1]; + + var isValidate = await this._authenticationProvider.IsValidateAsync(user, password, CancellationToken.None); + + if (!isValidate) + { + this._failReason = "Invalid username or password"; + return AuthenticateResult.Fail(this._failReason); + } + + return this.SignIn(user); + } + + private AuthenticateResult SignIn(string user) + { + var schemeName = this.Scheme.Name; + var claims = new[] { new Claim(ClaimTypes.Name, user) }; + var identity = new ClaimsIdentity(claims, schemeName); + var principal = new ClaimsPrincipal(identity); + var ticket = new AuthenticationTicket(principal, schemeName); + return AuthenticateResult.Success(ticket); + } + + protected override async Task HandleChallengeAsync(AuthenticationProperties properties) + { + // 寫入詳細的失敗原因,排除敏感性資料 + this.Logger.LogInformation("{FailureReason}", new + { + Code = "InvalidAuthentication", + Message = this._failReason + }); + + this.Response.StatusCode = 401; + this.Response.HttpContext.Features.Get().ReasonPhrase = this._failReason; + this.Response.Headers["WWW-Authenticate"] = $"Basic realm=\"{this.Options.Realm}\", charset=\"UTF-8\""; + + // 響應粗糙的內容,這不是標準的 Basic Authentication 失敗的回傳,僅是為了示意 + this.Response.WriteAsJsonAsync(new + { + Code = "InvalidAuthentication", + Message = "Please contact your administrator" + }); + await Task.CompletedTask; + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs new file mode 100644 index 00000000..86f5bd4e --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Authentication; + +namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; + +public class BasicAuthenticationOptions : AuthenticationSchemeOptions +{ + public string Realm { get; set; } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs new file mode 100644 index 00000000..175850e7 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Options; + +namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; + +public class BasicAuthenticationPostConfigureOptions : IPostConfigureOptions +{ + public void PostConfigure(string name, BasicAuthenticationOptions options) + { + if (string.IsNullOrEmpty(options.Realm)) + { + throw new InvalidOperationException("Realm must be provided in options"); + } + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs new file mode 100644 index 00000000..d90ec34b --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs @@ -0,0 +1,24 @@ +namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; + +public class BasicAuthenticationProvider : IBasicAuthenticationProvider +{ + private readonly Dictionary _clientIdentities = new(StringComparer.InvariantCultureIgnoreCase) + { + { "yao", "9527" } + }; + + public Task IsValidateAsync(string user, string password, CancellationToken cancel = default) + { + if (this._clientIdentities.TryGetValue(user, out var secret) == false) + { + return Task.FromResult(false); + } + + if (password != secret) + { + return Task.FromResult(false); + } + + return Task.FromResult(true); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs new file mode 100644 index 00000000..54602809 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs @@ -0,0 +1,6 @@ +namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; + +public interface IBasicAuthenticationProvider +{ + Task IsValidateAsync(string user, string password, CancellationToken cancel); +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/appsettings.Development.json b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/appsettings.json b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln index 1fc06b88..8f181734 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln @@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AspNetCore.Security.Bas EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest", "Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest\Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest.csproj", "{12F00FC6-1D31-48CD-AB17-B00F76846A33}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AspNetCore.Security.MultiAuthenticationSite", "Lab.AspNetCore.Security.MultiAuthenticationSite\Lab.AspNetCore.Security.MultiAuthenticationSite.csproj", "{FB32C9D2-A7C7-4199-9477-AA5EEA7D818F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,5 +26,9 @@ Global {12F00FC6-1D31-48CD-AB17-B00F76846A33}.Debug|Any CPU.Build.0 = Debug|Any CPU {12F00FC6-1D31-48CD-AB17-B00F76846A33}.Release|Any CPU.ActiveCfg = Release|Any CPU {12F00FC6-1D31-48CD-AB17-B00F76846A33}.Release|Any CPU.Build.0 = Release|Any CPU + {FB32C9D2-A7C7-4199-9477-AA5EEA7D818F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB32C9D2-A7C7-4199-9477-AA5EEA7D818F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB32C9D2-A7C7-4199-9477-AA5EEA7D818F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB32C9D2-A7C7-4199-9477-AA5EEA7D818F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From e186adf78c550be9d2ae8d0c8ea2dfdc179762d1 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 25 Sep 2022 22:27:18 +0800 Subject: [PATCH 274/424] fix bug --- .../Program.cs | 5 +-- .../BasicAuthenticationExtensions.cs | 41 +++++++++---------- .../BasicAuthenticationHandler.cs | 26 ++++++------ 3 files changed, 34 insertions(+), 38 deletions(-) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs index 4dc58c31..621214f9 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs @@ -9,8 +9,7 @@ // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -builder.Services.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme) - .AddBasic(o => { o.Realm = "My App"; }); +builder.Services.AddBasicAuthentication(o => o.Realm = "Basic Authentication"); var app = builder.Build(); @@ -26,7 +25,7 @@ app.UseHttpsRedirection(); app.UseRouting(); -// app.UseAuthentication(); +app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs index 00c36f85..24f9795a 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs @@ -5,35 +5,32 @@ namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authenticatio public static class BasicAuthenticationExtensions { - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder) - where TAuthService : class, IBasicAuthenticationProvider - { - return AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, _ => { }); - } - - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, - string authenticationScheme) - where TAuthService : class, IBasicAuthenticationProvider - { - return AddBasic(builder, authenticationScheme, _ => { }); - } - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, + string authenticationScheme, + string displayName, Action configureOptions) where TAuthService : class, IBasicAuthenticationProvider - { - return AddBasic(builder, BasicAuthenticationDefaults.AuthenticationScheme, configureOptions); - } - - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, - string authenticationScheme, Action configureOptions) - where TAuthService : class, IBasicAuthenticationProvider { builder.Services .AddSingleton, BasicAuthenticationPostConfigureOptions>(); - builder.Services.AddTransient(); + builder.Services.AddSingleton(); return builder.AddScheme( - authenticationScheme, configureOptions); + authenticationScheme, + displayName, + configureOptions); + } + + public static AuthenticationBuilder AddBasicAuthentication(this IServiceCollection services, + Action configureOptions) + where TAuthService : class, IBasicAuthenticationProvider + { + var scheme = BasicAuthenticationDefaults.AuthenticationScheme; + return services.AddAuthentication(o => + { + o.DefaultAuthenticateScheme = scheme; + o.DefaultChallengeScheme = scheme; + }) + .AddBasic(scheme, scheme, configureOptions); } } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs index 1450a13f..854a5a70 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs @@ -14,6 +14,8 @@ public class BasicAuthenticationHandler : AuthenticationHandler options, ILoggerFactory logger, @@ -25,11 +27,9 @@ public BasicAuthenticationHandler( this._authenticationProvider = authenticationProvider; } - private string _failReason; - protected override async Task HandleAuthenticateAsync() { - var schemeName = this.Scheme.Name; //由外部注入 + var schemeName = this.Scheme.Name; var endpoint = this.Context.GetEndpoint(); if (endpoint?.Metadata?.GetMetadata() != null) { @@ -78,16 +78,6 @@ protected override async Task HandleAuthenticateAsync() return this.SignIn(user); } - private AuthenticateResult SignIn(string user) - { - var schemeName = this.Scheme.Name; - var claims = new[] { new Claim(ClaimTypes.Name, user) }; - var identity = new ClaimsIdentity(claims, schemeName); - var principal = new ClaimsPrincipal(identity); - var ticket = new AuthenticationTicket(principal, schemeName); - return AuthenticateResult.Success(ticket); - } - protected override async Task HandleChallengeAsync(AuthenticationProperties properties) { // 寫入詳細的失敗原因,排除敏感性資料 @@ -109,4 +99,14 @@ protected override async Task HandleChallengeAsync(AuthenticationProperties prop }); await Task.CompletedTask; } + + private AuthenticateResult SignIn(string user) + { + var schemeName = this.Scheme.Name; + var claims = new[] { new Claim(ClaimTypes.Name, user) }; + var identity = new ClaimsIdentity(claims, schemeName); + var principal = new ClaimsPrincipal(identity); + var ticket = new AuthenticationTicket(principal, schemeName); + return AuthenticateResult.Success(ticket); + } } \ No newline at end of file From 1c869ec3b29c8c603b105b1a8bb17b78632574f2 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 25 Sep 2022 22:45:06 +0800 Subject: [PATCH 275/424] refactor --- ...r\345\226\256\345\205\203\346\270\254\350\251\246.cs" | 9 +++------ ...e\345\226\256\345\205\203\346\270\254\350\251\246.cs" | 3 +-- .../Controllers/DemoController.cs | 8 ++++---- .../Properties/launchSettings.json | 4 ++-- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" index aa95c4f2..df85afc4 100644 --- "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" +++ "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" @@ -100,8 +100,7 @@ private static async Task CreateTestClient() .ConfigureServices( services => { - services.AddSingleton(); - services.AddBasicAuthentication(_ => { }); + services.AddBasicAuthentication(_ => { }); services.AddAuthorization(); }) .Configure(app => @@ -124,8 +123,7 @@ private static async Task CreateTestHost() .ConfigureServices( services => { - services.AddSingleton(); - services.AddBasicAuthentication(_ => { }); + services.AddBasicAuthentication(_ => { }); services.AddAuthorization(); }) .Configure(app => @@ -147,8 +145,7 @@ private static async Task CreateTestServer() .ConfigureServices( services => { - services.AddSingleton(); - services.AddBasicAuthentication(_ => { }); + services.AddBasicAuthentication(_ => { }); services.AddAuthorization(); }) .Configure(app => diff --git "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" index 91941d36..6dd29f8f 100644 --- "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" +++ "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" @@ -56,8 +56,7 @@ private static async Task CreateTestServer() .ConfigureServices( services => { - services.AddSingleton(); - services.AddBasicAuthentication(_ => { }); + services.AddBasicAuthentication(_ => { }); services.AddAuthorization(); }) .Configure(app => diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs index 65298d94..2bc36483 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs @@ -11,13 +11,13 @@ public class DemoController : ControllerBase public DemoController(ILogger logger) { - this._logger = logger; + _logger = logger; } - [AllowAnonymous] [HttpGet] - public async Task Get() + [Authorize] + public ActionResult Get() { - return this.Ok("好"); + return this.Ok("OK~好"); } } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Properties/launchSettings.json b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Properties/launchSettings.json index 4ed84fd8..3018ce92 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Properties/launchSettings.json +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Properties/launchSettings.json @@ -12,7 +12,7 @@ "Lab.AspNetCore.Security.BasicAuthenticationSite": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": true, + "launchBrowser": false, "launchUrl": "swagger", "applicationUrl": "https://localhost:7089;http://localhost:5089", "environmentVariables": { @@ -21,7 +21,7 @@ }, "IIS Express": { "commandName": "IISExpress", - "launchBrowser": true, + "launchBrowser": false, "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" From fd5bc5425db10bafcda56b9633dd3af0b5f888ba Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 25 Sep 2022 22:46:00 +0800 Subject: [PATCH 276/424] refactor --- ...re.Security.BasicAuthenticationSite.csproj | 5 ---- .../Program.cs | 20 +++++++------- .../BasicAuthenticationDefaults.cs | 2 +- .../BasicAuthenticationExtensions.cs | 27 +++++++++++++------ .../BasicAuthenticationHandler.cs | 17 +++--------- .../BasicAuthenticationOptions.cs | 4 +-- ...BasicAuthenticationPostConfigureOptions.cs | 2 +- .../IBasicAuthenticationProvider.cs | 2 +- 8 files changed, 36 insertions(+), 43 deletions(-) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj index e71b3ef4..58b7e218 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj @@ -11,9 +11,4 @@ - - - - - diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs index 26a528f4..92bc5d6d 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs @@ -28,18 +28,13 @@ DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }); -builder.Services.AddSingleton(); -builder.Services.AddBasicAuthentication(options => { }); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -// builder.Services.AddAuthorization(options => -// { -// options.AddPolicy("Permission", policy => -// policy.Requirements.Add(new PermissionAuthorizationRequirement())); -// }); +builder.Services.AddBasicAuthentication(o => o.Realm = "Basic Authentication"); +// builder.Services.AddSingleton(); +// builder.Services.AddSingleton(); +// builder.Services.AddSingleton(); +// builder.Services.AddSingleton(); +// var app = builder.Build(); // Configure the HTTP request pipeline. @@ -49,7 +44,10 @@ app.UseSwaggerUI(); } +app.UseStaticFiles(); +app.UseStatusCodePages(); app.UseHttpsRedirection(); +app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs index ccd37f3f..5367317d 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs @@ -1,4 +1,4 @@ -namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; public static class BasicAuthenticationDefaults { diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs index 267ade4f..1177f07b 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs @@ -1,25 +1,36 @@ -using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Options; namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; public static class BasicAuthenticationExtensions { - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, + string authenticationScheme, + string displayName, Action configureOptions) + where TAuthService : class, IBasicAuthenticationProvider { + builder.Services + .AddSingleton, BasicAuthenticationPostConfigureOptions>(); + builder.Services.AddSingleton(); + return builder.AddScheme( - BasicAuthenticationDefaults.AuthenticationScheme, - BasicAuthenticationDefaults.AuthenticationScheme, configureOptions); + authenticationScheme, + displayName, + configureOptions); } - public static AuthenticationBuilder AddBasicAuthentication(this IServiceCollection services, + public static AuthenticationBuilder AddBasicAuthentication(this IServiceCollection services, Action configureOptions) + where TAuthService : class, IBasicAuthenticationProvider { + var scheme = BasicAuthenticationDefaults.AuthenticationScheme; return services.AddAuthentication(o => { - o.DefaultAuthenticateScheme = BasicAuthenticationDefaults.AuthenticationScheme; - o.DefaultChallengeScheme = BasicAuthenticationDefaults.AuthenticationScheme; + o.DefaultAuthenticateScheme = scheme; + o.DefaultChallengeScheme = scheme; }) - .AddBasic(configureOptions); + .AddBasic(scheme, scheme, configureOptions); } } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs index 41f3d02b..58033771 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs @@ -1,4 +1,4 @@ -using System.Net.Http.Headers; +using System.Net.Http.Headers; using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; @@ -13,6 +13,7 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authenticatio public class BasicAuthenticationHandler : AuthenticationHandler { private readonly IBasicAuthenticationProvider _authenticationProvider; + private string _failReason; public BasicAuthenticationHandler( @@ -26,21 +27,9 @@ public BasicAuthenticationHandler( this._authenticationProvider = authenticationProvider; } - /// - /// - /// - void CreateTestServer() - { - - } - - void CreateTask() - { - - } protected override async Task HandleAuthenticateAsync() { - var schemeName = this.Scheme.Name; //由外部注入 + var schemeName = this.Scheme.Name; var endpoint = this.Context.GetEndpoint(); if (endpoint?.Metadata?.GetMetadata() != null) { diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs index 735e435f..f2371e81 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs @@ -1,8 +1,8 @@ -using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication; namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; public class BasicAuthenticationOptions : AuthenticationSchemeOptions { - public string Realm { get; set; } = "Demo Site"; + public string Realm { get; set; } } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs index 40c7a136..690eaba0 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs index a5c4d00a..ff5d36fd 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs @@ -1,4 +1,4 @@ -namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; public interface IBasicAuthenticationProvider { From 2b2c81830d4722046a49d0410e9cf638ccf7d656 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 25 Sep 2022 23:18:54 +0800 Subject: [PATCH 277/424] refactor --- ...6\256\345\205\203\346\270\254\350\251\246.cs" | 6 +++++- ...6\256\345\205\203\346\270\254\350\251\246.cs" | 2 +- .../Program.cs | 16 ++++++++++------ .../BasicAuthenticationExtensions.cs | 12 ++++++------ 4 files changed, 22 insertions(+), 14 deletions(-) diff --git "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" index df85afc4..a17358bc 100644 --- "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" +++ "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationHandler\345\226\256\345\205\203\346\270\254\350\251\246.cs" @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -30,6 +31,8 @@ public async Task 驗證成功() using var testHost = await CreateTestHost(); var handler = testHost.Services.GetService(); + var authenticationHandler = testHost.Services.GetService>(); + await handler.InitializeAsync(new AuthenticationScheme("basic", "basic", typeof(BasicAuthenticationHandler)), @@ -123,7 +126,8 @@ private static async Task CreateTestHost() .ConfigureServices( services => { - services.AddBasicAuthentication(_ => { }); + + services.AddBasicAuthentication(o => { o.Realm = "Test"; }); services.AddAuthorization(); }) .Configure(app => diff --git "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" index 6dd29f8f..e3da0a29 100644 --- "a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" +++ "b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite.UnitTest/BasicAuthenticationMiddleware\345\226\256\345\205\203\346\270\254\350\251\246.cs" @@ -56,7 +56,7 @@ private static async Task CreateTestServer() .ConfigureServices( services => { - services.AddBasicAuthentication(_ => { }); + services.AddBasicAuthentication(o => { o.Realm = "Test"; }); services.AddAuthorization(); }) .Configure(app => diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs index 92bc5d6d..33c7224c 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs @@ -19,7 +19,12 @@ // builder.Services.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme) // .AddScheme(BasicAuthenticationDefaults.AuthenticationScheme, -// p => new BasicAuthenticationOptions()); +// p => new BasicAuthenticationOptions() +// { +// Realm = "Basic Authentication" +// }); + +builder.Services.AddBasicAuthentication(o => o.Realm = "Basic Authentication"); builder.Services.AddSingleton(p=>new JsonSerializerOptions { Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs), @@ -28,12 +33,11 @@ DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }); -builder.Services.AddBasicAuthentication(o => o.Realm = "Basic Authentication"); -// builder.Services.AddSingleton(); -// builder.Services.AddSingleton(); -// builder.Services.AddSingleton(); -// builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); // var app = builder.Build(); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs index 1177f07b..ca76b952 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs @@ -5,15 +5,15 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authenticatio public static class BasicAuthenticationExtensions { - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, + public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) - where TAuthService : class, IBasicAuthenticationProvider + where TAuthProvider : class, IBasicAuthenticationProvider { builder.Services .AddSingleton, BasicAuthenticationPostConfigureOptions>(); - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); return builder.AddScheme( authenticationScheme, @@ -21,9 +21,9 @@ public static AuthenticationBuilder AddBasic(this AuthenticationBu configureOptions); } - public static AuthenticationBuilder AddBasicAuthentication(this IServiceCollection services, + public static AuthenticationBuilder AddBasicAuthentication(this IServiceCollection services, Action configureOptions) - where TAuthService : class, IBasicAuthenticationProvider + where TAuthProvider : class, IBasicAuthenticationProvider { var scheme = BasicAuthenticationDefaults.AuthenticationScheme; return services.AddAuthentication(o => @@ -31,6 +31,6 @@ public static AuthenticationBuilder AddBasicAuthentication(this IS o.DefaultAuthenticateScheme = scheme; o.DefaultChallengeScheme = scheme; }) - .AddBasic(scheme, scheme, configureOptions); + .AddBasic(scheme, scheme, configureOptions); } } \ No newline at end of file From 118008ceab1fe12fdc0f61a1e2f8cbacb0d78837 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 26 Sep 2022 22:39:34 +0800 Subject: [PATCH 278/424] =?UTF-8?q?fix:=20=E7=84=A1=E6=B3=95=E8=A7=B8?= =?UTF-8?q?=E7=99=BC=20HandleChallengeAsync=20event?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PermissionAuthorizationMiddlewareResultHandler.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs index 03e594a0..6ba99ad0 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; @@ -7,8 +8,8 @@ namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization public class PermissionAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler { private readonly ILogger _logger; - private readonly JsonSerializerOptions _jsonSerializerOptions; + private readonly AuthorizationMiddlewareResultHandler _defaultHandler = new(); public PermissionAuthorizationMiddlewareResultHandler( ILogger logger, @@ -25,7 +26,7 @@ public async Task HandleAsync( PolicyAuthorizationResult authorizeResult) { var permissionAuthorizationRequirements = policy.Requirements.OfType(); - + if (authorizeResult.Forbidden && permissionAuthorizationRequirements.Any()) { @@ -41,13 +42,14 @@ await context.Response.WriteAsJsonAsync(new { ErrorCode = "Invalid Authorization", ErrorMessages = new[] { "Please contact your administrator" } + // ErrorMessages = authorizeResult.AuthorizationFailure.FailureReasons }, this._jsonSerializerOptions); return; } - await next.Invoke(context); + await this._defaultHandler.HandleAsync(next, context, policy, authorizeResult); - // await next(context); + // await next.Invoke(context); } } \ No newline at end of file From ee15f32b8ff82269fb9843f0e6389ded3d889b74 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 26 Sep 2022 22:53:39 +0800 Subject: [PATCH 279/424] add api key authentication --- ...re.Security.BasicAuthenticationSite.csproj | 1 + .../Program.cs | 15 ++++++-- .../Security/Authentication/ApiKeyProvider.cs | 37 +++++++++++++++++++ .../BasicAuthenticationExtensions.cs | 4 +- 4 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/ApiKeyProvider.cs diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj index 58b7e218..bcc5f904 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj @@ -7,6 +7,7 @@ + diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs index 33c7224c..9a5baf6f 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs @@ -2,6 +2,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Unicode; +using AspNetCore.Authentication.ApiKey; using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization; using Microsoft.AspNetCore.Authorization; @@ -24,6 +25,12 @@ // Realm = "Basic Authentication" // }); +builder.Services.AddAuthentication(ApiKeyDefaults.AuthenticationScheme) + .AddApiKeyInHeaderOrQueryParams(options => + { + options.Realm = "Sample Web API"; + options.KeyName = "X-API-KEY"; + }); builder.Services.AddBasicAuthentication(o => o.Realm = "Basic Authentication"); builder.Services.AddSingleton(p=>new JsonSerializerOptions { @@ -34,10 +41,10 @@ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); +// builder.Services.AddSingleton(); +// builder.Services.AddSingleton(); +// builder.Services.AddSingleton(); +// builder.Services.AddSingleton(); // var app = builder.Build(); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/ApiKeyProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/ApiKeyProvider.cs new file mode 100644 index 00000000..ba321ad2 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/ApiKeyProvider.cs @@ -0,0 +1,37 @@ +using System.Security.Claims; +using AspNetCore.Authentication.ApiKey; + +namespace Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; + +public class ApiKey : IApiKey +{ + public string Key { get; init; } + + public string OwnerName { get; init; } + + public IReadOnlyCollection Claims { get; init; } +} + +public class ApiKeyProvider : IApiKeyProvider +{ + private readonly ILogger _logger; + + public ApiKeyProvider(ILogger logger) + { + _logger = logger; + } + + public async Task ProvideAsync(string key) + { + var result = new ApiKey + { + Key = "9527", + OwnerName = "yao", + Claims = new List() + { + new(ClaimTypes.Name, "yao") + } + }; + return result; + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs index ca76b952..57f7ed9f 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs @@ -28,8 +28,8 @@ public static AuthenticationBuilder AddBasicAuthentication(this I var scheme = BasicAuthenticationDefaults.AuthenticationScheme; return services.AddAuthentication(o => { - o.DefaultAuthenticateScheme = scheme; - o.DefaultChallengeScheme = scheme; + // o.DefaultAuthenticateScheme = scheme; + // o.DefaultChallengeScheme = scheme; }) .AddBasic(scheme, scheme, configureOptions); } From e84040b0a34fb45f3498ef3b103805a44a6d3d65 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 4 Oct 2022 10:17:31 +0800 Subject: [PATCH 280/424] feat: add ddb sample --- AWS/Lab.AwsDDB/Lab.AwsDDB.sln | 21 + AWS/Lab.AwsDDB/docker-compose.yml | 17 + .../Lab.AwsDDB.Test/Lab.AwsDDB.Test.csproj | 19 + .../test/Lab.AwsDDB.Test/UnitTest1.cs | 524 ++++++++++++++++++ AWS/Lab.AwsDDB/test/Lab.AwsDDB.Test/Usings.cs | 1 + 5 files changed, 582 insertions(+) create mode 100644 AWS/Lab.AwsDDB/Lab.AwsDDB.sln create mode 100644 AWS/Lab.AwsDDB/docker-compose.yml create mode 100644 AWS/Lab.AwsDDB/test/Lab.AwsDDB.Test/Lab.AwsDDB.Test.csproj create mode 100644 AWS/Lab.AwsDDB/test/Lab.AwsDDB.Test/UnitTest1.cs create mode 100644 AWS/Lab.AwsDDB/test/Lab.AwsDDB.Test/Usings.cs diff --git a/AWS/Lab.AwsDDB/Lab.AwsDDB.sln b/AWS/Lab.AwsDDB/Lab.AwsDDB.sln new file mode 100644 index 00000000..8c7ba129 --- /dev/null +++ b/AWS/Lab.AwsDDB/Lab.AwsDDB.sln @@ -0,0 +1,21 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AwsDDB.Test", "test\Lab.AwsDDB.Test\Lab.AwsDDB.Test.csproj", "{5B2F9C1D-597C-4890-9806-42B42D42F15C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{23EBD672-F330-4AEB-9F14-24C0E562F670}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5B2F9C1D-597C-4890-9806-42B42D42F15C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B2F9C1D-597C-4890-9806-42B42D42F15C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B2F9C1D-597C-4890-9806-42B42D42F15C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B2F9C1D-597C-4890-9806-42B42D42F15C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/AWS/Lab.AwsDDB/docker-compose.yml b/AWS/Lab.AwsDDB/docker-compose.yml new file mode 100644 index 00000000..4dd4623e --- /dev/null +++ b/AWS/Lab.AwsDDB/docker-compose.yml @@ -0,0 +1,17 @@ +version: "3.8" + +services: + ddb: + image: amazon/dynamodb-local + command: [ "-jar", "DynamoDBLocal.jar", "-inMemory", "-sharedDb" ] + ports: + - 8000:8000 + + ddb-admin: + image: aaronshaf/dynamodb-admin + environment: + - DYNAMO_ENDPOINT=http://ddb:8000 + ports: + - 8005:8001 + depends_on: + - ddb \ No newline at end of file diff --git a/AWS/Lab.AwsDDB/test/Lab.AwsDDB.Test/Lab.AwsDDB.Test.csproj b/AWS/Lab.AwsDDB/test/Lab.AwsDDB.Test/Lab.AwsDDB.Test.csproj new file mode 100644 index 00000000..24fa062f --- /dev/null +++ b/AWS/Lab.AwsDDB/test/Lab.AwsDDB.Test/Lab.AwsDDB.Test.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + diff --git a/AWS/Lab.AwsDDB/test/Lab.AwsDDB.Test/UnitTest1.cs b/AWS/Lab.AwsDDB/test/Lab.AwsDDB.Test/UnitTest1.cs new file mode 100644 index 00000000..cddd0eb9 --- /dev/null +++ b/AWS/Lab.AwsDDB/test/Lab.AwsDDB.Test/UnitTest1.cs @@ -0,0 +1,524 @@ +using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2.DocumentModel; +using Amazon.DynamoDBv2.Model; + +namespace Lab.AwsDDB.Test; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestMethod1() + { + var clientConfig = new AmazonDynamoDBConfig(); + clientConfig.ServiceURL = "http://localhost:8000"; + var client = new AmazonDynamoDBClient(clientConfig); + // CreateTableProductCatalog(client); + LoadSampleProducts(client); + } + + // static void Main(string[] args) + // { + // try + // { + // //DeleteAllTables(client); + // DeleteTable("ProductCatalog"); + // DeleteTable("Forum"); + // DeleteTable("Thread"); + // DeleteTable("Reply"); + // + // // Create tables (using the AWS SDK for .NET low-level API). + // CreateTableProductCatalog(); + // CreateTableForum(); + // CreateTableThread(); // ForumTitle, Subject */ + // CreateTableReply(); + // + // // Load data (using the .NET SDK document API) + // LoadSampleProducts(); + // LoadSampleForums(); + // LoadSampleThreads(); + // LoadSampleReplies(); + // Console.WriteLine("Sample complete!"); + // Console.WriteLine("Press ENTER to continue"); + // Console.ReadLine(); + // } + // catch (AmazonServiceException e) { Console.WriteLine(e.Message); } + // catch (Exception e) { Console.WriteLine(e.Message); } + // } + + private static async Task DeleteTable(AmazonDynamoDBClient client, string tableName) + { + try + { + var deleteTableResponse = await client.DeleteTableAsync(new DeleteTableRequest() + { + TableName = tableName + }); + WaitTillTableDeleted(client, tableName, deleteTableResponse); + } + catch (ResourceNotFoundException) + { + // There is no such table. + } + } + + private static async Task CreateTableProductCatalog(AmazonDynamoDBClient client) + { + string tableName = "ProductCatalog"; + + var response = await client.CreateTableAsync(new CreateTableRequest + { + TableName = tableName, + AttributeDefinitions = new List() + { + new() + { + AttributeName = "Id", + AttributeType = "N" + } + }, + KeySchema = new List() + { + new() + { + AttributeName = "Id", + KeyType = "HASH" + } + }, + ProvisionedThroughput = new ProvisionedThroughput + { + ReadCapacityUnits = 10, + WriteCapacityUnits = 5 + } + }); + + WaitTillTableCreated(client, tableName, response); + } + + private static async Task CreateTableForum(AmazonDynamoDBClient client) + { + string tableName = "Forum"; + + var response = await client.CreateTableAsync(new CreateTableRequest + { + TableName = tableName, + AttributeDefinitions = new List() + { + new AttributeDefinition + { + AttributeName = "Name", + AttributeType = "S" + } + }, + KeySchema = new List() + { + new KeySchemaElement + { + AttributeName = "Name", // forum Title + KeyType = "HASH" + } + }, + ProvisionedThroughput = new ProvisionedThroughput + { + ReadCapacityUnits = 10, + WriteCapacityUnits = 5 + } + }); + + WaitTillTableCreated(client, tableName, response); + } + + private static async Task CreateTableThread(AmazonDynamoDBClient client) + { + string tableName = "Thread"; + + var response = await client.CreateTableAsync(new CreateTableRequest + { + TableName = tableName, + AttributeDefinitions = new List() + { + new AttributeDefinition + { + AttributeName = "ForumName", // Hash attribute + AttributeType = "S" + }, + new AttributeDefinition + { + AttributeName = "Subject", + AttributeType = "S" + } + }, + KeySchema = new List() + { + new KeySchemaElement + { + AttributeName = "ForumName", // Hash attribute + KeyType = "HASH" + }, + new KeySchemaElement + { + AttributeName = "Subject", // Range attribute + KeyType = "RANGE" + } + }, + ProvisionedThroughput = new ProvisionedThroughput + { + ReadCapacityUnits = 10, + WriteCapacityUnits = 5 + } + }); + + WaitTillTableCreated(client, tableName, response); + } + + private static async Task CreateTableReply(AmazonDynamoDBClient client) + { + string tableName = "Reply"; + var response = await client.CreateTableAsync(new CreateTableRequest + { + TableName = tableName, + AttributeDefinitions = new List() + { + new AttributeDefinition + { + AttributeName = "Id", + AttributeType = "S" + }, + new AttributeDefinition + { + AttributeName = "ReplyDateTime", + AttributeType = "S" + }, + new AttributeDefinition + { + AttributeName = "PostedBy", + AttributeType = "S" + } + }, + KeySchema = new List() + { + new KeySchemaElement() + { + AttributeName = "Id", + KeyType = "HASH" + }, + new KeySchemaElement() + { + AttributeName = "ReplyDateTime", + KeyType = "RANGE" + } + }, + LocalSecondaryIndexes = new List() + { + new LocalSecondaryIndex() + { + IndexName = "PostedBy_index", + + KeySchema = new List() + { + new KeySchemaElement() + { + AttributeName = "Id", KeyType = "HASH" + }, + new KeySchemaElement() + { + AttributeName = "PostedBy", KeyType = "RANGE" + } + }, + Projection = new Projection() + { + ProjectionType = ProjectionType.KEYS_ONLY + } + } + }, + ProvisionedThroughput = new ProvisionedThroughput + { + ReadCapacityUnits = 10, + WriteCapacityUnits = 5 + } + }); + + WaitTillTableCreated(client, tableName, response); + } + + private static async Task WaitTillTableCreated(AmazonDynamoDBClient client, + string tableName, + CreateTableResponse response) + { + var tableDescription = response.TableDescription; + + string status = tableDescription.TableStatus; + + Console.WriteLine(tableName + " - " + status); + + // Let us wait until table is created. Call DescribeTable. + while (status != "ACTIVE") + { + System.Threading.Thread.Sleep(5000); // Wait 5 seconds. + try + { + var res = await client.DescribeTableAsync(new DescribeTableRequest + { + TableName = tableName + }); + Console.WriteLine("Table name: {0}, status: {1}", res.Table.TableName, + res.Table.TableStatus); + status = res.Table.TableStatus; + } + + // Try-catch to handle potential eventual-consistency issue. + catch (ResourceNotFoundException) + { + } + } + } + + private static async Task WaitTillTableDeleted(AmazonDynamoDBClient client, string tableName, + DeleteTableResponse response) + { + var tableDescription = response.TableDescription; + + string status = tableDescription.TableStatus; + + Console.WriteLine(tableName + " - " + status); + + // Let us wait until table is created. Call DescribeTable + try + { + while (status == "DELETING") + { + System.Threading.Thread.Sleep(5000); // wait 5 seconds + + var res = await client.DescribeTableAsync(new DescribeTableRequest + { + TableName = tableName + }); + Console.WriteLine("Table name: {0}, status: {1}", res.Table.TableName, + res.Table.TableStatus); + status = res.Table.TableStatus; + } + } + catch (ResourceNotFoundException) + { + // Table deleted. + } + } + + private static async Task LoadSampleProducts(AmazonDynamoDBClient client) + { + Table productCatalogTable = Table.LoadTable(client, "ProductCatalog"); + + // ********** Add Books ********************* + var book1 = new Document(); + book1["Id"] = 101; + book1["Title"] = "Book 101 Title"; + book1["ISBN"] = "111-1111111111"; + book1["Authors"] = new List { "Author 1" }; + book1["Price"] = -2; // *** Intentional value. Later used to illustrate scan. + book1["Dimensions"] = "8.5 x 11.0 x 0.5"; + book1["PageCount"] = 500; + book1["InPublication"] = true; + book1["ProductCategory"] = "Book"; + await productCatalogTable.PutItemAsync(book1); + + var book2 = new Document(); + + book2["Id"] = 102; + book2["Title"] = "Book 102 Title"; + book2["ISBN"] = "222-2222222222"; + book2["Authors"] = new List { "Author 1", "Author 2" }; + ; + book2["Price"] = 20; + book2["Dimensions"] = "8.5 x 11.0 x 0.8"; + book2["PageCount"] = 600; + book2["InPublication"] = true; + book2["ProductCategory"] = "Book"; + await productCatalogTable.PutItemAsync(book2); + + var book3 = new Document(); + book3["Id"] = 103; + book3["Title"] = "Book 103 Title"; + book3["ISBN"] = "333-3333333333"; + book3["Authors"] = new List { "Author 1", "Author2", "Author 3" }; + ; + book3["Price"] = 2000; + book3["Dimensions"] = "8.5 x 11.0 x 1.5"; + book3["PageCount"] = 700; + book3["InPublication"] = false; + book3["ProductCategory"] = "Book"; + await productCatalogTable.PutItemAsync(book3); + + // ************ Add bikes. ******************* + var bicycle1 = new Document(); + bicycle1["Id"] = 201; + bicycle1["Title"] = "18-Bike 201"; // size, followed by some title. + bicycle1["Description"] = "201 description"; + bicycle1["BicycleType"] = "Road"; + bicycle1["Brand"] = "Brand-Company A"; // Trek, Specialized. + bicycle1["Price"] = 100; + bicycle1["Color"] = new List { "Red", "Black" }; + bicycle1["ProductCategory"] = "Bike"; + await productCatalogTable.PutItemAsync(bicycle1); + + var bicycle2 = new Document(); + bicycle2["Id"] = 202; + bicycle2["Title"] = "21-Bike 202Brand-Company A"; + bicycle2["Description"] = "202 description"; + bicycle2["BicycleType"] = "Road"; + bicycle2["Brand"] = ""; + bicycle2["Price"] = 200; + bicycle2["Color"] = new List { "Green", "Black" }; + bicycle2["ProductCategory"] = "Bicycle"; + await productCatalogTable.PutItemAsync(bicycle2); + + var bicycle3 = new Document(); + bicycle3["Id"] = 203; + bicycle3["Title"] = "19-Bike 203"; + bicycle3["Description"] = "203 description"; + bicycle3["BicycleType"] = "Road"; + bicycle3["Brand"] = "Brand-Company B"; + bicycle3["Price"] = 300; + bicycle3["Color"] = new List { "Red", "Green", "Black" }; + bicycle3["ProductCategory"] = "Bike"; + await productCatalogTable.PutItemAsync(bicycle3); + + var bicycle4 = new Document(); + bicycle4["Id"] = 204; + bicycle4["Title"] = "18-Bike 204"; + bicycle4["Description"] = "204 description"; + bicycle4["BicycleType"] = "Mountain"; + bicycle4["Brand"] = "Brand-Company B"; + bicycle4["Price"] = 400; + bicycle4["Color"] = new List { "Red" }; + bicycle4["ProductCategory"] = "Bike"; + await productCatalogTable.PutItemAsync(bicycle4); + + var bicycle5 = new Document(); + bicycle5["Id"] = 205; + bicycle5["Title"] = "20-Title 205"; + bicycle4["Description"] = "205 description"; + bicycle5["BicycleType"] = "Hybrid"; + bicycle5["Brand"] = "Brand-Company C"; + bicycle5["Price"] = 500; + bicycle5["Color"] = new List { "Red", "Black" }; + bicycle5["ProductCategory"] = "Bike"; + await productCatalogTable.PutItemAsync(bicycle5); + } + + private static async Task LoadSampleForums(AmazonDynamoDBClient client) + { + Table forumTable = Table.LoadTable(client, "Forum"); + + var forum1 = new Document(); + forum1["Name"] = "Amazon DynamoDB"; // PK + forum1["Category"] = "Amazon Web Services"; + forum1["Threads"] = 2; + forum1["Messages"] = 4; + forum1["Views"] = 1000; + + await forumTable.PutItemAsync(forum1); + + var forum2 = new Document(); + forum2["Name"] = "Amazon S3"; // PK + forum2["Category"] = "Amazon Web Services"; + forum2["Threads"] = 1; + + await forumTable.PutItemAsync(forum2); + } + + private static async Task LoadSampleThreads(AmazonDynamoDBClient client) + { + Table threadTable = Table.LoadTable(client, "Thread"); + + // Thread 1. + var thread1 = new Document(); + thread1["ForumName"] = "Amazon DynamoDB"; // Hash attribute. + thread1["Subject"] = "DynamoDB Thread 1"; // Range attribute. + thread1["Message"] = "DynamoDB thread 1 message text"; + thread1["LastPostedBy"] = "User A"; + thread1["LastPostedDateTime"] = DateTime.UtcNow.Subtract(new TimeSpan(14, 0, 0, 0)); + thread1["Views"] = 0; + thread1["Replies"] = 0; + thread1["Answered"] = false; + thread1["Tags"] = new List { "index", "primarykey", "table" }; + + await threadTable.PutItemAsync(thread1); + + // Thread 2. + var thread2 = new Document(); + thread2["ForumName"] = "Amazon DynamoDB"; // Hash attribute. + thread2["Subject"] = "DynamoDB Thread 2"; // Range attribute. + thread2["Message"] = "DynamoDB thread 2 message text"; + thread2["LastPostedBy"] = "User A"; + thread2["LastPostedDateTime"] = DateTime.UtcNow.Subtract(new TimeSpan(21, 0, 0, 0)); + thread2["Views"] = 0; + thread2["Replies"] = 0; + thread2["Answered"] = false; + thread2["Tags"] = new List { "index", "primarykey", "rangekey" }; + + await threadTable.PutItemAsync(thread2); + + // Thread 3. + var thread3 = new Document(); + thread3["ForumName"] = "Amazon S3"; // Hash attribute. + thread3["Subject"] = "S3 Thread 1"; // Range attribute. + thread3["Message"] = "S3 thread 3 message text"; + thread3["LastPostedBy"] = "User A"; + thread3["LastPostedDateTime"] = DateTime.UtcNow.Subtract(new TimeSpan(7, 0, 0, 0)); + thread3["Views"] = 0; + thread3["Replies"] = 0; + thread3["Answered"] = false; + thread3["Tags"] = new List { "largeobjects", "multipart upload" }; + await threadTable.PutItemAsync(thread3); + } + + private static async Task LoadSampleReplies(AmazonDynamoDBClient client) + { + Table replyTable = Table.LoadTable(client, "Reply"); + + // Reply 1 - thread 1. + var thread1Reply1 = new Document(); + thread1Reply1["Id"] = "Amazon DynamoDB#DynamoDB Thread 1"; // Hash attribute. + thread1Reply1["ReplyDateTime"] = DateTime.UtcNow.Subtract(new TimeSpan(21, 0, 0, 0)); // Range attribute. + thread1Reply1["Message"] = "DynamoDB Thread 1 Reply 1 text"; + thread1Reply1["PostedBy"] = "User A"; + + await replyTable.PutItemAsync(thread1Reply1); + + // Reply 2 - thread 1. + var thread1reply2 = new Document(); + thread1reply2["Id"] = "Amazon DynamoDB#DynamoDB Thread 1"; // Hash attribute. + thread1reply2["ReplyDateTime"] = DateTime.UtcNow.Subtract(new TimeSpan(14, 0, 0, 0)); // Range attribute. + thread1reply2["Message"] = "DynamoDB Thread 1 Reply 2 text"; + thread1reply2["PostedBy"] = "User B"; + + await replyTable.PutItemAsync(thread1reply2); + + // Reply 3 - thread 1. + var thread1Reply3 = new Document(); + thread1Reply3["Id"] = "Amazon DynamoDB#DynamoDB Thread 1"; // Hash attribute. + thread1Reply3["ReplyDateTime"] = DateTime.UtcNow.Subtract(new TimeSpan(7, 0, 0, 0)); // Range attribute. + thread1Reply3["Message"] = "DynamoDB Thread 1 Reply 3 text"; + thread1Reply3["PostedBy"] = "User B"; + + await replyTable.PutItemAsync(thread1Reply3); + + // Reply 1 - thread 2. + var thread2Reply1 = new Document(); + thread2Reply1["Id"] = "Amazon DynamoDB#DynamoDB Thread 2"; // Hash attribute. + thread2Reply1["ReplyDateTime"] = DateTime.UtcNow.Subtract(new TimeSpan(7, 0, 0, 0)); // Range attribute. + thread2Reply1["Message"] = "DynamoDB Thread 2 Reply 1 text"; + thread2Reply1["PostedBy"] = "User A"; + + await replyTable.PutItemAsync(thread2Reply1); + + // Reply 2 - thread 2. + var thread2Reply2 = new Document(); + thread2Reply2["Id"] = "Amazon DynamoDB#DynamoDB Thread 2"; // Hash attribute. + thread2Reply2["ReplyDateTime"] = DateTime.UtcNow.Subtract(new TimeSpan(1, 0, 0, 0)); // Range attribute. + thread2Reply2["Message"] = "DynamoDB Thread 2 Reply 2 text"; + thread2Reply2["PostedBy"] = "User A"; + + await replyTable.PutItemAsync(thread2Reply2); + } +} \ No newline at end of file diff --git a/AWS/Lab.AwsDDB/test/Lab.AwsDDB.Test/Usings.cs b/AWS/Lab.AwsDDB/test/Lab.AwsDDB.Test/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/AWS/Lab.AwsDDB/test/Lab.AwsDDB.Test/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file From 1db05e244b9585c16c60d914f6527d6d45287dae Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 8 Oct 2022 15:34:13 +0800 Subject: [PATCH 281/424] add IdempotencyKey project --- WebAPI/Idempotent/.editorconfig | 464 +++++++++++++++++ WebAPI/Idempotent/.gitignore | 350 +++++++++++++ WebAPI/Idempotent/Lab.Idempotent.sln | 23 + WebAPI/Idempotent/Taskfile.yml | 466 ++++++++++++++++++ .../src/Lab.Idempotent.WebApi/.dockerignore | 25 + .../Controllers/WeatherForecastController.cs | 44 ++ .../src/Lab.Idempotent.WebApi/Dockerfile | 20 + .../IdempotentAttributeFilter.cs | 87 ++++ .../Lab.Idempotent.WebApi.csproj | 14 + .../src/Lab.Idempotent.WebApi/Program.cs | 27 + .../Lab.Idempotent.WebApi/WeatherForecast.cs | 12 + .../appsettings.Development.json | 8 + .../Lab.Idempotent.WebApi/appsettings.json | 9 + 13 files changed, 1549 insertions(+) create mode 100644 WebAPI/Idempotent/.editorconfig create mode 100644 WebAPI/Idempotent/.gitignore create mode 100644 WebAPI/Idempotent/Lab.Idempotent.sln create mode 100644 WebAPI/Idempotent/Taskfile.yml create mode 100644 WebAPI/Idempotent/src/Lab.Idempotent.WebApi/.dockerignore create mode 100644 WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Controllers/WeatherForecastController.cs create mode 100644 WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Dockerfile create mode 100644 WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs create mode 100644 WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Lab.Idempotent.WebApi.csproj create mode 100644 WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Program.cs create mode 100644 WebAPI/Idempotent/src/Lab.Idempotent.WebApi/WeatherForecast.cs create mode 100644 WebAPI/Idempotent/src/Lab.Idempotent.WebApi/appsettings.Development.json create mode 100644 WebAPI/Idempotent/src/Lab.Idempotent.WebApi/appsettings.json diff --git a/WebAPI/Idempotent/.editorconfig b/WebAPI/Idempotent/.editorconfig new file mode 100644 index 00000000..9cb23b81 --- /dev/null +++ b/WebAPI/Idempotent/.editorconfig @@ -0,0 +1,464 @@ +root = true +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file + +# Don't use tabs for indentation. +[*] +indent_style = space +end_of_line = crlf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false +indent_size = 4 + +# Microsoft .NET properties +csharp_new_line_before_members_in_object_initializers = false +csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:suggestion +csharp_style_var_elsewhere = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +dotnet_naming_rule.constants_rule.import_to_resharper = as_predefined +dotnet_naming_rule.constants_rule.severity = warning +dotnet_naming_rule.constants_rule.style = upper_camel_case_style +dotnet_naming_rule.constants_rule.symbols = constants_symbols +dotnet_naming_rule.private_constants_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_constants_rule.severity = warning +dotnet_naming_rule.private_constants_rule.style = lower_camel_case_style +dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols +dotnet_naming_rule.private_static_fields_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_static_fields_rule.severity = warning +dotnet_naming_rule.private_static_fields_rule.style = s_lower_camel_case_style +dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols +dotnet_naming_rule.private_static_readonly_rule.import_to_resharper = as_predefined +dotnet_naming_rule.private_static_readonly_rule.severity = warning +dotnet_naming_rule.private_static_readonly_rule.style = s_lower_camel_case_style +dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols +dotnet_naming_rule.public_fields_rule.import_to_resharper = as_predefined +dotnet_naming_rule.public_fields_rule.resharper_style = AaBb, _ + aaBb +dotnet_naming_rule.public_fields_rule.severity = warning +dotnet_naming_rule.public_fields_rule.style = upper_camel_case_style +dotnet_naming_rule.public_fields_rule.symbols = public_fields_symbols +dotnet_naming_rule.static_readonly_rule.import_to_resharper = as_predefined +dotnet_naming_rule.static_readonly_rule.severity = warning +dotnet_naming_rule.static_readonly_rule.style = all_upper_style +dotnet_naming_rule.static_readonly_rule.symbols = static_readonly_symbols +dotnet_naming_rule.type_parameters_rule.import_to_resharper = as_predefined +dotnet_naming_rule.type_parameters_rule.severity = warning +dotnet_naming_rule.type_parameters_rule.style = upper_camel_case_style +dotnet_naming_rule.type_parameters_rule.symbols = type_parameters_symbols +dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper = True +dotnet_naming_rule.unity_serialized_field_rule.resharper_description = Unity serialized field +dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef +dotnet_naming_rule.unity_serialized_field_rule.severity = warning +dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style +dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols +dotnet_naming_style.all_upper_style.capitalization = all_upper +dotnet_naming_style.all_upper_style.word_separator = _ +dotnet_naming_style.lower_camel_case_style.capitalization = camel_case +dotnet_naming_style.s_lower_camel_case_style.capitalization = camel_case +dotnet_naming_style.s_lower_camel_case_style.required_prefix = s_ +dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case +dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected +dotnet_naming_symbols.constants_symbols.applicable_kinds = field +dotnet_naming_symbols.constants_symbols.required_modifiers = const +dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field +dotnet_naming_symbols.private_constants_symbols.required_modifiers = const +dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static +dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly +dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected +dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field +dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected +dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field +dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static, readonly +dotnet_naming_symbols.type_parameters_symbols.applicable_accessibilities = * +dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = +dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field +dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_qualification_for_event = true:suggestion +dotnet_style_qualification_for_field = true:suggestion +dotnet_style_qualification_for_method = true:suggestion +dotnet_style_qualification_for_property = true:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +# ReSharper properties +resharper_align_linq_query = true +resharper_align_multiline_extends_list = true +resharper_align_multiline_for_stmt = true +resharper_align_multiline_statement_conditions = false +resharper_align_multiple_declaration = true +resharper_align_multline_type_parameter_constrains = true +resharper_align_multline_type_parameter_list = true +resharper_apply_on_completion = true +resharper_autodetect_indent_settings = true +resharper_blank_lines_after_control_transfer_statements = 1 +resharper_blank_lines_around_single_line_auto_property = 1 +resharper_blank_lines_around_single_line_property = 1 +resharper_blank_lines_before_single_line_comment = 1 +resharper_braces_for_for = required +resharper_braces_for_foreach = required +resharper_braces_for_ifelse = required +resharper_braces_for_while = required +resharper_csharp_alignment_tab_fill_style = optimal_fill +resharper_csharp_blank_lines_around_single_line_invocable = 1 +resharper_csharp_int_align_comments = true +resharper_csharp_keep_blank_lines_in_code = 1 +resharper_csharp_keep_blank_lines_in_declarations = 1 +resharper_csharp_outdent_commas = true +resharper_csharp_stick_comment = false +resharper_enforce_line_ending_style = true +resharper_indent_braces_inside_statement_conditions = false +resharper_indent_pars = outside +resharper_int_align_methods = true +resharper_int_align_nested_ternary = true +resharper_parentheses_redundancy_style = remove +resharper_place_accessorholder_attribute_on_same_line = false +resharper_show_autodetect_configure_formatting_tip = false +resharper_use_continuous_indent_inside_initializer_braces = false +resharper_use_continuous_indent_inside_parens = false +resharper_use_indent_from_vs = false +resharper_wrap_array_initializer_style = chop_if_long + +# ReSharper inspection severities +resharper_arrange_redundant_parentheses_highlighting = suggestion +resharper_arrange_this_qualifier_highlighting = hint +resharper_arrange_type_member_modifiers_highlighting = hint +resharper_arrange_type_modifiers_highlighting = hint +resharper_built_in_type_reference_style_for_member_access_highlighting = hint +resharper_built_in_type_reference_style_highlighting = hint +resharper_suggest_var_or_type_built_in_types_highlighting = hint +resharper_suggest_var_or_type_elsewhere_highlighting = hint +resharper_suggest_var_or_type_simple_types_highlighting = hint +resharper_web_config_module_not_resolved_highlighting = warning +resharper_web_config_type_not_resolved_highlighting = warning +resharper_web_config_wrong_module_highlighting = warning +# (Please don't specify an indent_size here; that has too many unintended consequences.) + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8 + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 + +# Powershell files +[*.ps1] +indent_size = 2 + +# Shell script files +[*.sh] +indent_size = 2 + +# Template file +[*.gpl] +indent_size = 2 + +# Dotnet code style settings: +[*.{cs,vb}] + +# IDE0055: Fix formatting +dotnet_diagnostic.ide0055.severity = warning + +# Sort using and Import directives with System.* appearing first +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false + +# Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = true +dotnet_style_qualification_for_property = true +dotnet_style_qualification_for_method = true +dotnet_style_qualification_for_event = true + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Suggest more modern language features when available +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion + +# Whitespace options +dotnet_style_allow_multiple_blank_lines_experimental = false + +# Non-private static fields are PascalCase +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style + +dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected +dotnet_naming_symbols.non_private_static_fields.required_modifiers = static + +dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case + +# Non-private readonly fields are PascalCase +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style + +dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected +dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly + +dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case + +# Constants are PascalCase +dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants +dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style + +dotnet_naming_symbols.constants.applicable_kinds = field, local +dotnet_naming_symbols.constants.required_modifiers = const + +dotnet_naming_style.constant_style.capitalization = pascal_case + +# Static fields are camelCase and start with s_ +dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields +dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style + +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static + +dotnet_naming_style.static_field_style.capitalization = camel_case +dotnet_naming_style.static_field_style.required_prefix = s_ + +# Instance fields are camelCase and start with _ +dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields +dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style + +dotnet_naming_symbols.instance_fields.applicable_kinds = field + +dotnet_naming_style.instance_field_style.capitalization = camel_case +dotnet_naming_style.instance_field_style.required_prefix = _ + +# Locals and parameters are camelCase +dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion +dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters +dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style + +dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +# Local functions are PascalCase +dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions +dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style + +dotnet_naming_symbols.local_functions.applicable_kinds = local_function + +dotnet_naming_style.local_function_style.capitalization = pascal_case + +# By default, name items with PascalCase +dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members +dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style + +dotnet_naming_symbols.all_members.applicable_kinds = * + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# error RS2008: Enable analyzer release tracking for the analyzer project containing rule '{0}' +dotnet_diagnostic.rs2008.severity = none + +# IDE0073: File header +# dotnet_diagnostic.IDE0073.severity = warning +# file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.\nSee the LICENSE file in the project root for more information. + +# IDE0035: Remove unreachable code +dotnet_diagnostic.ide0035.severity = warning + +# IDE0036: Order modifiers +dotnet_diagnostic.ide0036.severity = warning + +# IDE0043: Format string contains invalid placeholder +dotnet_diagnostic.ide0043.severity = warning + +# IDE0044: Make field readonly +dotnet_diagnostic.ide0044.severity = warning + +# RS0016: Only enable if API files are present +dotnet_public_api_analyzer.require_api_files = true + +# CSharp code style settings: +[*.cs] +# Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +# csharp_new_line_before_members_in_object_initializers = true +# csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Whitespace options +csharp_style_allow_embedded_statements_on_same_line_experimental = false +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false + +# Prefer "var" everywhere +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion + +# Prefer method-like constructs to have a block body +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none + +# Prefer property-like constructs to have an expression-body +csharp_style_expression_bodied_properties = true:none +csharp_style_expression_bodied_indexers = true:none +csharp_style_expression_bodied_accessors = true:none + +# Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Blocks are allowed +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +# Currently only enabled for C# due to crash in VB analyzer. VB can be enabled once +# https://github.com/dotnet/roslyn/pull/54259 has been published. +dotnet_style_allow_statement_immediately_after_block_experimental = false + +[src/CodeStyle/**.{cs,vb}] +# warning RS0005: Do not use generic CodeAction.Create to create CodeAction +dotnet_diagnostic.rs0005.severity = none + +[src/{Analyzers,CodeStyle,Features,Workspaces,EditorFeatures,VisualStudio}/**/*.{cs,vb}] + +# IDE0011: Add braces +csharp_prefer_braces = when_multiline:warning +# NOTE: We need the below severity entry for Add Braces due to https://github.com/dotnet/roslyn/issues/44201 +dotnet_diagnostic.ide0011.severity = warning + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.ide0040.severity = warning + +# CONSIDER: Are IDE0051 and IDE0052 too noisy to be warnings for IDE editing scenarios? Should they be made build-only warnings? +# IDE0051: Remove unused private member +dotnet_diagnostic.ide0051.severity = warning + +# IDE0052: Remove unread private member +dotnet_diagnostic.ide0052.severity = warning + +# IDE0059: Unnecessary assignment to a value +dotnet_diagnostic.ide0059.severity = warning + +# IDE0060: Remove unused parameter +dotnet_diagnostic.ide0060.severity = warning + +# CA1012: Abstract types should not have public constructors +dotnet_diagnostic.ca1012.severity = warning + +# CA1822: Make member static +dotnet_diagnostic.ca1822.severity = warning + +# Prefer "var" everywhere +dotnet_diagnostic.ide0007.severity = warning +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:warning + +# dotnet_style_allow_multiple_blank_lines_experimental +dotnet_diagnostic.ide2000.severity = warning + +# csharp_style_allow_embedded_statements_on_same_line_experimental +dotnet_diagnostic.ide2001.severity = warning + +# csharp_style_allow_blank_lines_between_consecutive_braces_experimental +dotnet_diagnostic.ide2002.severity = warning + +# dotnet_style_allow_statement_immediately_after_block_experimental +dotnet_diagnostic.ide2003.severity = warning + +# csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental +dotnet_diagnostic.ide2004.severity = warning + +[src/{VisualStudio}/**/*.{cs,vb}] +# CA1822: Make member static +# There is a risk of accidentally breaking an internal API that partners rely on though IVT. +dotnet_code_quality.ca1822.api_surface = private + +[{*.har,*.inputactions,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,jest.config}] +indent_style = space +indent_size = 2 + +[{*.yaml,*.yml}] +indent_style = space +indent_size = 2 + +[*.csv] +indent_style = tab +tab_width = 1 + +[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cs,cshtml,cu,cuh,cxx,dtd,feature,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}] +indent_style = space +indent_size = 4 +tab_width = 4 diff --git a/WebAPI/Idempotent/.gitignore b/WebAPI/Idempotent/.gitignore new file mode 100644 index 00000000..81c554f7 --- /dev/null +++ b/WebAPI/Idempotent/.gitignore @@ -0,0 +1,350 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +#vistual studio extension +.localhistory + +# stress test output +stress/output + +# specflow feature.cs +**/*.feature.cs + +# secrets +secrets + +.DS_Store +*.zip + +deployments + +# minio local s3 +minio \ No newline at end of file diff --git a/WebAPI/Idempotent/Lab.Idempotent.sln b/WebAPI/Idempotent/Lab.Idempotent.sln new file mode 100644 index 00000000..bb9d84a2 --- /dev/null +++ b/WebAPI/Idempotent/Lab.Idempotent.sln @@ -0,0 +1,23 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{97B4E8A6-F946-4D97-87E8-6ED45319A07F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{FABC7D61-6407-48E3-BC03-E1618276A877}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Idempotent.WebApi", "src\Lab.Idempotent.WebApi\Lab.Idempotent.WebApi.csproj", "{EA97918D-52E7-4DD2-A2F5-5B5A76FED4FF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {EA97918D-52E7-4DD2-A2F5-5B5A76FED4FF} = {97B4E8A6-F946-4D97-87E8-6ED45319A07F} + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EA97918D-52E7-4DD2-A2F5-5B5A76FED4FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA97918D-52E7-4DD2-A2F5-5B5A76FED4FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA97918D-52E7-4DD2-A2F5-5B5A76FED4FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA97918D-52E7-4DD2-A2F5-5B5A76FED4FF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/WebAPI/Idempotent/Taskfile.yml b/WebAPI/Idempotent/Taskfile.yml new file mode 100644 index 00000000..b8174c4b --- /dev/null +++ b/WebAPI/Idempotent/Taskfile.yml @@ -0,0 +1,466 @@ +# Taskfile.yml + +version: "3" + +dotenv: [ "secrets/secrets.env" ] + +tasks: + ## Develop --------------------------------------------------- + dev-init: + desc: Init development environment + cmds: + - task: redis-start + - task: db-start + - task: db-update + - task: db-mssql-start + - task: s3-minio-start + - task: ddb-start + - task: ddb-init + - task: mock-membership + + + dev-stop: + desc: Stop development environment + cmds: + - docker-compose down + + api-dev: + desc: WebApi Development + dir: "src/NineYi.Msa.MemberService.WebAPI" + cmds: + - dotnet watch run --local {{.CLI_ARGS}} | jq -rR '. as $line | try (fromjson | "\(._ts) | \(._lvl) | \(._hid)-\(._ver) | \(._srctx)", ._msg, ._props) catch $line' + + dm-dev: + desc: DataMigration Development. Usage => task dm-dev -- diff -s 2001-01-01 -e 2022-01-01 + dir: "src/NineYi.Msa.MemberService.DataMigration.ConsoleApp" + cmds: + - dotnet watch run {{.CLI_ARGS}} --local true | jq -rR '. as $line | try (fromjson | "\(._ts) | \(._lvl) | \(._hid)-\(._ver) | \(._srctx)", ._msg, ._props) catch $line' + + dm-run: + desc: DataMigration run. Usage => task dm-run -- diff -s 2001-01-01 -e 2022-01-01 + dir: "src/NineYi.Msa.MemberService.DataMigration.ConsoleApp" + cmds: + - dotnet run {{.CLI_ARGS}} --local true + + db-start: + desc: start PostgreSQL at local + cmds: + - docker-compose up -d --remove-orphans db + + db-update: + desc: apply db migration on local PostgreSQL + cmds: + - dotnet ef database update {{.CLI_ARGS}} --project src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext + + redis-start: + desc: start redis 5.X version + cmds: + - docker-compose up -d redis + + redis-admin-start: + desc: admin ui to manage redis + cmds: + - docker-compose up -d redis-admin + + db-otm-db-start: + dir: test/NineYi.Msa.MemberService.NMQ3.Worker.Infrastructure.Tests + cmds: + - docker run --privileged -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=pass@w0rd1~' -p 1433:1433 -d datagrip/mssql-server-linux + + db-otm-db-update: + dir: test/NineYi.Msa.MemberService.NMQ3.Worker.Infrastructure.Tests + cmds: + - dotnet build + - dotnet ef database update --context CrmDbContext --no-build + - dotnet ef database update --context WebStoreDbContext --no-build + + db-mssql-start: + desc: start MSSQL at local + cmds: + - docker-compose up -d db-sql + + db-mssql-update: + desc: db update on local MSSQL + dir: src/NineYi.Msa.MemberService.DataMigration.Infrastructure + cmds: + - dotnet build + - dotnet ef database update --context CrmDbContext --no-build + - dotnet ef database update --context WebStoreDbContext --no-build + + db-list-migrations: + desc: member-service list migrations + cmds: + - dotnet ef migrations list {{.CLI_ARGS}} -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext + + db-add-migration: + desc: member-service add migration + cmds: + - dotnet ef migrations add {{.CLI_ARGS}} -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext + + db-remove-migration: + desc: member-service remove migration + cmds: + - dotnet ef migrations remove -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext + + db-script: + desc: generate member-service db script + cmds: + - dotnet ef migrations script 0 -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext + + ddb-start: + desc: start dynamodb at local + cmds: + - docker-compose up -d ddb + + ddb-clear: + desc: clear ddb table + cmds: + - docker-compose restart ddb + - aws dynamodb create-table --endpoint-url http://localhost:8000 --cli-input-json file://./ddb.json + + ddb-init: + desc: init dynamodb table at local + cmds: + - aws dynamodb create-table --endpoint-url http://localhost:8000 --cli-input-json file://./ddb.json + + ddb-list: + desc: list dynamodb tables at local + cmds: + - aws dynamodb list-tables --endpoint-url http://localhost:8000 + + ddb-logs: + cmds: + - docker-compose logs -f ddb + + ddb-admin-start: + desc: start dynamodb admin + cmds: + - docker-compose up -d ddb-admin + + s3-minio-start: + desc: start s3-minio at local + cmds: + - docker-compose up -d s3-minio + + gen-k8s: + desc: generate k8s + cmds: + - dotnet run --project src/NineYi.Msa.K8S.Template.GeneratorApp -- -s $(pwd)/env -d $(pwd)/env/k8s -v 1.0.0.1 + + gen-configs-k8s: + desc: generate env config and k8s + cmds: + - task: gen-configs + - dotnet run --project src/NineYi.Msa.K8S.Template.GeneratorApp -- -s $(pwd)/env -d $(pwd)/env/k8s -v 1.0.0.1 + + gen-configs: + desc: Generate configs + dir: "env" + cmds: + - nu -c 'rm -f *.env' + - task: _gen-config + vars: { MARKET: "TW", ENV: "DEV" } + - task: _gen-config + vars: { MARKET: "TW", ENV: "QA" } + - task: _gen-config + vars: { MARKET: "TW", ENV: "STRESS" } + - task: _gen-config + vars: { MARKET: "PX", ENV: "QA" } + - task: _gen-config + vars: { MARKET: "HK", ENV: "QA" } + - task: _gen-config + vars: { MARKET: "MY", ENV: "QA" } + - task: _gen-config + vars: { MARKET: "TW", ENV: "PROD" } + - task: _gen-config + vars: { MARKET: "PX", ENV: "PROD" } + - task: _gen-config + vars: { MARKET: "HK", ENV: "PROD" } + - task: _gen-config + vars: { MARKET: "MY", ENV: "PROD" } + + _gen-config: + dir: "env" + cmds: + - echo "## This file was generated by task gen-configs" > WebApi_{{.MARKET}}_{{.ENV}}.env + - echo >> WebApi_{{.MARKET}}_{{.ENV}}.env + - opc -i market={{.MARKET}} -i env={{.ENV}} -m WebApi.rego -f env-file >> WebApi_{{.MARKET}}_{{.ENV}}.env + + login-am: + desc: login am with envs $AM_USER_NAME $AM_USER_PASSWD + cmds: + - docker login docker.build.91app.io -u $AM_USER_NAME -p $AM_USER_PASSWD + + build-image: + desc: usage => task build-image -- 0.1.2 + vars: + VER: + sh: echo "{{.CLI_ARGS}}-$(git rev-parse --short HEAD)" + cmds: + - docker build --build-arg VER={{.VER}} . -t docker.build.91app.io/member-service:{{.VER}} + + push-image: + desc: usage => task build-image -- 0.1.2 + vars: + VER: + sh: echo "{{.CLI_ARGS}}-$(git rev-parse --short HEAD)" + cmds: + - docker push docker.build.91app.io/member-service:{{.VER}} + + build-dm-image: + desc: usage => task build-dm-image -- 0.1.2 + vars: + VER: + sh: echo "{{.CLI_ARGS}}-$(git rev-parse --short HEAD)" + cmds: + - docker build --build-arg VER={{.VER}} . -t docker.build.91app.io/member-service-data-migration:{{.VER}} -f Dockerfile.DataMigration + + push-dm-image: + desc: usage => task build-dm-image -- 0.1.2 + vars: + VER: + sh: echo "{{.CLI_ARGS}}-$(git rev-parse --short HEAD)" + cmds: + - docker push docker.build.91app.io/member-service-data-migration:{{.VER}} + + build-and-push-image: + desc: usage => task build-and-push-image -- 0.1.2 + vars: + VER: + sh: echo "{{.CLI_ARGS}}-$(git rev-parse --short HEAD)" + cmds: + - docker build --build-arg VER={{.VER}} . -t docker.build.91app.io/member-service:{{.VER}} + - docker build --build-arg VER={{.VER}} . -t docker.build.91app.io/member-service-data-migration:{{.VER}} -f Dockerfile.DataMigration + - docker push docker.build.91app.io/member-service-data-migration:{{.VER}} + - docker push docker.build.91app.io/member-service:{{.VER}} + + deploy: + desc: usage => task MARKET=TW ENV=QA VER=0.13.8 KUBE_CONFIG=$KUBE_CONFIG_EKS_TW_QA deploy + vars: + TS: + #sh: TZ=":Asia/Taipei" date +"%Y%m%d%H%M" + sh: nu -c 'date to-timezone "Asia/Taipei" | date format "%Y%m%d%H%M"' + FULL_VER: + sh: echo "{{.VER}}-$(git rev-parse --short HEAD)" + WORKDIR: + sh: echo "deployments/{{.MARKET}}-{{.ENV}}-{{.FULL_VER}}-{{.TS}}" + silent: true + preconditions: + - sh: "[ ! -z {{.MARKET}} ]" + msg: "MARKET(TW/PX/HK/MY) was required." + - sh: "[ ! -z {{.ENV}} ]" + msg: "ENV(QA/Prod) was required." + - sh: "[ ! -z {{.VER}} ]" + msg: "VER(ex: 1.2.0) was required." + - sh: "[ ! -z {{.KUBE_CONFIG}} ]" + msg: "KUBE_CONFIG(...) was required." + cmds: + - echo MARKET={{.MARKET}} + - echo ENV={{.ENV}} + - echo VER={{.VER}} + - echo WORKDIR={{.WORKDIR}} + - nu -c 'mkdir -s {{.WORKDIR}}' + - nu -c 'echo {{.KUBE_CONFIG}} | hash base64 --decode' > {{.WORKDIR}}/kube_config + - echo {{.FULL_VER}} + - dotnet run --project src/NineYi.Msa.K8S.Template.GeneratorApp -- -s $(pwd)/env -d $(pwd)/{{.WORKDIR}} -v {{.FULL_VER}} + # - echo ----------debug---------------- + # - cat {{.WORKDIR}}/kube_config + # - cat {{.WORKDIR}}/config-map.yaml + # - cat {{.WORKDIR}}/webapi-deployment-map.yaml + - kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/{{.MARKET}}-{{.ENV}}-ConfigMap.yaml + - kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/{{.MARKET}}-{{.ENV}}-Deployment.yaml + + deploy2: + desc: usage => task MARKET=TW ENV=QA VER=0.15.1-c86783fe KUBE_CONFIG=$KUBE_CONFIG_EKS_TW_QA deploy2 + vars: + TS: + sh: nu -c 'date to-timezone "Asia/Taipei" | date format "%Y%m%d%H%M"' + WORKDIR: + sh: echo "deployments/{{.MARKET}}-{{.ENV}}-{{.VER}}-{{.TS}}" + silent: true + preconditions: + - sh: "[ ! -z {{.MARKET}} ]" + msg: "MARKET(TW/PX/HK/MY) was required." + - sh: "[ ! -z {{.ENV}} ]" + msg: "ENV(QA/Prod) was required." + - sh: "[ ! -z {{.VER}} ]" + msg: "VER(ex: 0.15.1-c86783fe) was required." + cmds: + - | + echo MARKET={{.MARKET}} + echo ENV={{.ENV}} + echo VER={{.VER}} + echo WORKDIR={{.WORKDIR}} + mkdir -p {{.WORKDIR}} + cat env/WebApi_{{.MARKET}}_{{.ENV}}.env | grep -v '^#\|^_\|^K8S_' | grep '\S' | sed -e 's/^/K8S_CM_/' > {{.WORKDIR}}/env_file + cat env/WebApi_{{.MARKET}}_{{.ENV}}.env | grep '^K8S_' | grep '\S' >> {{.WORKDIR}}/env_file + set -a; source {{.WORKDIR}}/env_file; set +a; + export K8S_COMMON_IMAGE_VERSION={{.VER}}; + cat pipeline/templates/config-maps.yaml.gpl | gomplate > {{.WORKDIR}}/config-map.yaml + cat pipeline/templates/webapi-deployment.yaml.gpl | gomplate > {{.WORKDIR}}/webapi-deployment.yaml + cat pipeline/templates/dm-sync-daily-deployment.yaml.gpl | gomplate > {{.WORKDIR}}/dm-sync-daily-deployment.yaml + cat pipeline/templates/dm-shop-daily-deployment.yaml.gpl | gomplate > {{.WORKDIR}}/dm-shop-daily-deployment.yaml + cat pipeline/templates/dm-sync-deployment.yaml.gpl | gomplate > {{.WORKDIR}}/dm-sync-deployment.yaml + cat pipeline/templates/dm-shop-deployment.yaml.gpl | gomplate > {{.WORKDIR}}/dm-shop-deployment.yaml + + if [ ! -z {{.KUBE_CONFIG}} ]; then + echo {{.KUBE_CONFIG}} | base64 -d > {{.WORKDIR}}/kube_config + kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/config-map.yaml + kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/webapi-deployment.yaml + kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/dm-sync-daily-deployment.yaml + kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/dm-shop-daily-deployment.yaml + kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/dm-sync-deployment.yaml + kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/dm-shop-deployment.yaml + fi + + deploy-admin-tools: + desc: usage => task MARKET=TW ENV=QA KUBE_CONFIG=$KUBE_CONFIG_EKS_TW_QA deploy-admin-tools + vars: + TS: + sh: nu -c 'date to-timezone "Asia/Taipei" | date format "%Y%m%d%H%M"' + WORKDIR: + sh: echo "deployments/admin-tools-{{.MARKET}}-{{.ENV}}-{{.TS}}" + silent: true + preconditions: + - sh: "[ ! -z {{.MARKET}} ]" + msg: "MARKET(TW/PX/HK/MY) was required." + - sh: "[ ! -z {{.ENV}} ]" + msg: "ENV(QA/Prod) was required." + cmds: + - | + echo MARKET={{.MARKET}} + echo ENV={{.ENV}} + echo WORKDIR={{.WORKDIR}} + mkdir -p {{.WORKDIR}} + cat env/WebApi_{{.MARKET}}_{{.ENV}}.env | grep -v '^#\|^_\|^K8S_' | grep '\S' | sed -e 's/^/K8S_CM_/' > {{.WORKDIR}}/env_file + cat env/WebApi_{{.MARKET}}_{{.ENV}}.env | grep '^K8S_' | grep '\S' >> {{.WORKDIR}}/env_file + set -a; source {{.WORKDIR}}/env_file; set +a; + cat pipeline/templates/pg-vacuum-daily-deployment.yaml.gpl | gomplate > {{.WORKDIR}}/pg-vacuum-daily-deployment.yaml + + if [ ! -z {{.KUBE_CONFIG}} ]; then + echo {{.KUBE_CONFIG}} | base64 -d > {{.WORKDIR}}/kube_config + kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/pg-vacuum-daily-deployment.yaml + fi + + auto-test: + cmds: + - echo curl -X POST -F token=$AUTOTEST_CI_TRIGGER_TOKEN -F "ref=master" -F "variables\[TYPE\]=qa-test" -F "variables\[SITE\]=erp" -F "variables\[PROJECT\]=member_service" -F "variables\[CI_COMMIT_TAG\]={{.CLI_ARGS}}" https://gitlab.91app.com/api/v4/projects/2831/trigger/pipeline + - curl -X POST -F token=$AUTOTEST_CI_TRIGGER_TOKEN -F "ref=master" -F "variables\[TYPE\]=qa-test" -F "variables\[SITE\]=erp" -F "variables\[PROJECT\]=member_service" -F "variables\[CI_COMMIT_TAG\]={{.CLI_ARGS}}" https://gitlab.91app.com/api/v4/projects/2831/trigger/pipeline + + ef-codegen-webstoredb: + desc: EF Core 反向工程產生 WebStoreDbContext EF Entities + dir: "src/NineYi.Msa.MemberService.Infrastructure.DB" + cmds: + - dotnet ef dbcontext scaffold "$WEBSTORE_DB_CONN_STR" Microsoft.EntityFrameworkCore.SqlServer -o AutoGenerated/WebStore -t MemberInfo -t VipMember -t Shop -t VipMemberInfo -t Member -c WebStoreDbContext -n NineYi.Msa.MemberService.Infrastructure.DB.WebStore --force --no-onconfiguring --use-database-names + + ef-codegen-crmdb: + desc: EF Core 反向工程產生 CrmDbContext EF Entities + dir: "src/NineYi.Msa.MemberService.Infrastructure.DB" + cmds: + - dotnet ef dbcontext scaffold "$CRM_DB_CONN_STR" Microsoft.EntityFrameworkCore.SqlServer -o AutoGenerated/CRM -t CrmMember -t CrmMemberInfo -t CrmMemberCustomField -c CrmDbContext -n NineYi.Msa.MemberService.Infrastructure.DB.CRM --force --no-onconfiguring --use-database-names + + doc-codegen-all: + desc: 透過更新外部 swagger,並根據 swagger 來 codegen 所有 client 及 server + cmds: + - task: update-membership-swagger + #- task: doc-codegen-membership-client + - task: doc-codegen-client + - task: doc-codegen-server + - task: doc-codegen-testing-member-service-client + + doc-codegen-client: + desc: 透過 swagger 來產生 WebAPI 的 csharp httpclient code + cmds: + - nswag openapi2csclient /input:docs/swagger-private.yaml /classname:{controller}MemberClient /namespace:NineYi.Msa.MemberService.Infrastructure.Adapter /output:src/NineYi.Msa.MemberService.Infrastructure.Adapter/AutoGenerated/MemberClient.cs /jsonLibrary:SystemTextJson /generateClientInterfaces:true /exposeJsonSerializerSettings:false /useBaseUrl:false /GenerateOptionalPropertiesAsNullable:true + + doc-codegen-server: + desc: 透過 swagger 來產生 WebAPI 的 csharp server code + dir: "src/NineYi.Msa.OpenApiCodeGen" + cmds: + - dotnet run ../../docs/swagger-private.yaml ../NineYi.Msa.MemberService.WebAPI/AutoGenerated/Controller.cs NineYi.Msa.MemberService.WebAPI.Controllers + + doc-codegen-testing-member-service-client: + desc: 透過 swagger 來產生 WebAPI 的 csharp testing httpclient code + cmds: + - nswag openapi2csclient /input:docs/swagger-private.yaml /classname:{controller}MemberServiceClient /namespace:NineYi.Msa.MemberService.WebAPI.IntegrationTest.Testing /output:test/NineYi.Msa.MemberService.WebAPI.IntegrationTest/AutoGenerated/MemberServiceClient.cs /jsonLibrary:SystemTextJson /generateClientInterfaces:true /exposeJsonSerializerSettings:false /useBaseUrl:false /ClientBaseClass:MemberServiceClientBase /UseHttpRequestMessageCreationMethod:true /wrapResponses:true /GenerateOptionalPropertiesAsNullable:true /excludedParameterNames:ny-market,ny-shop-id,ny-user-id,ny-service-cert,ny-api-scopes,ny-trace-id,ny-idempotency-key + + update-membership-swagger: + desc: 從 Membership QA 抓取 swagger 文件 + cmds: + - curl -sL https://membership-internal.qa.91dev.tw/swagger/v1/swagger.json -o docs/external/membership/swagger.json + + doc-codegen-membership-client: + desc: 從本地的 Membership Swagger 文件產生 Membership httpclient + cmds: + - nswag openapi2csclient /input:docs/external/membership/swagger.json /classname:{controller}MemberShipClient /namespace:NineYi.Msa.MemberService.SupportingSubdomain.MemberShip /output:src/NineYi.Msa.MemberService.SupportingSubdomain/MemberShip/AutoGenerated/MemberShipClient.cs /jsonLibrary:SystemTextJson /generateClientInterfaces:true /exposeJsonSerializerSettings:false /useBaseUrl:false /ClientBaseClass:MemberShipClientBase /UseHttpRequestMessageCreationMethod:true /wrapResponses:true /GenerateOptionalPropertiesAsNullable:true /excludedParameterNames:X-API-KEY,NY-SHOP-ID + + mock-membership: + dir: "docs/external/membership" + cmds: + - imposter up -p 8100 + + stop-mock-membership: + dir: "docs/external/membership" + cmds: + - imposter down + + doc-codegen-contract: + desc: 透過 swagger 來產生 contract + dir: "src/NineYi.Msa.OpenApiCodeGen" + cmds: + - dotnet run ../../docs/swagger-private.yaml ../NineYi.Msa.MemberService.Contract/AutoGenerated/Contracts.cs NineYi.Msa.MemberService.Contract + + push-api-doc: + desc: 將 api-doc 放至 AM + dir: "docs" + vars: + VER: "latest" + cmds: + # 用 zip 指令有發生檔案 upload 然後 download 下來後 unzip 失敗,出現 Bad CRC 的 Error,改用 7z 就一切正常了 + - cd public; 7z a apidoc.zip ./apidoc + - cd internal; 7z a apidoc.zip ./apidoc + # - cd public; zip -r ./apidoc.zip ./apidoc + # - cd internal; zip -r ./apidoc.zip ./apidoc + - curl -v -u ${AM_USER}:${AM_PASSWORD} --upload-file ./public/apidoc/swagger.yaml https://entry.build.91app.io/repository/nineyi-develop-raw-hosted/Member_Service/{{.VER}}/swagger.yaml + - curl -v -u ${AM_USER}:${AM_PASSWORD} --upload-file ./internal/apidoc/swagger.yaml https://entry.build.91app.io/repository/nineyi-develop-raw-hosted/Member_Service/{{.VER}}/swagger-internal.yaml + - curl -v -u ${AM_USER}:${AM_PASSWORD} --upload-file ./public/apidoc.zip https://entry.build.91app.io/repository/nineyi-develop-raw-hosted/Member_Service/{{.VER}}/apidoc-{{.VER}}.zip + - curl -v -u ${AM_USER}:${AM_PASSWORD} --upload-file ./internal/apidoc.zip https://entry.build.91app.io/repository/nineyi-develop-raw-hosted/Member_Service/{{.VER}}/apidoc-internal-{{.VER}}.zip + - rm ./public/apidoc.zip + - rm ./internal/apidoc.zip + + deploy-api-doc: + desc: 將觸發 api-doc 的 CD 流程 + dir: "docs" + vars: + VER: "latest" + cmds: + - curl -X POST -F token=$API_DOC_CD_TOKEN -F ref="develop" -F "variables[SERVICE_NAME]=Member_Service" -F "variables[SERVICE_ID]=Member_Service" https://gitlab.91app.com/api/v4/projects/2682/trigger/pipeline + + run-dots: + desc: 在本地執行 dots + cmds: + - nu -c 'docker run --rm -it -w /workdir -v /var/run/docker.sock:/var/run/docker.sock -v $"($env.PWD):/workdir:rw" docker.build.91app.io/dots ash' + + redo-nmq-task: + desc: 重新執行 NMQ Task,usage => task Market=TW Env=QA JobName=MemberServiceSyncWorker redo-nmq-task + cmds: + - task: nmq-task + vars: { Method: Redo } + + delete-nmq-task: + desc: 刪除執行 NMQ Task,usage => task Market=TW Env=QA JobName=MemberServiceSyncWorker delete-nmq-task + cmds: + - task: nmq-task + vars: { Method: Delete } + + nmq-task: + desc: 執行 NMQ Task,usage => task Method=Redo Market=TW Env=QA JobName=MemberServiceSyncWorker nmq-task + cmds: + - dotnet run --project src/NineYi.Msa.NMQ3TaskApp -- {{.Method}} -m {{.Market}} -p {{.Env}} -j {{.JobName}} + + dm-diff: + desc: 執行 DataMigration.DiffReport,usage => task dm-diff -- -s C:\\source.csv + dir: "src/NineYi.Msa.MemberService.DataMigration.DiffReport.ConsoleApp" + cmds: + - dotnet run -- {{.CLI_ARGS}} + \ No newline at end of file diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/.dockerignore b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/.dockerignore new file mode 100644 index 00000000..cd967fc3 --- /dev/null +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Controllers/WeatherForecastController.cs b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..27067f12 --- /dev/null +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Controllers/WeatherForecastController.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.Idempotent.WebApi.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private static readonly List s_repository = new(); + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpPost("{temperature}")] + [Idempotent] + public WeatherForecast Post(int temperature) + { + var data = new WeatherForecast { TemperatureC = temperature, Date = DateTime.UtcNow }; + s_repository.Add(data); + + return data; + } + + [HttpGet] + public IEnumerable Get() + { + var rng = new Random(); + return s_repository.Select(p => new WeatherForecast + { + TemperatureC = p.TemperatureC, + Summary = Summaries[rng.Next(Summaries.Length)] + }) + .ToArray(); + } +} diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Dockerfile b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Dockerfile new file mode 100644 index 00000000..f6db24e1 --- /dev/null +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Dockerfile @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["src/Lab.Idempotent.WebApi/Lab.Idempotent.WebApi.csproj", "Lab.Idempotent.WebApi/"] +RUN dotnet restore "src/Lab.Idempotent.WebApi/Lab.Idempotent.WebApi.csproj" +COPY . . +WORKDIR "/src/Lab.Idempotent.WebApi" +RUN dotnet build "Lab.Idempotent.WebApi.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Lab.Idempotent.WebApi.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Lab.Idempotent.WebApi.dll"] diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs new file mode 100644 index 00000000..7126e50d --- /dev/null +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs @@ -0,0 +1,87 @@ +using System.Net; +using System.Text.Json; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Caching.Distributed; + +namespace Lab.Idempotent.WebApi; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +public class IdempotentAttribute : Attribute, IFilterFactory +{ + public bool IsReusable => false; + + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + var distributedCache = (IDistributedCache)serviceProvider.GetService(typeof(IDistributedCache)); + + var filter = new IdempotentAttributeFilter(distributedCache); + return filter; + } +} + +public class IdempotentAttributeFilter : ActionFilterAttribute +{ + private readonly IDistributedCache _distributedCache; + private bool _isIdempotencyCache = false; + const string HeaderName = "IdempotencyKey"; + private string _idempotencyKey; + + public IdempotentAttributeFilter(IDistributedCache distributedCache) + { + _distributedCache = distributedCache; + } + + public override void OnActionExecuting(ActionExecutingContext context) + { + if (context.HttpContext.Request.Headers.TryGetValue(HeaderName, out var idempotencyKey) == false) + { + context.Result = new ObjectResult(new + { + ErrorCode = "NotFoundIdempotentKey", + ErrorMessage = "Not found Idempotent key in header", + Data = new + { + PropertyName = "IdempotentKey", + Value = "" + } + }) + { + StatusCode = (int)HttpStatusCode.BadRequest + }; + + return; + } + + this._idempotencyKey = idempotencyKey.ToString(); + + var cacheData = this._distributedCache.GetString(this.GetDistributedCacheKey()); + if (cacheData == null) + { + return; + } + + context.Result = JsonSerializer.Deserialize(cacheData); + this._isIdempotencyCache = true; + } + + public override void OnResultExecuted(ResultExecutedContext context) + { + if (_isIdempotencyCache) + { + return; + } + + var contextResult = context.Result; + + DistributedCacheEntryOptions cacheOptions = new DistributedCacheEntryOptions(); + cacheOptions.AbsoluteExpirationRelativeToNow = new TimeSpan(24, 0, 0); + + _distributedCache.SetString(GetDistributedCacheKey(), JsonSerializer.Serialize(contextResult), cacheOptions); + } + + private string GetDistributedCacheKey() + { + return "Idempotency:" + _idempotencyKey; + } +} diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Lab.Idempotent.WebApi.csproj b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Lab.Idempotent.WebApi.csproj new file mode 100644 index 00000000..0db9dec4 --- /dev/null +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Lab.Idempotent.WebApi.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + Linux + + + + + + + diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Program.cs b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Program.cs new file mode 100644 index 00000000..42ffb20e --- /dev/null +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Program.cs @@ -0,0 +1,27 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddDistributedMemoryCache(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/WeatherForecast.cs b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/WeatherForecast.cs new file mode 100644 index 00000000..e032b21b --- /dev/null +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace Lab.Idempotent.WebApi; + +public class WeatherForecast +{ + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/appsettings.Development.json b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/appsettings.json b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From 5f07ed2bc24b7c14c0f4fce1d67981dca590b67e Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 8 Oct 2022 23:33:16 +0800 Subject: [PATCH 282/424] =?UTF-8?q?fix:=20=E5=BF=AB=E5=8F=96=E5=A4=B1?= =?UTF-8?q?=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WebAPI/Idempotent/Lab.Idempotent.sln | 5 + WebAPI/Idempotent/Taskfile.yml | 453 +----------------- .../Controllers/WeatherForecastController.cs | 20 +- .../src/Lab.Idempotent.WebApi/Failure.cs | 39 ++ .../IdempotentAttributeFilter.cs | 63 +-- .../src/Lab.Idempotent.WebApi/Program.cs | 23 +- 6 files changed, 111 insertions(+), 492 deletions(-) create mode 100644 WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Failure.cs diff --git a/WebAPI/Idempotent/Lab.Idempotent.sln b/WebAPI/Idempotent/Lab.Idempotent.sln index bb9d84a2..75b40d4c 100644 --- a/WebAPI/Idempotent/Lab.Idempotent.sln +++ b/WebAPI/Idempotent/Lab.Idempotent.sln @@ -6,6 +6,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{FABC7D61-6 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Idempotent.WebApi", "src\Lab.Idempotent.WebApi\Lab.Idempotent.WebApi.csproj", "{EA97918D-52E7-4DD2-A2F5-5B5A76FED4FF}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{87BA93CC-B40F-45DC-BB6D-3393B443C7CD}" + ProjectSection(SolutionItems) = preProject + Taskfile.yml = Taskfile.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/WebAPI/Idempotent/Taskfile.yml b/WebAPI/Idempotent/Taskfile.yml index b8174c4b..532f9520 100644 --- a/WebAPI/Idempotent/Taskfile.yml +++ b/WebAPI/Idempotent/Taskfile.yml @@ -10,457 +10,6 @@ tasks: desc: Init development environment cmds: - task: redis-start - - task: db-start - - task: db-update - - task: db-mssql-start - - task: s3-minio-start - - task: ddb-start - - task: ddb-init - - task: mock-membership - dev-stop: - desc: Stop development environment - cmds: - - docker-compose down - - api-dev: - desc: WebApi Development - dir: "src/NineYi.Msa.MemberService.WebAPI" - cmds: - - dotnet watch run --local {{.CLI_ARGS}} | jq -rR '. as $line | try (fromjson | "\(._ts) | \(._lvl) | \(._hid)-\(._ver) | \(._srctx)", ._msg, ._props) catch $line' - - dm-dev: - desc: DataMigration Development. Usage => task dm-dev -- diff -s 2001-01-01 -e 2022-01-01 - dir: "src/NineYi.Msa.MemberService.DataMigration.ConsoleApp" - cmds: - - dotnet watch run {{.CLI_ARGS}} --local true | jq -rR '. as $line | try (fromjson | "\(._ts) | \(._lvl) | \(._hid)-\(._ver) | \(._srctx)", ._msg, ._props) catch $line' - - dm-run: - desc: DataMigration run. Usage => task dm-run -- diff -s 2001-01-01 -e 2022-01-01 - dir: "src/NineYi.Msa.MemberService.DataMigration.ConsoleApp" - cmds: - - dotnet run {{.CLI_ARGS}} --local true - - db-start: - desc: start PostgreSQL at local - cmds: - - docker-compose up -d --remove-orphans db - - db-update: - desc: apply db migration on local PostgreSQL - cmds: - - dotnet ef database update {{.CLI_ARGS}} --project src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext - - redis-start: - desc: start redis 5.X version - cmds: - - docker-compose up -d redis - - redis-admin-start: - desc: admin ui to manage redis - cmds: - - docker-compose up -d redis-admin - - db-otm-db-start: - dir: test/NineYi.Msa.MemberService.NMQ3.Worker.Infrastructure.Tests - cmds: - - docker run --privileged -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=pass@w0rd1~' -p 1433:1433 -d datagrip/mssql-server-linux - - db-otm-db-update: - dir: test/NineYi.Msa.MemberService.NMQ3.Worker.Infrastructure.Tests - cmds: - - dotnet build - - dotnet ef database update --context CrmDbContext --no-build - - dotnet ef database update --context WebStoreDbContext --no-build - - db-mssql-start: - desc: start MSSQL at local - cmds: - - docker-compose up -d db-sql - - db-mssql-update: - desc: db update on local MSSQL - dir: src/NineYi.Msa.MemberService.DataMigration.Infrastructure - cmds: - - dotnet build - - dotnet ef database update --context CrmDbContext --no-build - - dotnet ef database update --context WebStoreDbContext --no-build - - db-list-migrations: - desc: member-service list migrations - cmds: - - dotnet ef migrations list {{.CLI_ARGS}} -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext - - db-add-migration: - desc: member-service add migration - cmds: - - dotnet ef migrations add {{.CLI_ARGS}} -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext - - db-remove-migration: - desc: member-service remove migration - cmds: - - dotnet ef migrations remove -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext - - db-script: - desc: generate member-service db script - cmds: - - dotnet ef migrations script 0 -p src/NineYi.Msa.MemberService.Infrastructure.DB --context MemberDbContext - - ddb-start: - desc: start dynamodb at local - cmds: - - docker-compose up -d ddb - - ddb-clear: - desc: clear ddb table - cmds: - - docker-compose restart ddb - - aws dynamodb create-table --endpoint-url http://localhost:8000 --cli-input-json file://./ddb.json - - ddb-init: - desc: init dynamodb table at local - cmds: - - aws dynamodb create-table --endpoint-url http://localhost:8000 --cli-input-json file://./ddb.json - - ddb-list: - desc: list dynamodb tables at local - cmds: - - aws dynamodb list-tables --endpoint-url http://localhost:8000 - - ddb-logs: - cmds: - - docker-compose logs -f ddb - - ddb-admin-start: - desc: start dynamodb admin - cmds: - - docker-compose up -d ddb-admin - - s3-minio-start: - desc: start s3-minio at local - cmds: - - docker-compose up -d s3-minio - - gen-k8s: - desc: generate k8s - cmds: - - dotnet run --project src/NineYi.Msa.K8S.Template.GeneratorApp -- -s $(pwd)/env -d $(pwd)/env/k8s -v 1.0.0.1 - - gen-configs-k8s: - desc: generate env config and k8s - cmds: - - task: gen-configs - - dotnet run --project src/NineYi.Msa.K8S.Template.GeneratorApp -- -s $(pwd)/env -d $(pwd)/env/k8s -v 1.0.0.1 - - gen-configs: - desc: Generate configs - dir: "env" - cmds: - - nu -c 'rm -f *.env' - - task: _gen-config - vars: { MARKET: "TW", ENV: "DEV" } - - task: _gen-config - vars: { MARKET: "TW", ENV: "QA" } - - task: _gen-config - vars: { MARKET: "TW", ENV: "STRESS" } - - task: _gen-config - vars: { MARKET: "PX", ENV: "QA" } - - task: _gen-config - vars: { MARKET: "HK", ENV: "QA" } - - task: _gen-config - vars: { MARKET: "MY", ENV: "QA" } - - task: _gen-config - vars: { MARKET: "TW", ENV: "PROD" } - - task: _gen-config - vars: { MARKET: "PX", ENV: "PROD" } - - task: _gen-config - vars: { MARKET: "HK", ENV: "PROD" } - - task: _gen-config - vars: { MARKET: "MY", ENV: "PROD" } - - _gen-config: - dir: "env" - cmds: - - echo "## This file was generated by task gen-configs" > WebApi_{{.MARKET}}_{{.ENV}}.env - - echo >> WebApi_{{.MARKET}}_{{.ENV}}.env - - opc -i market={{.MARKET}} -i env={{.ENV}} -m WebApi.rego -f env-file >> WebApi_{{.MARKET}}_{{.ENV}}.env - - login-am: - desc: login am with envs $AM_USER_NAME $AM_USER_PASSWD - cmds: - - docker login docker.build.91app.io -u $AM_USER_NAME -p $AM_USER_PASSWD - - build-image: - desc: usage => task build-image -- 0.1.2 - vars: - VER: - sh: echo "{{.CLI_ARGS}}-$(git rev-parse --short HEAD)" - cmds: - - docker build --build-arg VER={{.VER}} . -t docker.build.91app.io/member-service:{{.VER}} - - push-image: - desc: usage => task build-image -- 0.1.2 - vars: - VER: - sh: echo "{{.CLI_ARGS}}-$(git rev-parse --short HEAD)" - cmds: - - docker push docker.build.91app.io/member-service:{{.VER}} - - build-dm-image: - desc: usage => task build-dm-image -- 0.1.2 - vars: - VER: - sh: echo "{{.CLI_ARGS}}-$(git rev-parse --short HEAD)" - cmds: - - docker build --build-arg VER={{.VER}} . -t docker.build.91app.io/member-service-data-migration:{{.VER}} -f Dockerfile.DataMigration - - push-dm-image: - desc: usage => task build-dm-image -- 0.1.2 - vars: - VER: - sh: echo "{{.CLI_ARGS}}-$(git rev-parse --short HEAD)" - cmds: - - docker push docker.build.91app.io/member-service-data-migration:{{.VER}} - - build-and-push-image: - desc: usage => task build-and-push-image -- 0.1.2 - vars: - VER: - sh: echo "{{.CLI_ARGS}}-$(git rev-parse --short HEAD)" - cmds: - - docker build --build-arg VER={{.VER}} . -t docker.build.91app.io/member-service:{{.VER}} - - docker build --build-arg VER={{.VER}} . -t docker.build.91app.io/member-service-data-migration:{{.VER}} -f Dockerfile.DataMigration - - docker push docker.build.91app.io/member-service-data-migration:{{.VER}} - - docker push docker.build.91app.io/member-service:{{.VER}} - - deploy: - desc: usage => task MARKET=TW ENV=QA VER=0.13.8 KUBE_CONFIG=$KUBE_CONFIG_EKS_TW_QA deploy - vars: - TS: - #sh: TZ=":Asia/Taipei" date +"%Y%m%d%H%M" - sh: nu -c 'date to-timezone "Asia/Taipei" | date format "%Y%m%d%H%M"' - FULL_VER: - sh: echo "{{.VER}}-$(git rev-parse --short HEAD)" - WORKDIR: - sh: echo "deployments/{{.MARKET}}-{{.ENV}}-{{.FULL_VER}}-{{.TS}}" - silent: true - preconditions: - - sh: "[ ! -z {{.MARKET}} ]" - msg: "MARKET(TW/PX/HK/MY) was required." - - sh: "[ ! -z {{.ENV}} ]" - msg: "ENV(QA/Prod) was required." - - sh: "[ ! -z {{.VER}} ]" - msg: "VER(ex: 1.2.0) was required." - - sh: "[ ! -z {{.KUBE_CONFIG}} ]" - msg: "KUBE_CONFIG(...) was required." - cmds: - - echo MARKET={{.MARKET}} - - echo ENV={{.ENV}} - - echo VER={{.VER}} - - echo WORKDIR={{.WORKDIR}} - - nu -c 'mkdir -s {{.WORKDIR}}' - - nu -c 'echo {{.KUBE_CONFIG}} | hash base64 --decode' > {{.WORKDIR}}/kube_config - - echo {{.FULL_VER}} - - dotnet run --project src/NineYi.Msa.K8S.Template.GeneratorApp -- -s $(pwd)/env -d $(pwd)/{{.WORKDIR}} -v {{.FULL_VER}} - # - echo ----------debug---------------- - # - cat {{.WORKDIR}}/kube_config - # - cat {{.WORKDIR}}/config-map.yaml - # - cat {{.WORKDIR}}/webapi-deployment-map.yaml - - kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/{{.MARKET}}-{{.ENV}}-ConfigMap.yaml - - kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/{{.MARKET}}-{{.ENV}}-Deployment.yaml - - deploy2: - desc: usage => task MARKET=TW ENV=QA VER=0.15.1-c86783fe KUBE_CONFIG=$KUBE_CONFIG_EKS_TW_QA deploy2 - vars: - TS: - sh: nu -c 'date to-timezone "Asia/Taipei" | date format "%Y%m%d%H%M"' - WORKDIR: - sh: echo "deployments/{{.MARKET}}-{{.ENV}}-{{.VER}}-{{.TS}}" - silent: true - preconditions: - - sh: "[ ! -z {{.MARKET}} ]" - msg: "MARKET(TW/PX/HK/MY) was required." - - sh: "[ ! -z {{.ENV}} ]" - msg: "ENV(QA/Prod) was required." - - sh: "[ ! -z {{.VER}} ]" - msg: "VER(ex: 0.15.1-c86783fe) was required." - cmds: - - | - echo MARKET={{.MARKET}} - echo ENV={{.ENV}} - echo VER={{.VER}} - echo WORKDIR={{.WORKDIR}} - mkdir -p {{.WORKDIR}} - cat env/WebApi_{{.MARKET}}_{{.ENV}}.env | grep -v '^#\|^_\|^K8S_' | grep '\S' | sed -e 's/^/K8S_CM_/' > {{.WORKDIR}}/env_file - cat env/WebApi_{{.MARKET}}_{{.ENV}}.env | grep '^K8S_' | grep '\S' >> {{.WORKDIR}}/env_file - set -a; source {{.WORKDIR}}/env_file; set +a; - export K8S_COMMON_IMAGE_VERSION={{.VER}}; - cat pipeline/templates/config-maps.yaml.gpl | gomplate > {{.WORKDIR}}/config-map.yaml - cat pipeline/templates/webapi-deployment.yaml.gpl | gomplate > {{.WORKDIR}}/webapi-deployment.yaml - cat pipeline/templates/dm-sync-daily-deployment.yaml.gpl | gomplate > {{.WORKDIR}}/dm-sync-daily-deployment.yaml - cat pipeline/templates/dm-shop-daily-deployment.yaml.gpl | gomplate > {{.WORKDIR}}/dm-shop-daily-deployment.yaml - cat pipeline/templates/dm-sync-deployment.yaml.gpl | gomplate > {{.WORKDIR}}/dm-sync-deployment.yaml - cat pipeline/templates/dm-shop-deployment.yaml.gpl | gomplate > {{.WORKDIR}}/dm-shop-deployment.yaml - - if [ ! -z {{.KUBE_CONFIG}} ]; then - echo {{.KUBE_CONFIG}} | base64 -d > {{.WORKDIR}}/kube_config - kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/config-map.yaml - kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/webapi-deployment.yaml - kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/dm-sync-daily-deployment.yaml - kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/dm-shop-daily-deployment.yaml - kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/dm-sync-deployment.yaml - kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/dm-shop-deployment.yaml - fi - - deploy-admin-tools: - desc: usage => task MARKET=TW ENV=QA KUBE_CONFIG=$KUBE_CONFIG_EKS_TW_QA deploy-admin-tools - vars: - TS: - sh: nu -c 'date to-timezone "Asia/Taipei" | date format "%Y%m%d%H%M"' - WORKDIR: - sh: echo "deployments/admin-tools-{{.MARKET}}-{{.ENV}}-{{.TS}}" - silent: true - preconditions: - - sh: "[ ! -z {{.MARKET}} ]" - msg: "MARKET(TW/PX/HK/MY) was required." - - sh: "[ ! -z {{.ENV}} ]" - msg: "ENV(QA/Prod) was required." - cmds: - - | - echo MARKET={{.MARKET}} - echo ENV={{.ENV}} - echo WORKDIR={{.WORKDIR}} - mkdir -p {{.WORKDIR}} - cat env/WebApi_{{.MARKET}}_{{.ENV}}.env | grep -v '^#\|^_\|^K8S_' | grep '\S' | sed -e 's/^/K8S_CM_/' > {{.WORKDIR}}/env_file - cat env/WebApi_{{.MARKET}}_{{.ENV}}.env | grep '^K8S_' | grep '\S' >> {{.WORKDIR}}/env_file - set -a; source {{.WORKDIR}}/env_file; set +a; - cat pipeline/templates/pg-vacuum-daily-deployment.yaml.gpl | gomplate > {{.WORKDIR}}/pg-vacuum-daily-deployment.yaml - - if [ ! -z {{.KUBE_CONFIG}} ]; then - echo {{.KUBE_CONFIG}} | base64 -d > {{.WORKDIR}}/kube_config - kubectl apply --kubeconfig {{.WORKDIR}}/kube_config -f {{.WORKDIR}}/pg-vacuum-daily-deployment.yaml - fi - - auto-test: - cmds: - - echo curl -X POST -F token=$AUTOTEST_CI_TRIGGER_TOKEN -F "ref=master" -F "variables\[TYPE\]=qa-test" -F "variables\[SITE\]=erp" -F "variables\[PROJECT\]=member_service" -F "variables\[CI_COMMIT_TAG\]={{.CLI_ARGS}}" https://gitlab.91app.com/api/v4/projects/2831/trigger/pipeline - - curl -X POST -F token=$AUTOTEST_CI_TRIGGER_TOKEN -F "ref=master" -F "variables\[TYPE\]=qa-test" -F "variables\[SITE\]=erp" -F "variables\[PROJECT\]=member_service" -F "variables\[CI_COMMIT_TAG\]={{.CLI_ARGS}}" https://gitlab.91app.com/api/v4/projects/2831/trigger/pipeline - - ef-codegen-webstoredb: - desc: EF Core 反向工程產生 WebStoreDbContext EF Entities - dir: "src/NineYi.Msa.MemberService.Infrastructure.DB" - cmds: - - dotnet ef dbcontext scaffold "$WEBSTORE_DB_CONN_STR" Microsoft.EntityFrameworkCore.SqlServer -o AutoGenerated/WebStore -t MemberInfo -t VipMember -t Shop -t VipMemberInfo -t Member -c WebStoreDbContext -n NineYi.Msa.MemberService.Infrastructure.DB.WebStore --force --no-onconfiguring --use-database-names - - ef-codegen-crmdb: - desc: EF Core 反向工程產生 CrmDbContext EF Entities - dir: "src/NineYi.Msa.MemberService.Infrastructure.DB" - cmds: - - dotnet ef dbcontext scaffold "$CRM_DB_CONN_STR" Microsoft.EntityFrameworkCore.SqlServer -o AutoGenerated/CRM -t CrmMember -t CrmMemberInfo -t CrmMemberCustomField -c CrmDbContext -n NineYi.Msa.MemberService.Infrastructure.DB.CRM --force --no-onconfiguring --use-database-names - - doc-codegen-all: - desc: 透過更新外部 swagger,並根據 swagger 來 codegen 所有 client 及 server - cmds: - - task: update-membership-swagger - #- task: doc-codegen-membership-client - - task: doc-codegen-client - - task: doc-codegen-server - - task: doc-codegen-testing-member-service-client - - doc-codegen-client: - desc: 透過 swagger 來產生 WebAPI 的 csharp httpclient code - cmds: - - nswag openapi2csclient /input:docs/swagger-private.yaml /classname:{controller}MemberClient /namespace:NineYi.Msa.MemberService.Infrastructure.Adapter /output:src/NineYi.Msa.MemberService.Infrastructure.Adapter/AutoGenerated/MemberClient.cs /jsonLibrary:SystemTextJson /generateClientInterfaces:true /exposeJsonSerializerSettings:false /useBaseUrl:false /GenerateOptionalPropertiesAsNullable:true - - doc-codegen-server: - desc: 透過 swagger 來產生 WebAPI 的 csharp server code - dir: "src/NineYi.Msa.OpenApiCodeGen" - cmds: - - dotnet run ../../docs/swagger-private.yaml ../NineYi.Msa.MemberService.WebAPI/AutoGenerated/Controller.cs NineYi.Msa.MemberService.WebAPI.Controllers - - doc-codegen-testing-member-service-client: - desc: 透過 swagger 來產生 WebAPI 的 csharp testing httpclient code - cmds: - - nswag openapi2csclient /input:docs/swagger-private.yaml /classname:{controller}MemberServiceClient /namespace:NineYi.Msa.MemberService.WebAPI.IntegrationTest.Testing /output:test/NineYi.Msa.MemberService.WebAPI.IntegrationTest/AutoGenerated/MemberServiceClient.cs /jsonLibrary:SystemTextJson /generateClientInterfaces:true /exposeJsonSerializerSettings:false /useBaseUrl:false /ClientBaseClass:MemberServiceClientBase /UseHttpRequestMessageCreationMethod:true /wrapResponses:true /GenerateOptionalPropertiesAsNullable:true /excludedParameterNames:ny-market,ny-shop-id,ny-user-id,ny-service-cert,ny-api-scopes,ny-trace-id,ny-idempotency-key - - update-membership-swagger: - desc: 從 Membership QA 抓取 swagger 文件 - cmds: - - curl -sL https://membership-internal.qa.91dev.tw/swagger/v1/swagger.json -o docs/external/membership/swagger.json - - doc-codegen-membership-client: - desc: 從本地的 Membership Swagger 文件產生 Membership httpclient - cmds: - - nswag openapi2csclient /input:docs/external/membership/swagger.json /classname:{controller}MemberShipClient /namespace:NineYi.Msa.MemberService.SupportingSubdomain.MemberShip /output:src/NineYi.Msa.MemberService.SupportingSubdomain/MemberShip/AutoGenerated/MemberShipClient.cs /jsonLibrary:SystemTextJson /generateClientInterfaces:true /exposeJsonSerializerSettings:false /useBaseUrl:false /ClientBaseClass:MemberShipClientBase /UseHttpRequestMessageCreationMethod:true /wrapResponses:true /GenerateOptionalPropertiesAsNullable:true /excludedParameterNames:X-API-KEY,NY-SHOP-ID - - mock-membership: - dir: "docs/external/membership" - cmds: - - imposter up -p 8100 - - stop-mock-membership: - dir: "docs/external/membership" - cmds: - - imposter down - - doc-codegen-contract: - desc: 透過 swagger 來產生 contract - dir: "src/NineYi.Msa.OpenApiCodeGen" - cmds: - - dotnet run ../../docs/swagger-private.yaml ../NineYi.Msa.MemberService.Contract/AutoGenerated/Contracts.cs NineYi.Msa.MemberService.Contract - - push-api-doc: - desc: 將 api-doc 放至 AM - dir: "docs" - vars: - VER: "latest" - cmds: - # 用 zip 指令有發生檔案 upload 然後 download 下來後 unzip 失敗,出現 Bad CRC 的 Error,改用 7z 就一切正常了 - - cd public; 7z a apidoc.zip ./apidoc - - cd internal; 7z a apidoc.zip ./apidoc - # - cd public; zip -r ./apidoc.zip ./apidoc - # - cd internal; zip -r ./apidoc.zip ./apidoc - - curl -v -u ${AM_USER}:${AM_PASSWORD} --upload-file ./public/apidoc/swagger.yaml https://entry.build.91app.io/repository/nineyi-develop-raw-hosted/Member_Service/{{.VER}}/swagger.yaml - - curl -v -u ${AM_USER}:${AM_PASSWORD} --upload-file ./internal/apidoc/swagger.yaml https://entry.build.91app.io/repository/nineyi-develop-raw-hosted/Member_Service/{{.VER}}/swagger-internal.yaml - - curl -v -u ${AM_USER}:${AM_PASSWORD} --upload-file ./public/apidoc.zip https://entry.build.91app.io/repository/nineyi-develop-raw-hosted/Member_Service/{{.VER}}/apidoc-{{.VER}}.zip - - curl -v -u ${AM_USER}:${AM_PASSWORD} --upload-file ./internal/apidoc.zip https://entry.build.91app.io/repository/nineyi-develop-raw-hosted/Member_Service/{{.VER}}/apidoc-internal-{{.VER}}.zip - - rm ./public/apidoc.zip - - rm ./internal/apidoc.zip - - deploy-api-doc: - desc: 將觸發 api-doc 的 CD 流程 - dir: "docs" - vars: - VER: "latest" - cmds: - - curl -X POST -F token=$API_DOC_CD_TOKEN -F ref="develop" -F "variables[SERVICE_NAME]=Member_Service" -F "variables[SERVICE_ID]=Member_Service" https://gitlab.91app.com/api/v4/projects/2682/trigger/pipeline - - run-dots: - desc: 在本地執行 dots - cmds: - - nu -c 'docker run --rm -it -w /workdir -v /var/run/docker.sock:/var/run/docker.sock -v $"($env.PWD):/workdir:rw" docker.build.91app.io/dots ash' - - redo-nmq-task: - desc: 重新執行 NMQ Task,usage => task Market=TW Env=QA JobName=MemberServiceSyncWorker redo-nmq-task - cmds: - - task: nmq-task - vars: { Method: Redo } - - delete-nmq-task: - desc: 刪除執行 NMQ Task,usage => task Market=TW Env=QA JobName=MemberServiceSyncWorker delete-nmq-task - cmds: - - task: nmq-task - vars: { Method: Delete } - - nmq-task: - desc: 執行 NMQ Task,usage => task Method=Redo Market=TW Env=QA JobName=MemberServiceSyncWorker nmq-task - cmds: - - dotnet run --project src/NineYi.Msa.NMQ3TaskApp -- {{.Method}} -m {{.Market}} -p {{.Env}} -j {{.JobName}} - - dm-diff: - desc: 執行 DataMigration.DiffReport,usage => task dm-diff -- -s C:\\source.csv - dir: "src/NineYi.Msa.MemberService.DataMigration.DiffReport.ConsoleApp" - cmds: - - dotnet run -- {{.CLI_ARGS}} - \ No newline at end of file + \ No newline at end of file diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Controllers/WeatherForecastController.cs b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Controllers/WeatherForecastController.cs index 27067f12..a8a97a14 100644 --- a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Controllers/WeatherForecastController.cs +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Controllers/WeatherForecastController.cs @@ -22,23 +22,23 @@ public WeatherForecastController(ILogger logger) [HttpPost("{temperature}")] [Idempotent] - public WeatherForecast Post(int temperature) + public async Task> Post(int temperature, CancellationToken cancel = default) { - var data = new WeatherForecast { TemperatureC = temperature, Date = DateTime.UtcNow }; + var rng = new Random(); + var data = new WeatherForecast + { + TemperatureC = temperature, + Summary = Summaries[rng.Next(Summaries.Length)], + Date = DateTime.UtcNow + }; s_repository.Add(data); return data; } [HttpGet] - public IEnumerable Get() + public async Task>> Get() { - var rng = new Random(); - return s_repository.Select(p => new WeatherForecast - { - TemperatureC = p.TemperatureC, - Summary = Summaries[rng.Next(Summaries.Length)] - }) - .ToArray(); + return s_repository; } } diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Failure.cs b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Failure.cs new file mode 100644 index 00000000..5b73e80a --- /dev/null +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Failure.cs @@ -0,0 +1,39 @@ +using System.Net; +using Microsoft.AspNetCore.Mvc; + +namespace Lab.Idempotent.WebApi; + +public enum FailureCode +{ + NotFoundIdempotentKey, +} + +public class Failure +{ + public static IReadOnlyDictionary Results = new Dictionary() + { + { + FailureCode.NotFoundIdempotentKey,new ObjectResult(new Failure + { + Code = FailureCode.NotFoundIdempotentKey.ToString(), + Message = "Not found Idempotent key in header", + Data = new + { + Property = "IdempotentKey", + Value = "" + }, + }) + { + StatusCode = (int)HttpStatusCode.BadRequest + } + }, + }; + + public string Code { get; set; } + + public string Message { get; set; } + + public object Data { get; set; } + + public IEnumerable Failures { get; set; } +} diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs index 7126e50d..2c12255b 100644 --- a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs @@ -1,12 +1,13 @@ using System.Net; using System.Text.Json; +using System.Text.Json.Nodes; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Caching.Distributed; namespace Lab.Idempotent.WebApi; -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false)] public class IdempotentAttribute : Attribute, IFilterFactory { public bool IsReusable => false; @@ -22,38 +23,27 @@ public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) public class IdempotentAttributeFilter : ActionFilterAttribute { + public const string HeaderName = "IdempotencyKey"; + public static readonly TimeSpan Expiration = new(0, 0, 60); + private readonly IDistributedCache _distributedCache; - private bool _isIdempotencyCache = false; - const string HeaderName = "IdempotencyKey"; private string _idempotencyKey; + private bool _hasIdempotencyKey; public IdempotentAttributeFilter(IDistributedCache distributedCache) { - _distributedCache = distributedCache; + this._distributedCache = distributedCache; } public override void OnActionExecuting(ActionExecutingContext context) { if (context.HttpContext.Request.Headers.TryGetValue(HeaderName, out var idempotencyKey) == false) { - context.Result = new ObjectResult(new - { - ErrorCode = "NotFoundIdempotentKey", - ErrorMessage = "Not found Idempotent key in header", - Data = new - { - PropertyName = "IdempotentKey", - Value = "" - } - }) - { - StatusCode = (int)HttpStatusCode.BadRequest - }; - + context.Result = Failure.Results[FailureCode.NotFoundIdempotentKey]; return; } - this._idempotencyKey = idempotencyKey.ToString(); + this._idempotencyKey = idempotencyKey; var cacheData = this._distributedCache.GetString(this.GetDistributedCacheKey()); if (cacheData == null) @@ -61,27 +51,44 @@ public override void OnActionExecuting(ActionExecutingContext context) return; } - context.Result = JsonSerializer.Deserialize(cacheData); - this._isIdempotencyCache = true; + //從快取取出內容回傳給調用端 + var jsonObject = JsonObject.Parse(cacheData); + context.Result = new ObjectResult(jsonObject["Data"]) + { + StatusCode = jsonObject["StatusCode"].GetValue() + }; + this._hasIdempotencyKey = true; } public override void OnResultExecuted(ResultExecutedContext context) { - if (_isIdempotencyCache) + if (this._hasIdempotencyKey) { return; } - var contextResult = context.Result; - - DistributedCacheEntryOptions cacheOptions = new DistributedCacheEntryOptions(); - cacheOptions.AbsoluteExpirationRelativeToNow = new TimeSpan(24, 0, 0); + var contextResult = (ObjectResult)context.Result; + if (contextResult.StatusCode != (int)HttpStatusCode.OK) + { + return; + } - _distributedCache.SetString(GetDistributedCacheKey(), JsonSerializer.Serialize(contextResult), cacheOptions); + var cacheOptions = new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = Expiration + }; + var json = JsonSerializer.Serialize(new + { + Data = contextResult.Value, + contextResult.StatusCode + }); + this._distributedCache.SetString(this.GetDistributedCacheKey(), + json, + cacheOptions); } private string GetDistributedCacheKey() { - return "Idempotency:" + _idempotencyKey; + return "IdempotencyKey:" + this._idempotencyKey; } } diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Program.cs b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Program.cs index 42ffb20e..a3a74ff6 100644 --- a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Program.cs +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Program.cs @@ -1,3 +1,8 @@ +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Unicode; + var builder = WebApplication.CreateBuilder(args); // Add services to the container. @@ -7,8 +12,11 @@ // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -builder.Services.AddDistributedMemoryCache(); - +builder.Services.AddDistributedMemoryCache(p => +{ + p.ExpirationScanFrequency = TimeSpan.FromSeconds(60); +}); +builder.Services.AddSingleton(p => CreateJsonSerializerOptions()); var app = builder.Build(); // Configure the HTTP request pipeline. @@ -25,3 +33,14 @@ app.MapControllers(); app.Run(); + +JsonSerializerOptions CreateJsonSerializerOptions() => + new() + { + Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs), + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Converters = { new JsonStringEnumConverter() } + }; From 0d3efe016772428f60fc5af0ab89d8b01fe8c284 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 9 Oct 2022 13:36:31 +0800 Subject: [PATCH 283/424] refactor --- .../IdempotentAttributeFilter.cs | 7 ++++-- .../src/Lab.Idempotent.WebApi/Program.cs | 22 +++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs index 2c12255b..af8a27e0 100644 --- a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs @@ -37,21 +37,24 @@ public IdempotentAttributeFilter(IDistributedCache distributedCache) public override void OnActionExecuting(ActionExecutingContext context) { + // 檢查 Header 有沒有 IdempotencyKey if (context.HttpContext.Request.Headers.TryGetValue(HeaderName, out var idempotencyKey) == false) { + // 沒有的話則回傳 Bad Request context.Result = Failure.Results[FailureCode.NotFoundIdempotentKey]; return; } this._idempotencyKey = idempotencyKey; - + var cacheData = this._distributedCache.GetString(this.GetDistributedCacheKey()); if (cacheData == null) { + // 沒有快取則進入 Action return; } - //從快取取出內容回傳給調用端 + // 從快取取出內容回傳給調用端 var jsonObject = JsonObject.Parse(cacheData); context.Result = new ObjectResult(jsonObject["Data"]) { diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Program.cs b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Program.cs index a3a74ff6..9bd7d200 100644 --- a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Program.cs +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/Program.cs @@ -16,7 +16,16 @@ { p.ExpirationScanFrequency = TimeSpan.FromSeconds(60); }); -builder.Services.AddSingleton(p => CreateJsonSerializerOptions()); +builder.Services.AddSingleton(p => new JsonSerializerOptions +{ + Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs), + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Converters = { new JsonStringEnumConverter() } +}); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -33,14 +42,3 @@ app.MapControllers(); app.Run(); - -JsonSerializerOptions CreateJsonSerializerOptions() => - new() - { - Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs), - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - Converters = { new JsonStringEnumConverter() } - }; From f138245bab8a17d2a1dec046dd79b49ab6927cc9 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 9 Oct 2022 23:34:47 +0800 Subject: [PATCH 284/424] refactor --- .../src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs index af8a27e0..842451f3 100644 --- a/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs +++ b/WebAPI/Idempotent/src/Lab.Idempotent.WebApi/IdempotentAttributeFilter.cs @@ -69,7 +69,8 @@ public override void OnResultExecuted(ResultExecutedContext context) { return; } - + + // 把回傳結果放到快取裡面 var contextResult = (ObjectResult)context.Result; if (contextResult.StatusCode != (int)HttpStatusCode.OK) { From d13ac7e208355a7a71e2a84fe9527bf63b540d94 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 15 Oct 2022 22:32:38 +0800 Subject: [PATCH 285/424] add test file --- Test/Lab jmeter sample/Taskfile.yml | 18 +++ Test/Lab jmeter sample/first.jmx | 175 ++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 Test/Lab jmeter sample/Taskfile.yml create mode 100644 Test/Lab jmeter sample/first.jmx diff --git a/Test/Lab jmeter sample/Taskfile.yml b/Test/Lab jmeter sample/Taskfile.yml new file mode 100644 index 00000000..ce7f3bf6 --- /dev/null +++ b/Test/Lab jmeter sample/Taskfile.yml @@ -0,0 +1,18 @@ +# Taskfile.yml + +version: "3" + +dotenv: [ "secrets/secrets.env" ] + +tasks: + clear-log: + cmds: + - nu -c 'rm -rf ./jmeter.log' + - nu -c 'rm -rf ./temp' + + first: + desc: first sample + cmds: + - task: clear-log + - nu -c 'rm -rf ./first' + - jmeter -n -t first.jmx -l ./first/result.jtl -e -o ./first diff --git a/Test/Lab jmeter sample/first.jmx b/Test/Lab jmeter sample/first.jmx new file mode 100644 index 00000000..8dd7309a --- /dev/null +++ b/Test/Lab jmeter sample/first.jmx @@ -0,0 +1,175 @@ + + + + + + false + true + false + + + + + + + + + + currentFolder + ${__BeanShell(import org.apache.jmeter.services.FileServer; FileServer.getFileServer().getBaseDir())} + = + + + currentTestPlanFileName + ${__BeanShell(import org.apache.jmeter.services.FileServer;import org.apache.commons.io.FilenameUtils; FilenameUtils.getBaseName(FileServer.getFileServer().getScriptName()))} + = + + + + + + groovy + + + true + import org.apache.jmeter.util.JMeterUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.FileUtils; + +log.warn("currentFolder: " + vars.get("currentFolder")); +log.warn("currentTestPlanFileName: " + vars.get("currentTestPlanFileName")); + + + + + + 30 + 0 + 30 + 30 + 0 + + + + false + -1 + + stoptest + + + + + allowedThroughputSurplus + 1.0 + 0.0 + + 1 + 0 + 300 + 10000 + 0 + + throughput + 35.0 + 0.0 + + 1 + + + + + + + test.k6.io + 80 + http + + + GET + true + false + false + false + + + + + + + + true + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + ${currentFolder}\\${currentTestPlanFileName}\View Results Tree.csv + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + ${currentFolder}\\${currentTestPlanFileName}\Summary Report.csv + + + + + From 10a78cfab064aac5ed180256d633a1d1d7cd59a2 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 16 Oct 2022 23:24:46 +0800 Subject: [PATCH 286/424] =?UTF-8?q?=E8=AA=BF=E6=95=B4=E8=A8=AD=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Test/Lab jmeter sample/first.jmx | 270 ++++++++++++++++++++++++++++++- 1 file changed, 266 insertions(+), 4 deletions(-) diff --git a/Test/Lab jmeter sample/first.jmx b/Test/Lab jmeter sample/first.jmx index 8dd7309a..f460b973 100644 --- a/Test/Lab jmeter sample/first.jmx +++ b/Test/Lab jmeter sample/first.jmx @@ -27,7 +27,7 @@ - + groovy @@ -42,11 +42,11 @@ log.warn("currentTestPlanFileName: " + vars.get("currentTestPlanF - + 30 0 - 30 - 30 + 10 + 300 0 @@ -95,6 +95,33 @@ log.warn("currentTestPlanFileName: " + vars.get("currentTestPlanF + + false + false + + + false + 2 + + + 60000 + 600 + 800 + + false + 150 + + + ${currentFolder}\\${currentTestPlanFileName}\graphs + true + true + false + false + ${currentFolder}\\${currentTestPlanFileName}\result.jtl + + + + true @@ -170,6 +197,241 @@ log.warn("currentTestPlanFileName: " + vars.get("currentTestPlanF ${currentFolder}\\${currentTestPlanFileName}\Summary Report.csv + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + 500 + false + + + + + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + 1000 + false + + + + + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + 500 + false + + + + + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + 1000 + false + + + + + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + true + + + + 500 + false + + + + + false + false + + + jp@gc - Response Times Over Time + jp@gc - Transactions per Second + + + Overall Response Times + Successful Transactions per Second + + + + From 7b2d0a74f4d04190ced2031eb47791b975338158 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 19 Oct 2022 23:39:31 +0800 Subject: [PATCH 287/424] feat: add test case --- Test/nbomber/Lab.NBomberTest/.gitignore | 365 ++++++++++++++++++ .../Lab.NBomberTest/Lab.NBomberTest.sln | 21 + .../Lab.NBomberTest/Lab.NBomberTest.csproj | 21 + .../test/Lab.NBomberTest/Usings.cs | 1 + .../Lab.NBomberTest/features/test.feature | 9 + .../test/Lab.NBomberTest/steps/test.cs | 64 +++ 6 files changed, 481 insertions(+) create mode 100644 Test/nbomber/Lab.NBomberTest/.gitignore create mode 100644 Test/nbomber/Lab.NBomberTest/Lab.NBomberTest.sln create mode 100644 Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/Lab.NBomberTest.csproj create mode 100644 Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/Usings.cs create mode 100644 Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/features/test.feature create mode 100644 Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/steps/test.cs diff --git a/Test/nbomber/Lab.NBomberTest/.gitignore b/Test/nbomber/Lab.NBomberTest/.gitignore new file mode 100644 index 00000000..a33719ce --- /dev/null +++ b/Test/nbomber/Lab.NBomberTest/.gitignore @@ -0,0 +1,365 @@ +### VisualStudio template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +*.feature.cs diff --git a/Test/nbomber/Lab.NBomberTest/Lab.NBomberTest.sln b/Test/nbomber/Lab.NBomberTest/Lab.NBomberTest.sln new file mode 100644 index 00000000..073073aa --- /dev/null +++ b/Test/nbomber/Lab.NBomberTest/Lab.NBomberTest.sln @@ -0,0 +1,21 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{029D0395-F267-4B37-BFD7-D6CC61D0495B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.NBomberTest", "test\Lab.NBomberTest\Lab.NBomberTest.csproj", "{90320F28-4CD0-4A7A-A95E-9F1D642EFF74}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {90320F28-4CD0-4A7A-A95E-9F1D642EFF74} = {029D0395-F267-4B37-BFD7-D6CC61D0495B} + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {90320F28-4CD0-4A7A-A95E-9F1D642EFF74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90320F28-4CD0-4A7A-A95E-9F1D642EFF74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90320F28-4CD0-4A7A-A95E-9F1D642EFF74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90320F28-4CD0-4A7A-A95E-9F1D642EFF74}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/Lab.NBomberTest.csproj b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/Lab.NBomberTest.csproj new file mode 100644 index 00000000..bea799f2 --- /dev/null +++ b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/Lab.NBomberTest.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + diff --git a/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/Usings.cs b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/features/test.feature b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/features/test.feature new file mode 100644 index 00000000..222f40a4 --- /dev/null +++ b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/features/test.feature @@ -0,0 +1,9 @@ +Feature: test +Simple calculator for adding two numbers + + Scenario: 壓力測試 + Given 準備以下 Header 參數 + | Key | Value | + | x-api-key | 123456 | + Given 準備 HttpRequest 'GET', "http://test.k6.io" + Then 執行測試 \ No newline at end of file diff --git a/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/steps/test.cs b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/steps/test.cs new file mode 100644 index 00000000..c4f3c371 --- /dev/null +++ b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/steps/test.cs @@ -0,0 +1,64 @@ +using NBomber.Contracts; +using NBomber.CSharp; +using NBomber.Plugins.Http.CSharp; +using TechTalk.SpecFlow; + +namespace Lab.NBomberTest.steps; + +[Binding] +public class test : Steps +{ + [Given(@"準備以下 Header 參數")] + public void Given準備以下Header參數(Table table) + { + var headers = new Dictionary(); + foreach (var row in table.Rows) + { + if (headers.ContainsKey(row["Key"]) == false) + { + headers.Add("key", row["Value"]); + } + } + + this.ScenarioContext.Set(headers, "headers"); + } + + [Given(@"準備 HttpRequest '(.*)', ""(.*)""")] + public void Given準備HttpRequest(string httpMethod, string url) + { + var httpFactory = HttpClientFactory.Create(); + + this.ScenarioContext.TryGetValue>("headers", out var headers); + var step = Step.Create($"{httpMethod}-{url}", + httpFactory, + async context => + { + var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); + foreach (var header in headers) + { + // httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + // await context.Client.SendAsync(httpRequestMessage); + var response = await context.Client.GetAsync(url, context.CancellationToken); + + return response.IsSuccessStatusCode + ? Response.Ok(statusCode: (int)response.StatusCode) + : Response.Fail(statusCode: (int)response.StatusCode); + }); + this.ScenarioContext.Set(step, "step"); + } + + [Then(@"執行測試")] + public void Then執行測試() + { + var step = this.ScenarioContext.Get("step"); + var scenario = ScenarioBuilder.CreateScenario("demo", step) + .WithLoadSimulations(Simulation.InjectPerSec(1, TimeSpan.FromSeconds(10))) + ; + + var result = NBomberRunner + .RegisterScenarios(scenario) + .Run(); + } +} \ No newline at end of file From 7d214ad4c5e9c84b786e150a6b4b4f987104ad42 Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 21 Oct 2022 22:57:02 +0800 Subject: [PATCH 288/424] add test app --- .../Lab.NBomberTest/Lab.NBomberTest.sln | 9 ++++++ .../Lab.NBomberTest.App.csproj | 15 +++++++++ .../src/Lab.NBomberTest.App/Program.cs | 32 +++++++++++++++++++ .../src/Lab.NBomberTest.App/Program1.cs | 26 +++++++++++++++ .../Lab.NBomberTest/features/test.feature | 2 +- .../test/Lab.NBomberTest/steps/test.cs | 6 ++-- 6 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 Test/nbomber/Lab.NBomberTest/src/Lab.NBomberTest.App/Lab.NBomberTest.App.csproj create mode 100644 Test/nbomber/Lab.NBomberTest/src/Lab.NBomberTest.App/Program.cs create mode 100644 Test/nbomber/Lab.NBomberTest/src/Lab.NBomberTest.App/Program1.cs diff --git a/Test/nbomber/Lab.NBomberTest/Lab.NBomberTest.sln b/Test/nbomber/Lab.NBomberTest/Lab.NBomberTest.sln index 073073aa..fac4faae 100644 --- a/Test/nbomber/Lab.NBomberTest/Lab.NBomberTest.sln +++ b/Test/nbomber/Lab.NBomberTest/Lab.NBomberTest.sln @@ -4,6 +4,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{029D0395-F EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.NBomberTest", "test\Lab.NBomberTest\Lab.NBomberTest.csproj", "{90320F28-4CD0-4A7A-A95E-9F1D642EFF74}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7C968F1D-D31D-4B7E-93AB-0D13DDA9AA34}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.NBomberTest.App", "src\Lab.NBomberTest.App\Lab.NBomberTest.App.csproj", "{7EF73F64-28FF-4F46-8331-431BDA6AC3EF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -11,11 +15,16 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {90320F28-4CD0-4A7A-A95E-9F1D642EFF74} = {029D0395-F267-4B37-BFD7-D6CC61D0495B} + {7EF73F64-28FF-4F46-8331-431BDA6AC3EF} = {7C968F1D-D31D-4B7E-93AB-0D13DDA9AA34} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {90320F28-4CD0-4A7A-A95E-9F1D642EFF74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {90320F28-4CD0-4A7A-A95E-9F1D642EFF74}.Debug|Any CPU.Build.0 = Debug|Any CPU {90320F28-4CD0-4A7A-A95E-9F1D642EFF74}.Release|Any CPU.ActiveCfg = Release|Any CPU {90320F28-4CD0-4A7A-A95E-9F1D642EFF74}.Release|Any CPU.Build.0 = Release|Any CPU + {7EF73F64-28FF-4F46-8331-431BDA6AC3EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EF73F64-28FF-4F46-8331-431BDA6AC3EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EF73F64-28FF-4F46-8331-431BDA6AC3EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EF73F64-28FF-4F46-8331-431BDA6AC3EF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Test/nbomber/Lab.NBomberTest/src/Lab.NBomberTest.App/Lab.NBomberTest.App.csproj b/Test/nbomber/Lab.NBomberTest/src/Lab.NBomberTest.App/Lab.NBomberTest.App.csproj new file mode 100644 index 00000000..91fd3274 --- /dev/null +++ b/Test/nbomber/Lab.NBomberTest/src/Lab.NBomberTest.App/Lab.NBomberTest.App.csproj @@ -0,0 +1,15 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + diff --git a/Test/nbomber/Lab.NBomberTest/src/Lab.NBomberTest.App/Program.cs b/Test/nbomber/Lab.NBomberTest/src/Lab.NBomberTest.App/Program.cs new file mode 100644 index 00000000..5fc4623a --- /dev/null +++ b/Test/nbomber/Lab.NBomberTest/src/Lab.NBomberTest.App/Program.cs @@ -0,0 +1,32 @@ +using NBomber.Contracts; +using NBomber.CSharp; +using NBomber.Plugins.Http.CSharp; + +var httpFactory = ClientFactory.Create( + name: "http_factory", + clientCount: 1, + initClient: (number,context) => Task.FromResult(new HttpClient()) +); + +var step1 = Step.Create("1", + clientFactory: HttpClientFactory.Create("1"), + execute: async context => + { + var response = await context.Client.GetAsync("http://test.k6.io", context.CancellationToken); + + return response.IsSuccessStatusCode + ? Response.Ok(statusCode: (int)response.StatusCode) + : Response.Fail(statusCode: (int)response.StatusCode); + }); +var scenario1 = ScenarioBuilder.CreateScenario("1", step1) + .WithLoadSimulations(Simulation.InjectPerSec(rate: 10, during: TimeSpan.FromSeconds(30))); + + +// var pingPluginConfig = PingPluginConfig.CreateDefault(new[] { "test.k6.io" }); +// var pingPlugin = new PingPlugin(pingPluginConfig); +// +NBomberRunner + .RegisterScenarios(scenario1) + + // .WithWorkerPlugins(pingPlugin) + .Run(); \ No newline at end of file diff --git a/Test/nbomber/Lab.NBomberTest/src/Lab.NBomberTest.App/Program1.cs b/Test/nbomber/Lab.NBomberTest/src/Lab.NBomberTest.App/Program1.cs new file mode 100644 index 00000000..5bf4b6b1 --- /dev/null +++ b/Test/nbomber/Lab.NBomberTest/src/Lab.NBomberTest.App/Program1.cs @@ -0,0 +1,26 @@ +// using NBomber.Contracts; +// using NBomber.CSharp; +// using NBomber.Plugins.Http.CSharp; +// +// var httpFactory = HttpClientFactory.Create(); +// +// var step = Step.Create("fetch_html_page", +// clientFactory: httpFactory, +// execute: async context => +// { +// var response = await context.Client.GetAsync("https://test.k6.io/", context.CancellationToken); +// +// return response.IsSuccessStatusCode +// ? Response.Ok(statusCode: (int)response.StatusCode) +// : Response.Fail(statusCode: (int)response.StatusCode); +// }); +// +// var scenario = ScenarioBuilder +// .CreateScenario("simple_http", step) +// .WithWarmUpDuration(TimeSpan.FromSeconds(5)) +// .WithLoadSimulations(new[] +// { +// Simulation.InjectPerSec(rate: 100, during: TimeSpan.FromSeconds(30)) +// }); +// +// NBomberRunner.RegisterScenarios(scenario).Run(); \ No newline at end of file diff --git a/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/features/test.feature b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/features/test.feature index 222f40a4..38924bf8 100644 --- a/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/features/test.feature +++ b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/features/test.feature @@ -1,6 +1,6 @@ Feature: test -Simple calculator for adding two numbers + @ignore Scenario: 壓力測試 Given 準備以下 Header 參數 | Key | Value | diff --git a/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/steps/test.cs b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/steps/test.cs index c4f3c371..bce9c785 100644 --- a/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/steps/test.cs +++ b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/steps/test.cs @@ -36,7 +36,7 @@ public void Given準備HttpRequest(string httpMethod, string url) var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); foreach (var header in headers) { - // httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value); + httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value); } // await context.Client.SendAsync(httpRequestMessage); @@ -46,6 +46,7 @@ public void Given準備HttpRequest(string httpMethod, string url) ? Response.Ok(statusCode: (int)response.StatusCode) : Response.Fail(statusCode: (int)response.StatusCode); }); + this.ScenarioContext.Set(step, "step"); } @@ -54,9 +55,8 @@ public void Then執行測試() { var step = this.ScenarioContext.Get("step"); var scenario = ScenarioBuilder.CreateScenario("demo", step) - .WithLoadSimulations(Simulation.InjectPerSec(1, TimeSpan.FromSeconds(10))) + .WithLoadSimulations(Simulation.InjectPerSec(50, TimeSpan.FromSeconds(60))) ; - var result = NBomberRunner .RegisterScenarios(scenario) .Run(); From 7c9527f7ff4ab17ceaa4cd3a8beb8a820ea1efca Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 21 Oct 2022 23:12:43 +0800 Subject: [PATCH 289/424] fix bug --- .../Lab.NBomberTest/test/Lab.NBomberTest/features/test.feature | 1 - Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/steps/test.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/features/test.feature b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/features/test.feature index 38924bf8..65af3d29 100644 --- a/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/features/test.feature +++ b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/features/test.feature @@ -1,6 +1,5 @@ Feature: test - @ignore Scenario: 壓力測試 Given 準備以下 Header 參數 | Key | Value | diff --git a/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/steps/test.cs b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/steps/test.cs index bce9c785..18cb6564 100644 --- a/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/steps/test.cs +++ b/Test/nbomber/Lab.NBomberTest/test/Lab.NBomberTest/steps/test.cs @@ -55,7 +55,7 @@ public void Then執行測試() { var step = this.ScenarioContext.Get("step"); var scenario = ScenarioBuilder.CreateScenario("demo", step) - .WithLoadSimulations(Simulation.InjectPerSec(50, TimeSpan.FromSeconds(60))) + .WithLoadSimulations(Simulation.InjectPerSec(30, TimeSpan.FromSeconds(60))) ; var result = NBomberRunner .RegisterScenarios(scenario) From 4751e72663ee5e565902e1bd2157ada4a1035896 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 22 Oct 2022 21:45:54 +0800 Subject: [PATCH 290/424] fix bug --- .../Security/Authentication/BasicAuthenticationExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs index 57f7ed9f..ca76b952 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs @@ -28,8 +28,8 @@ public static AuthenticationBuilder AddBasicAuthentication(this I var scheme = BasicAuthenticationDefaults.AuthenticationScheme; return services.AddAuthentication(o => { - // o.DefaultAuthenticateScheme = scheme; - // o.DefaultChallengeScheme = scheme; + o.DefaultAuthenticateScheme = scheme; + o.DefaultChallengeScheme = scheme; }) .AddBasic(scheme, scheme, configureOptions); } From 4d19726b3d00407f0da75b2d5e3b90e59b5283a7 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 22 Oct 2022 23:05:21 +0800 Subject: [PATCH 291/424] feat: multi authentication scheme --- .../Controllers/DemoController.cs | 6 +++++- .../Program.cs | 2 -- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs index 2bc36483..41790f82 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs @@ -1,3 +1,5 @@ +using AspNetCore.Authentication.ApiKey; +using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -15,7 +17,9 @@ public DemoController(ILogger logger) } [HttpGet] - [Authorize] + // [Authorize] + [Authorize(AuthenticationSchemes = BasicAuthenticationDefaults.AuthenticationScheme)] + [Authorize(AuthenticationSchemes = ApiKeyDefaults.AuthenticationScheme)] public ActionResult Get() { return this.Ok("OK~好"); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs index 9a5baf6f..81f399a1 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs @@ -4,8 +4,6 @@ using System.Text.Unicode; using AspNetCore.Authentication.ApiKey; using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; -using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authorization; -using Microsoft.AspNetCore.Authorization; var builder = WebApplication.CreateBuilder(args); From cc06574f21ce2eb4dcf6e58b6a2ba9cf3dd0a995 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 23 Oct 2022 15:01:56 +0800 Subject: [PATCH 292/424] add multi authentication --- .../BasicAuthenticationDefaults.cs | 2 +- .../BasicAuthenticationExtensions.cs | 19 ++++--- .../BasicAuthenticationHandler.cs | 4 +- .../BasicAuthenticationOptions.cs | 2 +- ...BasicAuthenticationPostConfigureOptions.cs | 2 +- .../DefaultBasicAuthenticationProvider.cs | 9 +++ .../IBasicAuthenticationProvider.cs | 2 +- .../IPermissionAuthorizationProvider.cs | 6 ++ .../Authorization/Permission.cs | 22 ++++++++ .../PermissionAuthorizationHandler.cs | 36 ++++++++++++ ...ionAuthorizationMiddlewareResultHandler.cs | 56 +++++++++++++++++++ .../PermissionAuthorizationPolicyProvider.cs | 51 +++++++++++++++++ .../PermissionAuthorizationProvider.cs | 21 +++++++ .../PermissionAuthorizationRequirement.cs | 8 +++ .../FieldTypeAssistant.cs | 41 ++++++++++++++ ...etCore.Security.BasicAuthentication.csproj | 17 ++++++ .../Controllers/DemoController.cs | 6 +- ...re.Security.BasicAuthenticationSite.csproj | 1 - .../Program.cs | 14 ----- .../ApiKeyProvider.cs | 0 .../BasicAuthenticationProvider.cs | 2 +- .../Controllers/DemoController.cs | 6 +- ...re.Security.MultiAuthenticationSite.csproj | 5 ++ .../Program.cs | 14 ++++- .../Lab.AspNetCore.Security.sln | 6 ++ 25 files changed, 314 insertions(+), 38 deletions(-) rename WebAPI/Security/Lab.AspNetCore.Security/{Lab.AspNetCore.Security.MultiAuthenticationSite/Security => Lab.AspNetCore.Security.BasicAuthentication}/Authentication/BasicAuthenticationDefaults.cs (55%) rename WebAPI/Security/Lab.AspNetCore.Security/{Lab.AspNetCore.Security.MultiAuthenticationSite/Security => Lab.AspNetCore.Security.BasicAuthentication}/Authentication/BasicAuthenticationExtensions.cs (61%) rename WebAPI/Security/Lab.AspNetCore.Security/{Lab.AspNetCore.Security.MultiAuthenticationSite/Security => Lab.AspNetCore.Security.BasicAuthentication}/Authentication/BasicAuthenticationHandler.cs (97%) rename WebAPI/Security/Lab.AspNetCore.Security/{Lab.AspNetCore.Security.MultiAuthenticationSite/Security => Lab.AspNetCore.Security.BasicAuthentication}/Authentication/BasicAuthenticationOptions.cs (65%) rename WebAPI/Security/Lab.AspNetCore.Security/{Lab.AspNetCore.Security.MultiAuthenticationSite/Security => Lab.AspNetCore.Security.BasicAuthentication}/Authentication/BasicAuthenticationPostConfigureOptions.cs (82%) create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/DefaultBasicAuthenticationProvider.cs rename WebAPI/Security/Lab.AspNetCore.Security/{Lab.AspNetCore.Security.MultiAuthenticationSite/Security => Lab.AspNetCore.Security.BasicAuthentication}/Authentication/IBasicAuthenticationProvider.cs (61%) create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/IPermissionAuthorizationProvider.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/Permission.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationHandler.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationPolicyProvider.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationProvider.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationRequirement.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/FieldTypeAssistant.cs create mode 100644 WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Lab.AspNetCore.Security.BasicAuthentication.csproj rename WebAPI/Security/Lab.AspNetCore.Security/{Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication => Lab.AspNetCore.Security.MultiAuthenticationSite}/ApiKeyProvider.cs (100%) rename WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/{Security/Authentication => }/BasicAuthenticationProvider.cs (88%) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationDefaults.cs similarity index 55% rename from WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs rename to WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationDefaults.cs index 0b345dc1..b828bf60 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationDefaults.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationDefaults.cs @@ -1,4 +1,4 @@ -namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; +namespace Lab.AspNetCore.Security.BasicAuthentication; public static class BasicAuthenticationDefaults { diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationExtensions.cs similarity index 61% rename from WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs rename to WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationExtensions.cs index 24f9795a..76711688 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationExtensions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationExtensions.cs @@ -1,19 +1,20 @@ using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; +namespace Lab.AspNetCore.Security.BasicAuthentication; public static class BasicAuthenticationExtensions { - public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, + public static AuthenticationBuilder AddBasicAuthentication(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) - where TAuthService : class, IBasicAuthenticationProvider + where TAuthProvider : class, IBasicAuthenticationProvider { builder.Services .AddSingleton, BasicAuthenticationPostConfigureOptions>(); - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); return builder.AddScheme( authenticationScheme, @@ -21,16 +22,16 @@ public static AuthenticationBuilder AddBasic(this AuthenticationBu configureOptions); } - public static AuthenticationBuilder AddBasicAuthentication(this IServiceCollection services, + public static AuthenticationBuilder AddBasicAuthentication(this IServiceCollection services, Action configureOptions) - where TAuthService : class, IBasicAuthenticationProvider + where TAuthProvider : class, IBasicAuthenticationProvider { var scheme = BasicAuthenticationDefaults.AuthenticationScheme; return services.AddAuthentication(o => { - o.DefaultAuthenticateScheme = scheme; - o.DefaultChallengeScheme = scheme; + o.DefaultScheme = scheme; + // o.DefaultChallengeScheme = scheme; }) - .AddBasic(scheme, scheme, configureOptions); + .AddBasicAuthentication(scheme, scheme, configureOptions); } } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationHandler.cs similarity index 97% rename from WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs rename to WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationHandler.cs index 854a5a70..60ed5b75 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationHandler.cs @@ -4,11 +4,13 @@ using System.Text.Encodings.Web; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; -namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; +namespace Lab.AspNetCore.Security.BasicAuthentication; public class BasicAuthenticationHandler : AuthenticationHandler { diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationOptions.cs similarity index 65% rename from WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs rename to WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationOptions.cs index 86f5bd4e..1056f1eb 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationOptions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationOptions.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Authentication; -namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; +namespace Lab.AspNetCore.Security.BasicAuthentication; public class BasicAuthenticationOptions : AuthenticationSchemeOptions { diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationPostConfigureOptions.cs similarity index 82% rename from WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs rename to WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationPostConfigureOptions.cs index 175850e7..689c95df 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationPostConfigureOptions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationPostConfigureOptions.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Options; -namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; +namespace Lab.AspNetCore.Security.BasicAuthentication; public class BasicAuthenticationPostConfigureOptions : IPostConfigureOptions { diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/DefaultBasicAuthenticationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/DefaultBasicAuthenticationProvider.cs new file mode 100644 index 00000000..4d27578d --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/DefaultBasicAuthenticationProvider.cs @@ -0,0 +1,9 @@ +namespace Lab.AspNetCore.Security.BasicAuthentication; + +public class DefaultBasicAuthenticationProvider : IBasicAuthenticationProvider +{ + public Task IsValidateAsync(string user, string password, CancellationToken cancel = default) + { + return Task.FromResult(true); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/IBasicAuthenticationProvider.cs similarity index 61% rename from WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs rename to WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/IBasicAuthenticationProvider.cs index 54602809..1e5b3c13 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/IBasicAuthenticationProvider.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/IBasicAuthenticationProvider.cs @@ -1,4 +1,4 @@ -namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; +namespace Lab.AspNetCore.Security.BasicAuthentication; public interface IBasicAuthenticationProvider { diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/IPermissionAuthorizationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/IPermissionAuthorizationProvider.cs new file mode 100644 index 00000000..35ede6e1 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/IPermissionAuthorizationProvider.cs @@ -0,0 +1,6 @@ +namespace Lab.AspNetCore.Security.BasicAuthentication; + +public interface IPermissionAuthorizationProvider +{ + IEnumerable GetPermissions(string userId); +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/Permission.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/Permission.cs new file mode 100644 index 00000000..fbdf88b6 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/Permission.cs @@ -0,0 +1,22 @@ +namespace Lab.AspNetCore.Security.BasicAuthentication; + +public class Permission +{ + public class Operation + { + public const string Write = $"{nameof(Permission)}.{nameof(Operation)}:{nameof(Write)}"; + public const string Read = $"{nameof(Permission)}.{nameof(Operation)}:{nameof(Read)}"; + + private static readonly Lazy> s_values + = new(() => + { + return FieldTypeAssistant.GetStaticFieldName() + .ToDictionary(p => p.Key, + p => p.Value, + StringComparer.InvariantCultureIgnoreCase); + }); + + public static Dictionary GetValues() + => s_values.Value; + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationHandler.cs new file mode 100644 index 00000000..5647342f --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationHandler.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Lab.AspNetCore.Security.BasicAuthentication; + +public class PermissionAuthorizationHandler : AuthorizationHandler +{ + private readonly IPermissionAuthorizationProvider _authorizationProvider; + + public PermissionAuthorizationHandler(IPermissionAuthorizationProvider authorizationProvider) + { + this._authorizationProvider = authorizationProvider; + } + + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, + PermissionAuthorizationRequirement requirement) + { + if (context.User.Identity.IsAuthenticated == false) + { + context.Fail(new AuthorizationFailureReason(this, $"目前請求沒有通過驗證")); + return; + } + + var userId = context.User.Identity.Name; + var permissions = this._authorizationProvider.GetPermissions(userId); + if (permissions.Any(p => p.StartsWith(requirement.PolicyName, StringComparison.InvariantCultureIgnoreCase)) == + false) + { + context.Fail(new AuthorizationFailureReason(this, $"用戶 '{userId}',沒有授權 '{requirement.PolicyName}'")); + } + + if (context.HasFailed == false) + { + context.Succeed(requirement); + } + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs new file mode 100644 index 00000000..3e0d3d12 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationMiddlewareResultHandler.cs @@ -0,0 +1,56 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization.Policy; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Lab.AspNetCore.Security.BasicAuthentication; + +public class PermissionAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler +{ + private readonly ILogger _logger; + private readonly JsonSerializerOptions _jsonSerializerOptions; + private readonly AuthorizationMiddlewareResultHandler _defaultHandler = new(); + + public PermissionAuthorizationMiddlewareResultHandler( + ILogger logger, + JsonSerializerOptions jsonSerializerOptions) + { + this._logger = logger; + this._jsonSerializerOptions = jsonSerializerOptions; + } + + public async Task HandleAsync( + RequestDelegate next, + HttpContext context, + AuthorizationPolicy policy, + PolicyAuthorizationResult authorizeResult) + { + var permissionAuthorizationRequirements = policy.Requirements.OfType(); + + if (authorizeResult.Forbidden + && permissionAuthorizationRequirements.Any()) + { + context.Response.StatusCode = 403; + this._logger.LogInformation("{AuthorizationFailureResults}", new + { + ErrorCode = "Invalid Authorization", + ErrorMessages = authorizeResult.AuthorizationFailure.FailureReasons + }); + + // 回傳前端模糊訊息 + await context.Response.WriteAsJsonAsync(new + { + ErrorCode = "Invalid Authorization", + ErrorMessages = new[] { "Please contact your administrator" } + + // ErrorMessages = authorizeResult.AuthorizationFailure.FailureReasons + }, this._jsonSerializerOptions); + return; + } + + await this._defaultHandler.HandleAsync(next, context, policy, authorizeResult); + + // await next.Invoke(context); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationPolicyProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationPolicyProvider.cs new file mode 100644 index 00000000..d169dd2b --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationPolicyProvider.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Options; + +namespace Lab.AspNetCore.Security.BasicAuthentication; + +internal class PermissionAuthorizationPolicyProvider : IAuthorizationPolicyProvider +{ + public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } + + public PermissionAuthorizationPolicyProvider(IOptions options) + { + // ASP.NET Core only uses one authorization policy provider, so if the custom implementation + // doesn't handle all policies (including default policies, etc.) it should fall back to an + // alternate provider. + // + // In this sample, a default authorization policy provider (constructed with options from the + // dependency injection container) is used if this custom provider isn't able to handle a given + // policy name. + // + // If a custom policy provider is able to handle all expected policy names then, of course, this + // fallback pattern is unnecessary. + this.FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options); + } + + public Task GetDefaultPolicyAsync() => this.FallbackPolicyProvider.GetDefaultPolicyAsync(); + + public Task GetFallbackPolicyAsync() => this.FallbackPolicyProvider.GetFallbackPolicyAsync(); + + // Policies are looked up by string name, so expect 'parameters' (like age) + // to be embedded in the policy names. This is abstracted away from developers + // by the more strongly-typed attributes derived from AuthorizeAttribute + // (like [MinimumAgeAuthorize] in this sample) + public Task GetPolicyAsync(string policyName) + { + var operationValues = Permission.Operation.GetValues(); + if (operationValues.Any(p => p.Key.StartsWith(policyName, StringComparison.InvariantCultureIgnoreCase))) + { + var policy = new AuthorizationPolicyBuilder(); + policy.AddRequirements(new PermissionAuthorizationRequirement + { + PolicyName = policyName + }); + return Task.FromResult(policy.Build()); + } + + // If the policy name doesn't match the format expected by this policy provider, + // try the fallback provider. If no fallback provider is used, this would return + // Task.FromResult(null) instead. + return this.FallbackPolicyProvider.GetPolicyAsync(policyName); + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationProvider.cs new file mode 100644 index 00000000..9dceacc7 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationProvider.cs @@ -0,0 +1,21 @@ +namespace Lab.AspNetCore.Security.BasicAuthentication; + +public class PermissionAuthorizationProvider : IPermissionAuthorizationProvider +{ + private readonly Dictionary> _clientPermissions = + new(StringComparer.InvariantCultureIgnoreCase) + { + { "yao", new[] { Permission.Operation.Read, Permission.Operation.Write } }, + { "jojo", new[] { Permission.Operation.Read} } + }; + + public IEnumerable GetPermissions(string userId) + { + if (this._clientPermissions.TryGetValue(userId, out var result) == false) + { + result = new List(); + } + + return result; + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationRequirement.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationRequirement.cs new file mode 100644 index 00000000..910da0ed --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authorization/PermissionAuthorizationRequirement.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Lab.AspNetCore.Security.BasicAuthentication; + +public class PermissionAuthorizationRequirement : IAuthorizationRequirement +{ + public string PolicyName { get; init; } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/FieldTypeAssistant.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/FieldTypeAssistant.cs new file mode 100644 index 00000000..5b489664 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/FieldTypeAssistant.cs @@ -0,0 +1,41 @@ +using System.Collections.Concurrent; +using System.Reflection; + +namespace Lab.AspNetCore.Security.BasicAuthentication; + +public class FieldTypeAssistant +{ + private static ConcurrentDictionary> s_fieldTypeList = new(); + + public static Dictionary GetEnumValues() + { + return Enum.GetValues(typeof(T)) + .Cast() + .ToDictionary(p => p.ToString(), p => p); + } + + public static Dictionary GetStaticFieldName() + { + var type = typeof(T); + var fieldTypeList = s_fieldTypeList; + if (fieldTypeList.TryGetValue(type, out var results)) + { + return results; + } + + var bindingFlags = BindingFlags.Public + | BindingFlags.Static + ; + results = new Dictionary(); + var fieldInfosInfos = type.GetFields(bindingFlags); + foreach (var fieldInfo in fieldInfosInfos) + { + var value = fieldInfo.GetValue(null); + + results.Add(value.ToString(), fieldInfo.FieldType); + } + + fieldTypeList.TryAdd(type, results); + return results; + } +} \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Lab.AspNetCore.Security.BasicAuthentication.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Lab.AspNetCore.Security.BasicAuthentication.csproj new file mode 100644 index 00000000..e4190c84 --- /dev/null +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Lab.AspNetCore.Security.BasicAuthentication.csproj @@ -0,0 +1,17 @@ + + + net6.0 + enable + enable + + + + + + + + + + + + diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs index 41790f82..2bc36483 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Controllers/DemoController.cs @@ -1,5 +1,3 @@ -using AspNetCore.Authentication.ApiKey; -using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -17,9 +15,7 @@ public DemoController(ILogger logger) } [HttpGet] - // [Authorize] - [Authorize(AuthenticationSchemes = BasicAuthenticationDefaults.AuthenticationScheme)] - [Authorize(AuthenticationSchemes = ApiKeyDefaults.AuthenticationScheme)] + [Authorize] public ActionResult Get() { return this.Ok("OK~好"); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj index bcc5f904..58b7e218 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Lab.AspNetCore.Security.BasicAuthenticationSite.csproj @@ -7,7 +7,6 @@ - diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs index 81f399a1..c3b89d50 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Program.cs @@ -2,7 +2,6 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Unicode; -using AspNetCore.Authentication.ApiKey; using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; var builder = WebApplication.CreateBuilder(args); @@ -16,19 +15,6 @@ builder.Services.AddSwaggerGen(); builder.Logging.AddConsole(); -// builder.Services.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme) -// .AddScheme(BasicAuthenticationDefaults.AuthenticationScheme, -// p => new BasicAuthenticationOptions() -// { -// Realm = "Basic Authentication" -// }); - -builder.Services.AddAuthentication(ApiKeyDefaults.AuthenticationScheme) - .AddApiKeyInHeaderOrQueryParams(options => - { - options.Realm = "Sample Web API"; - options.KeyName = "X-API-KEY"; - }); builder.Services.AddBasicAuthentication(o => o.Realm = "Basic Authentication"); builder.Services.AddSingleton(p=>new JsonSerializerOptions { diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/ApiKeyProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/ApiKeyProvider.cs similarity index 100% rename from WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/ApiKeyProvider.cs rename to WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/ApiKeyProvider.cs diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/BasicAuthenticationProvider.cs similarity index 88% rename from WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs rename to WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/BasicAuthenticationProvider.cs index d90ec34b..8940a26b 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Security/Authentication/BasicAuthenticationProvider.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/BasicAuthenticationProvider.cs @@ -1,4 +1,4 @@ -namespace Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; +namespace Lab.AspNetCore.Security.BasicAuthentication; public class BasicAuthenticationProvider : IBasicAuthenticationProvider { diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Controllers/DemoController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Controllers/DemoController.cs index 421c0caa..a21189ae 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Controllers/DemoController.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Controllers/DemoController.cs @@ -1,3 +1,5 @@ +using AspNetCore.Authentication.ApiKey; +using Lab.AspNetCore.Security.BasicAuthentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -14,7 +16,9 @@ public DemoController(ILogger logger) _logger = logger; } - [Authorize] + [Authorize(AuthenticationSchemes = BasicAuthenticationDefaults.AuthenticationScheme)] + [Authorize(AuthenticationSchemes = ApiKeyDefaults.AuthenticationScheme)] + public ActionResult Get() { return this.Ok("OK~好"); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Lab.AspNetCore.Security.MultiAuthenticationSite.csproj b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Lab.AspNetCore.Security.MultiAuthenticationSite.csproj index b9baca3e..7afd3fbe 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Lab.AspNetCore.Security.MultiAuthenticationSite.csproj +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Lab.AspNetCore.Security.MultiAuthenticationSite.csproj @@ -8,6 +8,11 @@ + + + + + diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs index 621214f9..0a45ca28 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs @@ -1,4 +1,6 @@ -using Lab.AspNetCore.Security.MultiAuthenticationSite.Security.Authentication; +using AspNetCore.Authentication.ApiKey; +using Lab.AspNetCore.Security.BasicAuthentication; +using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; var builder = WebApplication.CreateBuilder(args); @@ -9,7 +11,15 @@ // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -builder.Services.AddBasicAuthentication(o => o.Realm = "Basic Authentication"); +builder.Services.AddAuthentication(ApiKeyDefaults.AuthenticationScheme) + // .AddApiKeyInHeaderOrQueryParams(options => + // { + // options.Realm = "Sample Web API"; + // options.KeyName = "X-API-KEY"; + // }) + .AddBasicAuthentication(BasicAuthenticationDefaults.AuthenticationScheme, + null, + o => o.Realm = "Basic Authentication"); var app = builder.Build(); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln index 8f181734..d4e84f3d 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.sln @@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AspNetCore.Security.Bas EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AspNetCore.Security.MultiAuthenticationSite", "Lab.AspNetCore.Security.MultiAuthenticationSite\Lab.AspNetCore.Security.MultiAuthenticationSite.csproj", "{FB32C9D2-A7C7-4199-9477-AA5EEA7D818F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.AspNetCore.Security.BasicAuthentication", "Lab.AspNetCore.Security.BasicAuthentication\Lab.AspNetCore.Security.BasicAuthentication.csproj", "{CE3A118F-BBE8-475A-86BF-A37ED4C1E1F8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,5 +32,9 @@ Global {FB32C9D2-A7C7-4199-9477-AA5EEA7D818F}.Debug|Any CPU.Build.0 = Debug|Any CPU {FB32C9D2-A7C7-4199-9477-AA5EEA7D818F}.Release|Any CPU.ActiveCfg = Release|Any CPU {FB32C9D2-A7C7-4199-9477-AA5EEA7D818F}.Release|Any CPU.Build.0 = Release|Any CPU + {CE3A118F-BBE8-475A-86BF-A37ED4C1E1F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE3A118F-BBE8-475A-86BF-A37ED4C1E1F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE3A118F-BBE8-475A-86BF-A37ED4C1E1F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE3A118F-BBE8-475A-86BF-A37ED4C1E1F8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 5a7ba53cf3bd97e97ad0ffdf0c580d85885152b4 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 23 Oct 2022 16:13:41 +0800 Subject: [PATCH 293/424] fix --- .../BasicAuthenticationHandler.cs | 18 ++++----- .../BasicAuthenticationHandler.cs | 2 +- .../Controllers/DemoController.cs | 5 ++- .../Program.cs | 37 +++++++++++++++---- 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationHandler.cs index 60ed5b75..1337b303 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationHandler.cs @@ -88,18 +88,18 @@ protected override async Task HandleChallengeAsync(AuthenticationProperties prop Code = "InvalidAuthentication", Message = this._failReason }); - + this.Response.StatusCode = 401; this.Response.HttpContext.Features.Get().ReasonPhrase = this._failReason; - this.Response.Headers["WWW-Authenticate"] = $"Basic realm=\"{this.Options.Realm}\", charset=\"UTF-8\""; - + this.Response.Headers[HeaderNames.WWWAuthenticate] = $"Basic realm=\"{this.Options.Realm}\", charset=\"UTF-8\""; + // 響應粗糙的內容,這不是標準的 Basic Authentication 失敗的回傳,僅是為了示意 - this.Response.WriteAsJsonAsync(new - { - Code = "InvalidAuthentication", - Message = "Please contact your administrator" - }); - await Task.CompletedTask; + // this.Response.WriteAsJsonAsync(new + // { + // Code = "InvalidAuthentication", + // Message = "Please contact your administrator" + // }); + await base.HandleChallengeAsync(properties); } private AuthenticateResult SignIn(string user) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs index 58033771..2da5bc36 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthenticationSite/Security/Authentication/BasicAuthenticationHandler.cs @@ -89,7 +89,7 @@ protected override async Task HandleChallengeAsync(AuthenticationProperties prop this.Response.StatusCode = 401; this.Response.HttpContext.Features.Get().ReasonPhrase = this._failReason; - this.Response.Headers["WWW-Authenticate"] = $"Basic realm=\"{this.Options.Realm}\", charset=\"UTF-8\""; + this.Response.Headers[HeaderNames.WWWAuthenticate] = $"Basic realm=\"{this.Options.Realm}\", charset=\"UTF-8\""; // 響應粗糙的內容,這不是標準的 Basic Authentication 失敗的回傳,僅是為了示意 this.Response.WriteAsJsonAsync(new diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Controllers/DemoController.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Controllers/DemoController.cs index a21189ae..ef88c969 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Controllers/DemoController.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Controllers/DemoController.cs @@ -16,9 +16,10 @@ public DemoController(ILogger logger) _logger = logger; } - [Authorize(AuthenticationSchemes = BasicAuthenticationDefaults.AuthenticationScheme)] - [Authorize(AuthenticationSchemes = ApiKeyDefaults.AuthenticationScheme)] + // [Authorize(AuthenticationSchemes = BasicAuthenticationDefaults.AuthenticationScheme)] + // [Authorize(AuthenticationSchemes = ApiKeyDefaults.AuthenticationScheme)] + [Authorize] public ActionResult Get() { return this.Ok("OK~好"); diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs index 0a45ca28..7acd31a4 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs @@ -1,6 +1,8 @@ using AspNetCore.Authentication.ApiKey; using Lab.AspNetCore.Security.BasicAuthentication; using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.Net.Http.Headers; var builder = WebApplication.CreateBuilder(args); @@ -11,15 +13,34 @@ // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -builder.Services.AddAuthentication(ApiKeyDefaults.AuthenticationScheme) - // .AddApiKeyInHeaderOrQueryParams(options => - // { - // options.Realm = "Sample Web API"; - // options.KeyName = "X-API-KEY"; - // }) +builder.Services.AddAuthentication(p => + { + p.DefaultScheme = "MultiAuthSchemes"; + p.DefaultChallengeScheme = "MultiAuthSchemes"; + }) + .AddApiKeyInHeaderOrQueryParams(p => + { + p.Realm = "Sample Web API"; + p.KeyName = "X-API-KEY"; + }) .AddBasicAuthentication(BasicAuthenticationDefaults.AuthenticationScheme, - null, - o => o.Realm = "Basic Authentication"); + BasicAuthenticationDefaults.AuthenticationScheme, + p => p.Realm = "Basic Authentication") + .AddPolicyScheme("MultiAuthSchemes", ApiKeyDefaults.AuthenticationScheme, p => + { + p.ForwardDefaultSelector = context => + { + string authorization = context.Request.Headers[HeaderNames.Authorization]; + if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Basic ")) + { + return BasicAuthenticationDefaults.AuthenticationScheme; + } + + return ApiKeyDefaults.AuthenticationScheme; + }; + }); + ; +// private string Challenge => $"{GetWwwAuthenticateSchemeName()} realm=\"{Options.Realm}\", charset=\"UTF-8\", in=\"{GetWwwAuthenticateInParameter()}\", key_name=\"{Options.KeyName}\""; var app = builder.Build(); From 72c9265374d95085f1ac43db699a5ca133b6a186 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 23 Oct 2022 17:22:19 +0800 Subject: [PATCH 294/424] refactor --- .../Program.cs | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs index 7acd31a4..8f31ca9e 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs @@ -31,15 +31,46 @@ p.ForwardDefaultSelector = context => { string authorization = context.Request.Headers[HeaderNames.Authorization]; - if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Basic ")) + if (string.IsNullOrEmpty(authorization) == false && + authorization.StartsWith($"{BasicAuthenticationDefaults.AuthenticationScheme} ", + StringComparison.InvariantCultureIgnoreCase)) { return BasicAuthenticationDefaults.AuthenticationScheme; } return ApiKeyDefaults.AuthenticationScheme; }; - }); + }) ; + +// builder.Services.AddAuthentication(p => +// { +// p.DefaultScheme = "MultiAuthSchemes"; +// p.DefaultChallengeScheme = "MultiAuthSchemes"; +// }) +// .AddApiKeyInHeaderOrQueryParams(p => +// { +// p.Realm = "Sample Web API"; +// p.KeyName = "X-API-KEY"; +// }) +// .AddBasicAuthentication(BasicAuthenticationDefaults.AuthenticationScheme, +// BasicAuthenticationDefaults.AuthenticationScheme, +// p => p.Realm = "Basic Authentication") +// .AddPolicyScheme("MultiAuthSchemes", ApiKeyDefaults.AuthenticationScheme, p => +// { +// p.ForwardDefaultSelector = context => +// { +// string authorization = context.Request.Headers[HeaderNames.Authorization]; +// if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Basic ")) +// { +// return BasicAuthenticationDefaults.AuthenticationScheme; +// } +// +// return ApiKeyDefaults.AuthenticationScheme; +// }; +// }) +// ; + // private string Challenge => $"{GetWwwAuthenticateSchemeName()} realm=\"{Options.Realm}\", charset=\"UTF-8\", in=\"{GetWwwAuthenticateInParameter()}\", key_name=\"{Options.KeyName}\""; var app = builder.Build(); From 3cb63d04450752a4f0604023e663702271e74cdd Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 23 Oct 2022 17:36:25 +0800 Subject: [PATCH 295/424] refactor --- .../BasicAuthenticationExtensions.cs | 16 ++++++---------- .../Program.cs | 13 ++++++++----- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationExtensions.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationExtensions.cs index 76711688..d075a779 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationExtensions.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.BasicAuthentication/Authentication/BasicAuthenticationExtensions.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Authentication; +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -21,17 +22,12 @@ public static AuthenticationBuilder AddBasicAuthentication(this A displayName, configureOptions); } - - public static AuthenticationBuilder AddBasicAuthentication(this IServiceCollection services, + public static AuthenticationBuilder AddBasicAuthentication(this AuthenticationBuilder builder, + string authenticationScheme, Action configureOptions) where TAuthProvider : class, IBasicAuthenticationProvider { - var scheme = BasicAuthenticationDefaults.AuthenticationScheme; - return services.AddAuthentication(o => - { - o.DefaultScheme = scheme; - // o.DefaultChallengeScheme = scheme; - }) - .AddBasicAuthentication(scheme, scheme, configureOptions); + return AddBasicAuthentication(builder, authenticationScheme, null, configureOptions); } + } \ No newline at end of file diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs index 8f31ca9e..6c2f3b30 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs @@ -13,10 +13,11 @@ // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +var multiScheme = "MultiAuthSchemes"; builder.Services.AddAuthentication(p => { - p.DefaultScheme = "MultiAuthSchemes"; - p.DefaultChallengeScheme = "MultiAuthSchemes"; + p.DefaultScheme = multiScheme; + p.DefaultChallengeScheme = multiScheme; }) .AddApiKeyInHeaderOrQueryParams(p => { @@ -24,9 +25,11 @@ p.KeyName = "X-API-KEY"; }) .AddBasicAuthentication(BasicAuthenticationDefaults.AuthenticationScheme, - BasicAuthenticationDefaults.AuthenticationScheme, - p => p.Realm = "Basic Authentication") - .AddPolicyScheme("MultiAuthSchemes", ApiKeyDefaults.AuthenticationScheme, p => + p => + { + p.Realm = "Basic Authentication"; + }) + .AddPolicyScheme(multiScheme, ApiKeyDefaults.AuthenticationScheme, p => { p.ForwardDefaultSelector = context => { From b553f99ae69f9aeb61212a3e24302c7f4600f92f Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 23 Oct 2022 17:38:25 +0800 Subject: [PATCH 296/424] refactor --- .../Program.cs | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs index 6c2f3b30..fd836b47 100644 --- a/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs +++ b/WebAPI/Security/Lab.AspNetCore.Security/Lab.AspNetCore.Security.MultiAuthenticationSite/Program.cs @@ -1,7 +1,6 @@ using AspNetCore.Authentication.ApiKey; using Lab.AspNetCore.Security.BasicAuthentication; using Lab.AspNetCore.Security.BasicAuthenticationSite.Security.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.Net.Http.Headers; var builder = WebApplication.CreateBuilder(args); @@ -46,36 +45,6 @@ }) ; -// builder.Services.AddAuthentication(p => -// { -// p.DefaultScheme = "MultiAuthSchemes"; -// p.DefaultChallengeScheme = "MultiAuthSchemes"; -// }) -// .AddApiKeyInHeaderOrQueryParams(p => -// { -// p.Realm = "Sample Web API"; -// p.KeyName = "X-API-KEY"; -// }) -// .AddBasicAuthentication(BasicAuthenticationDefaults.AuthenticationScheme, -// BasicAuthenticationDefaults.AuthenticationScheme, -// p => p.Realm = "Basic Authentication") -// .AddPolicyScheme("MultiAuthSchemes", ApiKeyDefaults.AuthenticationScheme, p => -// { -// p.ForwardDefaultSelector = context => -// { -// string authorization = context.Request.Headers[HeaderNames.Authorization]; -// if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Basic ")) -// { -// return BasicAuthenticationDefaults.AuthenticationScheme; -// } -// -// return ApiKeyDefaults.AuthenticationScheme; -// }; -// }) -// ; - -// private string Challenge => $"{GetWwwAuthenticateSchemeName()} realm=\"{Options.Realm}\", charset=\"UTF-8\", in=\"{GetWwwAuthenticateInParameter()}\", key_name=\"{Options.KeyName}\""; - var app = builder.Build(); // Configure the HTTP request pipeline. From 1197f45901990597689325e18b88e81f56cb85d8 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 25 Oct 2022 02:09:19 +0800 Subject: [PATCH 297/424] add redis sample --- .../Lab.Redis.Client/src/Lab.Redis.Client.sln | 16 +++ .../src/TestProject1/RedisConnection.cs | 119 ++++++++++++++++++ .../src/TestProject1/TestProject1.csproj | 19 +++ .../src/TestProject1/UnitTest1.cs | 95 ++++++++++++++ .../src/TestProject1/Usings.cs | 1 + 5 files changed, 250 insertions(+) create mode 100644 Redis/Lab.Redis.Client/src/Lab.Redis.Client.sln create mode 100644 Redis/Lab.Redis.Client/src/TestProject1/RedisConnection.cs create mode 100644 Redis/Lab.Redis.Client/src/TestProject1/TestProject1.csproj create mode 100644 Redis/Lab.Redis.Client/src/TestProject1/UnitTest1.cs create mode 100644 Redis/Lab.Redis.Client/src/TestProject1/Usings.cs diff --git a/Redis/Lab.Redis.Client/src/Lab.Redis.Client.sln b/Redis/Lab.Redis.Client/src/Lab.Redis.Client.sln new file mode 100644 index 00000000..e4e04d4b --- /dev/null +++ b/Redis/Lab.Redis.Client/src/Lab.Redis.Client.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject1", "TestProject1\TestProject1.csproj", "{8339C08B-EA55-4705-BA65-BA93ED6195DB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8339C08B-EA55-4705-BA65-BA93ED6195DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8339C08B-EA55-4705-BA65-BA93ED6195DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8339C08B-EA55-4705-BA65-BA93ED6195DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8339C08B-EA55-4705-BA65-BA93ED6195DB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Redis/Lab.Redis.Client/src/TestProject1/RedisConnection.cs b/Redis/Lab.Redis.Client/src/TestProject1/RedisConnection.cs new file mode 100644 index 00000000..5d23f111 --- /dev/null +++ b/Redis/Lab.Redis.Client/src/TestProject1/RedisConnection.cs @@ -0,0 +1,119 @@ +using System.Runtime.Serialization.Formatters.Binary; +using System.Text.Json; +using StackExchange.Redis; + +namespace TestProject1; + +public class RedisClient +{ + private static string _setting; + + static RedisClient() + { + s_connectionLazy = new Lazy(() => + { + if (string.IsNullOrWhiteSpace(_setting)) + { + return ConnectionMultiplexer.Connect("localhost"); + } + + return ConnectionMultiplexer.Connect(_setting); + }); + } + + private static readonly Lazy s_connectionLazy; + + public ConnectionMultiplexer Instance => s_connectionLazy.Value; + + public IDatabase Database => this.Instance.GetDatabase(); + + public static void Init(string setting) + { + _setting = setting; + } + + public T Get(string key) + { + if (Exists(key)) + { + return Deserialize(this.Database.StringGet(key)); + } + + throw new Exception(); + } + public T Get2(string key) + { + if (Exists(key)) + { + return JsonSerializer.Deserialize(this.Database.StringGet(key)); + } + + throw new Exception(); + } + public bool Exists(string key) + { + return this.Database.KeyExists(key); //可直接調用 + } + + public void Set(string key, T value, TimeSpan? expiry = default(TimeSpan?), When when = When.Always, + CommandFlags flags = CommandFlags.None) + { + this.Database.StringSet(key, Serialize(value), expiry, when, flags); + } + public void Set2(string key, T value, TimeSpan? expiry = default(TimeSpan?), When when = When.Always, + CommandFlags flags = CommandFlags.None) + { + this.Database.StringSet(key,JsonSerializer.Serialize(value) , expiry, when, flags); + } + + private static byte[] Serialize(object instance) + { + if (instance == null) + { + return null; + } + + var formatter = new BinaryFormatter(); + using var outputStream = new MemoryStream(); + formatter.Serialize(outputStream, instance); + return outputStream.ToArray(); + } + + private static T Deserialize(byte[] srcStream) + { + if (srcStream == null) + { + return default(T); + } + + var formatter = new BinaryFormatter(); + using var outputStream = new MemoryStream(srcStream); + return (T)formatter.Deserialize(outputStream); + } +} + +public class RedisConnection2 +{ + private static readonly Lazy s_redisConnectionLazy = new(() => new RedisConnection2()); + + private static string _setting; + + public readonly ConnectionMultiplexer ConnectionMultiplexer; + + public static RedisConnection2 Instance => s_redisConnectionLazy.Value; + + private RedisConnection2() + { + if (string.IsNullOrWhiteSpace(_setting)) + { + _setting = "localhost"; + } + + this.ConnectionMultiplexer = ConnectionMultiplexer.Connect(_setting); + } + + public static void Init(string setting) + { + _setting = setting; + } +} \ No newline at end of file diff --git a/Redis/Lab.Redis.Client/src/TestProject1/TestProject1.csproj b/Redis/Lab.Redis.Client/src/TestProject1/TestProject1.csproj new file mode 100644 index 00000000..60cd2706 --- /dev/null +++ b/Redis/Lab.Redis.Client/src/TestProject1/TestProject1.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + diff --git a/Redis/Lab.Redis.Client/src/TestProject1/UnitTest1.cs b/Redis/Lab.Redis.Client/src/TestProject1/UnitTest1.cs new file mode 100644 index 00000000..b73bf085 --- /dev/null +++ b/Redis/Lab.Redis.Client/src/TestProject1/UnitTest1.cs @@ -0,0 +1,95 @@ +using StackExchange.Redis; + +namespace TestProject1; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void StringSet() + { + var db = new RedisClient().Database; + + // Set + string value = "Hello World"; + var key = "Test"; + db.StringSet(key, value); + + // set timeout 5min + db.StringSet(key, value, TimeSpan.FromSeconds(60)); + + // Get + var test = db.StringGet(key); + } + + [TestMethod] + public void Sets() + { + var db = new RedisClient().Database; + db.StringIncrement("visitCount"); + + // Set + string value = "Hello World"; + db.SetAdd("event", "001"); + db.SetAdd("event", "002"); + db.SetAdd("event", "003"); + var hashGetAll = db.HashGetAll("event"); + + // Get + var result = db.SetScan("event", "00*"); + result.ToList().ForEach(x => Console.WriteLine(x)); + + //然後是刪除的部份 + db.SetRemove("event", "002"); + } + + [TestMethod] + public void Hashset() + { + var db = new RedisClient().Database; + + db.HashSet("employee", new HashEntry[] + { + new("1", "anson"), + new("2", "kin"), + new("3", "jacky"), + }); + + //取出全部 + db.HashGetAll("employee").ToList().ForEach(x => Console.WriteLine(x)); + + //取出某筆 + db.HashGet("employee", 2); + + //刪除某筆 + db.HashDelete("employee", 2); + + //修改資料 + db.HashSet("employee", 3, "anson"); + } + + [TestMethod] + public void SetDTO() + { + var connection = new RedisClient(); + var db = connection.Database; + + var model = new MyClass + { + Name = "小章", + Age = 29 + }; + connection.Set("dto1", model); + connection.Set2("dto2", model); + + // var myClass = connection.Get("dto"); + } + + [Serializable] + class MyClass + { + public string Name { get; set; } + + public int Age { get; set; } + } +} \ No newline at end of file diff --git a/Redis/Lab.Redis.Client/src/TestProject1/Usings.cs b/Redis/Lab.Redis.Client/src/TestProject1/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/Redis/Lab.Redis.Client/src/TestProject1/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file From a363929fd81561cda950d178a90ea6d738df48d5 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 30 Oct 2022 11:59:00 +0800 Subject: [PATCH 298/424] =?UTF-8?q?=E5=A2=9E=E5=8A=A0Connection=20Pool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Redis/Lab.Redis.Client/Taskfile.yml | 11 +++++++ Redis/Lab.Redis.Client/docker-compose.yml | 17 ++++++++++ .../Lab.Redis.Client/src/Lab.Redis.Client.sln | 12 +++++++ .../Lab.Redis.Client/Lab.Redis.Client.csproj | 13 ++++++++ .../src/Lab.Redis.Client/RedisConnection.cs | 26 +++++++++++++++ .../RedisDatabaseExtensions.cs | 30 +++++++++++++++++ .../TestProject1/RedisConnectionUnitTest.cs | 32 +++++++++++++++++++ .../src/TestProject1/TestProject1.csproj | 4 +++ 8 files changed, 145 insertions(+) create mode 100644 Redis/Lab.Redis.Client/Taskfile.yml create mode 100644 Redis/Lab.Redis.Client/docker-compose.yml create mode 100644 Redis/Lab.Redis.Client/src/Lab.Redis.Client/Lab.Redis.Client.csproj create mode 100644 Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisConnection.cs create mode 100644 Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisDatabaseExtensions.cs create mode 100644 Redis/Lab.Redis.Client/src/TestProject1/RedisConnectionUnitTest.cs diff --git a/Redis/Lab.Redis.Client/Taskfile.yml b/Redis/Lab.Redis.Client/Taskfile.yml new file mode 100644 index 00000000..0a12096f --- /dev/null +++ b/Redis/Lab.Redis.Client/Taskfile.yml @@ -0,0 +1,11 @@ +# Taskfile.yml + +version: "3" + +dotenv: [ "secrets/secrets.env" ] + +tasks: + dev-stop: + desc: Stop development environment + cmds: + - docker-compose down \ No newline at end of file diff --git a/Redis/Lab.Redis.Client/docker-compose.yml b/Redis/Lab.Redis.Client/docker-compose.yml new file mode 100644 index 00000000..af57f699 --- /dev/null +++ b/Redis/Lab.Redis.Client/docker-compose.yml @@ -0,0 +1,17 @@ +version: "3.8" + +services: + redis: + image: redis + ports: + - 6379:6379 + + # 在登入頁面 + # host:redis + # port:6379 + redis-admin: + image: marian/rebrow + ports: + - 5001:5001 + depends_on: + - redis \ No newline at end of file diff --git a/Redis/Lab.Redis.Client/src/Lab.Redis.Client.sln b/Redis/Lab.Redis.Client/src/Lab.Redis.Client.sln index e4e04d4b..587b7ba4 100644 --- a/Redis/Lab.Redis.Client/src/Lab.Redis.Client.sln +++ b/Redis/Lab.Redis.Client/src/Lab.Redis.Client.sln @@ -2,6 +2,14 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject1", "TestProject1\TestProject1.csproj", "{8339C08B-EA55-4705-BA65-BA93ED6195DB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Redis.Client", "Lab.Redis.Client\Lab.Redis.Client.csproj", "{27A19D5D-9E2F-4B8A-9516-2FFD77B1052B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{2CC81745-AD2C-41A5-B7CB-5E2691440BAD}" + ProjectSection(SolutionItems) = preProject + ..\Taskfile.yml = ..\Taskfile.yml + ..\docker-compose.yml = ..\docker-compose.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -12,5 +20,9 @@ Global {8339C08B-EA55-4705-BA65-BA93ED6195DB}.Debug|Any CPU.Build.0 = Debug|Any CPU {8339C08B-EA55-4705-BA65-BA93ED6195DB}.Release|Any CPU.ActiveCfg = Release|Any CPU {8339C08B-EA55-4705-BA65-BA93ED6195DB}.Release|Any CPU.Build.0 = Release|Any CPU + {27A19D5D-9E2F-4B8A-9516-2FFD77B1052B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27A19D5D-9E2F-4B8A-9516-2FFD77B1052B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27A19D5D-9E2F-4B8A-9516-2FFD77B1052B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27A19D5D-9E2F-4B8A-9516-2FFD77B1052B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Redis/Lab.Redis.Client/src/Lab.Redis.Client/Lab.Redis.Client.csproj b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/Lab.Redis.Client.csproj new file mode 100644 index 00000000..6a137afd --- /dev/null +++ b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/Lab.Redis.Client.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisConnection.cs b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisConnection.cs new file mode 100644 index 00000000..837cd3fb --- /dev/null +++ b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisConnection.cs @@ -0,0 +1,26 @@ +using System.Collections.Concurrent; +using StackExchange.Redis; + +namespace TestProject1; + +public class RedisConnection +{ + private static ConcurrentDictionary s_connections = new(); + + public IDatabase Connect(string setting = "localhost") + { + var connMultiplexer = ConnectionMultiplexer.Connect(setting); + s_connections.TryAdd(setting, connMultiplexer); + return connMultiplexer.GetDatabase(); + } + + public IDatabase GetDatabase(string setting = "localhost") + { + if (s_connections.TryGetValue(setting, out var connMultiplexer)) + { + return connMultiplexer.GetDatabase(); + } + + return null; + } +} \ No newline at end of file diff --git a/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisDatabaseExtensions.cs b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisDatabaseExtensions.cs new file mode 100644 index 00000000..07c50dcd --- /dev/null +++ b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisDatabaseExtensions.cs @@ -0,0 +1,30 @@ +using System.Text.Json; +using StackExchange.Redis; + +namespace TestProject1; + +public static class RedisDatabaseExtensions +{ + public static bool IsExist(this IDatabase db, string key) + { + return db.KeyExists(key); + } + + public static void Set(this IDatabase db, string key, T value, + TimeSpan? expiry = default, + When when = When.Always, + CommandFlags flags = CommandFlags.None) + { + db.StringSet(key, JsonSerializer.Serialize(value), expiry, when, flags); + } + + public static T Get(this IDatabase db, string key) + { + if (db.IsExist(key)) + { + return JsonSerializer.Deserialize(db.StringGet(key)); + } + + return default; + } +} \ No newline at end of file diff --git a/Redis/Lab.Redis.Client/src/TestProject1/RedisConnectionUnitTest.cs b/Redis/Lab.Redis.Client/src/TestProject1/RedisConnectionUnitTest.cs new file mode 100644 index 00000000..d23c89e1 --- /dev/null +++ b/Redis/Lab.Redis.Client/src/TestProject1/RedisConnectionUnitTest.cs @@ -0,0 +1,32 @@ +using StackExchange.Redis; + +namespace TestProject1; + +[TestClass] +public class RedisConnectionUnitTest +{ + [TestMethod] + public void SetDTO() + { + var connection = new RedisConnection(); + var database = connection.Connect(); + + var model = new Model + { + Name = "小章", + Age = 29 + }; + + database.Set("dto", model); + var actual = database.Get("dto"); + Assert.AreEqual(model, actual); + } + + [Serializable] + record Model + { + public string Name { get; set; } + + public int Age { get; set; } + } +} \ No newline at end of file diff --git a/Redis/Lab.Redis.Client/src/TestProject1/TestProject1.csproj b/Redis/Lab.Redis.Client/src/TestProject1/TestProject1.csproj index 60cd2706..47d61bb8 100644 --- a/Redis/Lab.Redis.Client/src/TestProject1/TestProject1.csproj +++ b/Redis/Lab.Redis.Client/src/TestProject1/TestProject1.csproj @@ -16,4 +16,8 @@ + + + + From 6b9880015e7a2f26fd68b9e260417c0af69ef267 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 30 Oct 2022 15:34:38 +0800 Subject: [PATCH 299/424] refactor --- .../src/Lab.Redis.Client/RedisClient.cs | 31 +++++ .../src/Lab.Redis.Client/RedisConnection.cs | 17 +-- .../RedisDatabaseExtensions.cs | 2 +- .../src/TestProject1/RedisConnection.cs | 119 ------------------ .../TestProject1/RedisConnectionUnitTest.cs | 6 +- .../src/TestProject1/UnitTest1.cs | 18 +-- 6 files changed, 47 insertions(+), 146 deletions(-) create mode 100644 Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisClient.cs delete mode 100644 Redis/Lab.Redis.Client/src/TestProject1/RedisConnection.cs diff --git a/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisClient.cs b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisClient.cs new file mode 100644 index 00000000..90ad0deb --- /dev/null +++ b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisClient.cs @@ -0,0 +1,31 @@ +using StackExchange.Redis; + +namespace Lab.Redis.Client; + +public class RedisClient +{ + private static readonly Lazy s_connectionLazy; + private static string _setting; + + private ConnectionMultiplexer Instance => s_connectionLazy.Value; + + public IDatabase Database => this.Instance.GetDatabase(); + + static RedisClient() + { + s_connectionLazy = new Lazy(() => + { + if (string.IsNullOrWhiteSpace(_setting)) + { + return ConnectionMultiplexer.Connect("localhost"); + } + + return ConnectionMultiplexer.Connect(_setting); + }); + } + + public static void Init(string setting) + { + _setting = setting; + } +} \ No newline at end of file diff --git a/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisConnection.cs b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisConnection.cs index 837cd3fb..e03d47c8 100644 --- a/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisConnection.cs +++ b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisConnection.cs @@ -1,26 +1,27 @@ using System.Collections.Concurrent; using StackExchange.Redis; -namespace TestProject1; +namespace Lab.Redis.Client; public class RedisConnection { - private static ConcurrentDictionary s_connections = new(); + private static ConcurrentDictionary> s_connectionPool = new(); public IDatabase Connect(string setting = "localhost") { - var connMultiplexer = ConnectionMultiplexer.Connect(setting); - s_connections.TryAdd(setting, connMultiplexer); - return connMultiplexer.GetDatabase(); + var connMultiplexer = s_connectionPool.GetOrAdd(setting, + new Lazy(() => ConnectionMultiplexer.Connect(setting))); + + return connMultiplexer.Value.GetDatabase(); } public IDatabase GetDatabase(string setting = "localhost") { - if (s_connections.TryGetValue(setting, out var connMultiplexer)) + if (s_connectionPool.TryGetValue(setting, out var connMultiplexer)) { - return connMultiplexer.GetDatabase(); + return connMultiplexer.Value.GetDatabase(); } - return null; + return default; } } \ No newline at end of file diff --git a/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisDatabaseExtensions.cs b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisDatabaseExtensions.cs index 07c50dcd..d1c042c6 100644 --- a/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisDatabaseExtensions.cs +++ b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisDatabaseExtensions.cs @@ -1,7 +1,7 @@ using System.Text.Json; using StackExchange.Redis; -namespace TestProject1; +namespace Lab.Redis.Client; public static class RedisDatabaseExtensions { diff --git a/Redis/Lab.Redis.Client/src/TestProject1/RedisConnection.cs b/Redis/Lab.Redis.Client/src/TestProject1/RedisConnection.cs deleted file mode 100644 index 5d23f111..00000000 --- a/Redis/Lab.Redis.Client/src/TestProject1/RedisConnection.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System.Runtime.Serialization.Formatters.Binary; -using System.Text.Json; -using StackExchange.Redis; - -namespace TestProject1; - -public class RedisClient -{ - private static string _setting; - - static RedisClient() - { - s_connectionLazy = new Lazy(() => - { - if (string.IsNullOrWhiteSpace(_setting)) - { - return ConnectionMultiplexer.Connect("localhost"); - } - - return ConnectionMultiplexer.Connect(_setting); - }); - } - - private static readonly Lazy s_connectionLazy; - - public ConnectionMultiplexer Instance => s_connectionLazy.Value; - - public IDatabase Database => this.Instance.GetDatabase(); - - public static void Init(string setting) - { - _setting = setting; - } - - public T Get(string key) - { - if (Exists(key)) - { - return Deserialize(this.Database.StringGet(key)); - } - - throw new Exception(); - } - public T Get2(string key) - { - if (Exists(key)) - { - return JsonSerializer.Deserialize(this.Database.StringGet(key)); - } - - throw new Exception(); - } - public bool Exists(string key) - { - return this.Database.KeyExists(key); //可直接調用 - } - - public void Set(string key, T value, TimeSpan? expiry = default(TimeSpan?), When when = When.Always, - CommandFlags flags = CommandFlags.None) - { - this.Database.StringSet(key, Serialize(value), expiry, when, flags); - } - public void Set2(string key, T value, TimeSpan? expiry = default(TimeSpan?), When when = When.Always, - CommandFlags flags = CommandFlags.None) - { - this.Database.StringSet(key,JsonSerializer.Serialize(value) , expiry, when, flags); - } - - private static byte[] Serialize(object instance) - { - if (instance == null) - { - return null; - } - - var formatter = new BinaryFormatter(); - using var outputStream = new MemoryStream(); - formatter.Serialize(outputStream, instance); - return outputStream.ToArray(); - } - - private static T Deserialize(byte[] srcStream) - { - if (srcStream == null) - { - return default(T); - } - - var formatter = new BinaryFormatter(); - using var outputStream = new MemoryStream(srcStream); - return (T)formatter.Deserialize(outputStream); - } -} - -public class RedisConnection2 -{ - private static readonly Lazy s_redisConnectionLazy = new(() => new RedisConnection2()); - - private static string _setting; - - public readonly ConnectionMultiplexer ConnectionMultiplexer; - - public static RedisConnection2 Instance => s_redisConnectionLazy.Value; - - private RedisConnection2() - { - if (string.IsNullOrWhiteSpace(_setting)) - { - _setting = "localhost"; - } - - this.ConnectionMultiplexer = ConnectionMultiplexer.Connect(_setting); - } - - public static void Init(string setting) - { - _setting = setting; - } -} \ No newline at end of file diff --git a/Redis/Lab.Redis.Client/src/TestProject1/RedisConnectionUnitTest.cs b/Redis/Lab.Redis.Client/src/TestProject1/RedisConnectionUnitTest.cs index d23c89e1..733c44ba 100644 --- a/Redis/Lab.Redis.Client/src/TestProject1/RedisConnectionUnitTest.cs +++ b/Redis/Lab.Redis.Client/src/TestProject1/RedisConnectionUnitTest.cs @@ -1,3 +1,4 @@ +using Lab.Redis.Client; using StackExchange.Redis; namespace TestProject1; @@ -9,8 +10,9 @@ public class RedisConnectionUnitTest public void SetDTO() { var connection = new RedisConnection(); - var database = connection.Connect(); - + // var database = connection.Connect("localhost:6379"); + var config = ConfigurationOptions.Parse("127.0.0.1:6379"); + var database = connection.Connect(config.ToString()); var model = new Model { Name = "小章", diff --git a/Redis/Lab.Redis.Client/src/TestProject1/UnitTest1.cs b/Redis/Lab.Redis.Client/src/TestProject1/UnitTest1.cs index b73bf085..309d0339 100644 --- a/Redis/Lab.Redis.Client/src/TestProject1/UnitTest1.cs +++ b/Redis/Lab.Redis.Client/src/TestProject1/UnitTest1.cs @@ -1,3 +1,4 @@ +using Lab.Redis.Client; using StackExchange.Redis; namespace TestProject1; @@ -8,6 +9,7 @@ public class UnitTest1 [TestMethod] public void StringSet() { + RedisClient.Init("localhost"); var db = new RedisClient().Database; // Set @@ -68,22 +70,6 @@ public void Hashset() db.HashSet("employee", 3, "anson"); } - [TestMethod] - public void SetDTO() - { - var connection = new RedisClient(); - var db = connection.Database; - - var model = new MyClass - { - Name = "小章", - Age = 29 - }; - connection.Set("dto1", model); - connection.Set2("dto2", model); - - // var myClass = connection.Get("dto"); - } [Serializable] class MyClass From a03aaa99619f66e5e3a224c7f9ea151ba93781c6 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 30 Oct 2022 15:53:32 +0800 Subject: [PATCH 300/424] refactor --- .../Lab.Redis.Client/JsonSerializeFactory.cs | 21 +++++++++++++++++++ .../RedisDatabaseExtensions.cs | 19 +++++++++++++---- .../TestProject1/RedisConnectionUnitTest.cs | 5 +++-- 3 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 Redis/Lab.Redis.Client/src/Lab.Redis.Client/JsonSerializeFactory.cs diff --git a/Redis/Lab.Redis.Client/src/Lab.Redis.Client/JsonSerializeFactory.cs b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/JsonSerializeFactory.cs new file mode 100644 index 00000000..3d4365c5 --- /dev/null +++ b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/JsonSerializeFactory.cs @@ -0,0 +1,21 @@ +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Unicode; + +namespace Lab.Redis.Client; + +public class JsonSerializeFactory +{ + public static JsonSerializerOptions CreateDefault() + { + return new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs), + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + } +} \ No newline at end of file diff --git a/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisDatabaseExtensions.cs b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisDatabaseExtensions.cs index d1c042c6..3c64bd50 100644 --- a/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisDatabaseExtensions.cs +++ b/Redis/Lab.Redis.Client/src/Lab.Redis.Client/RedisDatabaseExtensions.cs @@ -13,18 +13,29 @@ public static bool IsExist(this IDatabase db, string key) public static void Set(this IDatabase db, string key, T value, TimeSpan? expiry = default, When when = When.Always, - CommandFlags flags = CommandFlags.None) + CommandFlags flags = CommandFlags.None, + JsonSerializerOptions options = default) { - db.StringSet(key, JsonSerializer.Serialize(value), expiry, when, flags); + db.StringSet(key, Serialize(value, options), expiry, when, flags); } - public static T Get(this IDatabase db, string key) + private static string Serialize(T value, JsonSerializerOptions options) + { + return JsonSerializer.Serialize(value, options); + } + + public static T Get(this IDatabase db, string key, JsonSerializerOptions options = default) { if (db.IsExist(key)) { - return JsonSerializer.Deserialize(db.StringGet(key)); + return Deserialize(db.StringGet(key), options); } return default; } + + private static T? Deserialize(RedisValue value, JsonSerializerOptions options) + { + return JsonSerializer.Deserialize(value, options); + } } \ No newline at end of file diff --git a/Redis/Lab.Redis.Client/src/TestProject1/RedisConnectionUnitTest.cs b/Redis/Lab.Redis.Client/src/TestProject1/RedisConnectionUnitTest.cs index 733c44ba..8701ba07 100644 --- a/Redis/Lab.Redis.Client/src/TestProject1/RedisConnectionUnitTest.cs +++ b/Redis/Lab.Redis.Client/src/TestProject1/RedisConnectionUnitTest.cs @@ -10,6 +10,7 @@ public class RedisConnectionUnitTest public void SetDTO() { var connection = new RedisConnection(); + // var database = connection.Connect("localhost:6379"); var config = ConfigurationOptions.Parse("127.0.0.1:6379"); var database = connection.Connect(config.ToString()); @@ -19,8 +20,8 @@ public void SetDTO() Age = 29 }; - database.Set("dto", model); - var actual = database.Get("dto"); + database.Set("dto", model, options: JsonSerializeFactory.CreateDefault()); + var actual = database.Get("dto", options: JsonSerializeFactory.CreateDefault()); Assert.AreEqual(model, actual); } From f45ce2660e56c56cde69acacbb72281931224d42 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 11 Dec 2022 11:16:14 +0800 Subject: [PATCH 301/424] =?UTF-8?q?feat:=20=E5=8A=A0=E5=85=A5=20Context=20?= =?UTF-8?q?DI=20Container=20Project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Lab.StepDependencyInjection.sln | 16 +++ .../Features/Demo0.feature | 5 + .../Features/Demo0.feature.cs | 124 ++++++++++++++++++ .../Features/Demo1.feature | 5 + .../Features/Demo1.feature.cs | 124 ++++++++++++++++++ .../Features/Demo2.feature | 5 + .../FileProvider.cs | 9 ++ .../Steps/Demo0Steps.cs | 40 ++++++ .../Steps/Demo1Steps.cs | 36 +++++ .../Steps/Demo2Steps.cs | 43 ++++++ .../SysFileProvider.cs | 16 +++ ...ct1Lab.StepDependencyInjection.Test.csproj | 22 ++++ .../Usings.cs | 1 + .../specflow.json | 10 ++ 14 files changed, 456 insertions(+) create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.sln create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo0.feature create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo1.feature create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo1.feature.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo2.feature create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/FileProvider.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo1Steps.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo2Steps.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/SysFileProvider.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/TestProject1Lab.StepDependencyInjection.Test.csproj create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Usings.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/specflow.json diff --git a/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.sln b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.sln new file mode 100644 index 00000000..cf12eb8f --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject1Lab.StepDependencyInjection.Test", "TestProject1Lab.StepDependencyInjection.Test\TestProject1Lab.StepDependencyInjection.Test.csproj", "{0347B68D-50D6-46EB-A366-747CF121C24B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0347B68D-50D6-46EB-A366-747CF121C24B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0347B68D-50D6-46EB-A366-747CF121C24B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0347B68D-50D6-46EB-A366-747CF121C24B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0347B68D-50D6-46EB-A366-747CF121C24B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo0.feature b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo0.feature new file mode 100644 index 00000000..4c80e487 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo0.feature @@ -0,0 +1,5 @@ +Feature: Demo0 + + Scenario: 測試步驟注入 + When 取得檔案路徑 + Then 預期得到 "File Provider" \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs new file mode 100644 index 00000000..17b05a33 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs @@ -0,0 +1,124 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (https://www.specflow.org/). +// SpecFlow Version:3.9.0.0 +// SpecFlow Generator Version:3.9.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace TestProject1Lab.StepDependencyInjection.Test.Features +{ + using TechTalk.SpecFlow; + using System; + using System.Linq; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute()] + public partial class Demo0Feature + { + + private static TechTalk.SpecFlow.ITestRunner testRunner; + + private Microsoft.VisualStudio.TestTools.UnitTesting.TestContext _testContext; + + private static string[] featureTags = ((string[])(null)); + +#line 1 "Demo0.feature" +#line hidden + + public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestContext + { + get + { + return this._testContext; + } + set + { + this._testContext = value; + } + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute()] + public static void FeatureSetup(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext testContext) + { + testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); + TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Features", "Demo0", null, ProgrammingLanguage.CSharp, featureTags); + testRunner.OnFeatureStart(featureInfo); + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute()] + public static void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute()] + public void TestInitialize() + { + if (((testRunner.FeatureContext != null) + && (testRunner.FeatureContext.FeatureInfo.Title != "Demo0"))) + { + global::TestProject1Lab.StepDependencyInjection.Test.Features.Demo0Feature.FeatureSetup(null); + } + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute()] + public void TestTearDown() + { + testRunner.OnScenarioEnd(); + } + + public void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioInitialize(scenarioInfo); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(_testContext); + } + + public void ScenarioStart() + { + testRunner.OnScenarioStart(); + } + + public void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()] + [Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("測試步驟注入")] + [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "Demo0")] + public void 測試步驟注入() + { + string[] tagsOfScenario = ((string[])(null)); + System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("測試步驟注入", null, tagsOfScenario, argumentsOfScenario, featureTags); +#line 3 + this.ScenarioInitialize(scenarioInfo); +#line hidden + if ((TagHelper.ContainsIgnoreTag(tagsOfScenario) || TagHelper.ContainsIgnoreTag(featureTags))) + { + testRunner.SkipScenario(); + } + else + { + this.ScenarioStart(); +#line 4 + testRunner.When("取得檔案路徑", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line hidden +#line 5 + testRunner.Then("預期得到 \"File Provider\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + } + this.ScenarioCleanup(); + } + } +} +#pragma warning restore +#endregion diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo1.feature b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo1.feature new file mode 100644 index 00000000..d2eeaff0 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo1.feature @@ -0,0 +1,5 @@ +Feature: Demo1 + + Scenario: 測試步驟注入 + When 取得檔案路徑 + Then 預期得到 "File Provider" diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo1.feature.cs b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo1.feature.cs new file mode 100644 index 00000000..b6472c2d --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo1.feature.cs @@ -0,0 +1,124 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (https://www.specflow.org/). +// SpecFlow Version:3.9.0.0 +// SpecFlow Generator Version:3.9.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace TestProject1Lab.StepDependencyInjection.Test.Features +{ + using TechTalk.SpecFlow; + using System; + using System.Linq; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute()] + public partial class Demo1Feature + { + + private static TechTalk.SpecFlow.ITestRunner testRunner; + + private Microsoft.VisualStudio.TestTools.UnitTesting.TestContext _testContext; + + private static string[] featureTags = ((string[])(null)); + +#line 1 "Demo1.feature" +#line hidden + + public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestContext + { + get + { + return this._testContext; + } + set + { + this._testContext = value; + } + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute()] + public static void FeatureSetup(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext testContext) + { + testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); + TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Features", "Demo1", null, ProgrammingLanguage.CSharp, featureTags); + testRunner.OnFeatureStart(featureInfo); + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute()] + public static void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute()] + public void TestInitialize() + { + if (((testRunner.FeatureContext != null) + && (testRunner.FeatureContext.FeatureInfo.Title != "Demo1"))) + { + global::TestProject1Lab.StepDependencyInjection.Test.Features.Demo1Feature.FeatureSetup(null); + } + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute()] + public void TestTearDown() + { + testRunner.OnScenarioEnd(); + } + + public void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioInitialize(scenarioInfo); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(_testContext); + } + + public void ScenarioStart() + { + testRunner.OnScenarioStart(); + } + + public void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()] + [Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("測試步驟注入")] + [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "Demo1")] + public void 測試步驟注入() + { + string[] tagsOfScenario = ((string[])(null)); + System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("測試步驟注入", null, tagsOfScenario, argumentsOfScenario, featureTags); +#line 3 + this.ScenarioInitialize(scenarioInfo); +#line hidden + if ((TagHelper.ContainsIgnoreTag(tagsOfScenario) || TagHelper.ContainsIgnoreTag(featureTags))) + { + testRunner.SkipScenario(); + } + else + { + this.ScenarioStart(); +#line 4 + testRunner.When("取得檔案路徑", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line hidden +#line 5 + testRunner.Then("預期得到 \"File Provider\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + } + this.ScenarioCleanup(); + } + } +} +#pragma warning restore +#endregion diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo2.feature b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo2.feature new file mode 100644 index 00000000..d7f0b85c --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo2.feature @@ -0,0 +1,5 @@ +Feature: Demo2 + + Scenario: 測試步驟注入 + When 取得檔案路徑 + Then 預期得到 "fake provider:SysFileProvider" diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/FileProvider.cs b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/FileProvider.cs new file mode 100644 index 00000000..4879839f --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/FileProvider.cs @@ -0,0 +1,9 @@ +namespace TestProject1Lab.StepDependencyInjection.Test; + +public class FileProvider +{ + public string GetPath() + { + return "File Provider"; + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs new file mode 100644 index 00000000..83aa2124 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs @@ -0,0 +1,40 @@ +using TechTalk.SpecFlow; + +namespace TestProject1Lab.StepDependencyInjection.Test.Steps; + +[Binding] +[Scope(Feature = "Demo0")] +public class Demo0Steps +{ + private readonly FileProvider _fileProvider; + + private ScenarioContext ScenarioContext { get; } + + public Demo0Steps(ScenarioContext scenarioContext, FileProvider fileProvider) + { + this.ScenarioContext = scenarioContext; + this._fileProvider = fileProvider; + } + + // [ScenarioDependencies] + // public static IServiceCollection CreateServices() + // { + // var services = new ServiceCollection(); + // services.AddSingleton(); + // return services; + // } + + [When(@"取得檔案路徑")] + public void When取得檔案路徑() + { + var path = this._fileProvider.GetPath(); + this.ScenarioContext.Set(path, "actual"); + } + + [Then(@"預期得到 ""(.*)""")] + public void Then預期得到(string expected) + { + var actual = this.ScenarioContext.Get("actual"); + Assert.AreEqual(expected, actual); + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo1Steps.cs b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo1Steps.cs new file mode 100644 index 00000000..b4683059 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo1Steps.cs @@ -0,0 +1,36 @@ +using BoDi; +using TechTalk.SpecFlow; + +namespace TestProject1Lab.StepDependencyInjection.Test.Steps; + +[Binding] +[Scope(Feature = "Demo1")] +public class Demo1Steps : TechTalk.SpecFlow.Steps +{ + private readonly IObjectContainer objectContainer; + + private ScenarioContext ScenarioContext { get; } + + public Demo1Steps(IObjectContainer objectContainer, + ScenarioContext scenarioContext) + { + objectContainer.RegisterInstanceAs(new FileProvider(), nameof(FileProvider)); + this.objectContainer = objectContainer; + this.ScenarioContext = scenarioContext; + } + + [When(@"取得檔案路徑")] + public void When取得檔案路徑() + { + var target = this.objectContainer.Resolve(nameof(FileProvider)); + var path = target.GetPath(); + this.ScenarioContext.Set(path, "actual"); + } + + [Then(@"預期得到 ""(.*)""")] + public void Then預期得到(string expected) + { + var actual = this.ScenarioContext.Get("actual"); + Assert.AreEqual(expected, actual); + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo2Steps.cs b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo2Steps.cs new file mode 100644 index 00000000..d0b97811 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo2Steps.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.DependencyInjection; +using SolidToken.SpecFlow.DependencyInjection; +using TechTalk.SpecFlow; + +namespace TestProject1Lab.StepDependencyInjection.Test.Steps; + +[Binding] +[Scope(Feature = "Demo2")] +public class Demo2Steps +{ + private readonly SysFileProvider _sysFileProvider; + + public Demo2Steps(SysFileProvider sysFileProvider, ScenarioContext scenarioContext) + { + this._sysFileProvider = sysFileProvider; + this.ScenarioContext = scenarioContext; + } + + private ScenarioContext ScenarioContext { get; } + + + [ScenarioDependencies] + public static IServiceCollection CreateServices() + { + var services = new ServiceCollection(); + services.AddSingleton(new SysFileProvider("fake provider")); + return services; + } + + [When(@"取得檔案路徑")] + public void When取得檔案路徑() + { + var path = this._sysFileProvider.GetPath(); + this.ScenarioContext.Set(path, "actual"); + } + + [Then(@"預期得到 ""(.*)""")] + public void Then預期得到(string expected) + { + var actual = this.ScenarioContext.Get("actual"); + Assert.AreEqual(expected, actual); + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/SysFileProvider.cs b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/SysFileProvider.cs new file mode 100644 index 00000000..2cbb1770 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/SysFileProvider.cs @@ -0,0 +1,16 @@ +namespace TestProject1Lab.StepDependencyInjection.Test; + +public class SysFileProvider +{ + private readonly string _name; + + public SysFileProvider(string name) + { + this._name = name; + } + + public string GetPath() + { + return $"{this._name}:SysFileProvider"; + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/TestProject1Lab.StepDependencyInjection.Test.csproj b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/TestProject1Lab.StepDependencyInjection.Test.csproj new file mode 100644 index 00000000..d3d8c81c --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/TestProject1Lab.StepDependencyInjection.Test.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + + diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Usings.cs b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/specflow.json b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/specflow.json new file mode 100644 index 00000000..06565c4c --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/specflow.json @@ -0,0 +1,10 @@ +{ + "language": { + "feature": "en-US" + }, + "stepAssemblies": [ + { + "assembly": "Allure.SpecFlowPlugin" + } + ] +} \ No newline at end of file From af02f3a5eabe8340b8aeb796cbb568df659329f3 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 11 Dec 2022 18:10:28 +0800 Subject: [PATCH 302/424] refactor --- .../Features/Demo0.feature | 0 .../Features/Demo0.feature.cs | 30 ++--- .../Features/Demo1.feature | 0 .../Features/Demo1.feature.cs | 30 ++--- .../Features/Demo2.feature | 0 .../Features/Demo2.feature.cs | 122 ++++++++++++++++++ .../FileProvider.cs | 2 +- .../Lab.StepDependencyInjection.Test.csproj | 22 ++++ .../Steps/Demo0Steps.cs | 2 +- .../Steps/Demo1Steps.cs | 2 +- .../Steps/Demo2Steps.cs | 2 +- .../SysFileProvider.cs | 2 +- .../UnitTest1.cs | 10 ++ .../Usings.cs | 1 + .../specflow.json | 0 .../Lab.StepDependencyInjection.sln | 6 + ...ct1Lab.StepDependencyInjection.Test.csproj | 12 ++ 17 files changed, 206 insertions(+), 37 deletions(-) rename Test/Specflow3/Lab.StepDependencyInjection/{TestProject1Lab.StepDependencyInjection.Test => Lab.StepDependencyInjection.Test}/Features/Demo0.feature (100%) rename Test/Specflow3/Lab.StepDependencyInjection/{TestProject1Lab.StepDependencyInjection.Test => Lab.StepDependencyInjection.Test}/Features/Demo0.feature.cs (80%) rename Test/Specflow3/Lab.StepDependencyInjection/{TestProject1Lab.StepDependencyInjection.Test => Lab.StepDependencyInjection.Test}/Features/Demo1.feature (100%) rename Test/Specflow3/Lab.StepDependencyInjection/{TestProject1Lab.StepDependencyInjection.Test => Lab.StepDependencyInjection.Test}/Features/Demo1.feature.cs (80%) rename Test/Specflow3/Lab.StepDependencyInjection/{TestProject1Lab.StepDependencyInjection.Test => Lab.StepDependencyInjection.Test}/Features/Demo2.feature (100%) create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo2.feature.cs rename Test/Specflow3/Lab.StepDependencyInjection/{TestProject1Lab.StepDependencyInjection.Test => Lab.StepDependencyInjection.Test}/FileProvider.cs (63%) create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Lab.StepDependencyInjection.Test.csproj rename Test/Specflow3/Lab.StepDependencyInjection/{TestProject1Lab.StepDependencyInjection.Test => Lab.StepDependencyInjection.Test}/Steps/Demo0Steps.cs (94%) rename Test/Specflow3/Lab.StepDependencyInjection/{TestProject1Lab.StepDependencyInjection.Test => Lab.StepDependencyInjection.Test}/Steps/Demo1Steps.cs (94%) rename Test/Specflow3/Lab.StepDependencyInjection/{TestProject1Lab.StepDependencyInjection.Test => Lab.StepDependencyInjection.Test}/Steps/Demo2Steps.cs (94%) rename Test/Specflow3/Lab.StepDependencyInjection/{TestProject1Lab.StepDependencyInjection.Test => Lab.StepDependencyInjection.Test}/SysFileProvider.cs (80%) create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/UnitTest1.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Usings.cs rename Test/Specflow3/Lab.StepDependencyInjection/{TestProject1Lab.StepDependencyInjection.Test => Lab.StepDependencyInjection.Test}/specflow.json (100%) diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo0.feature b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo0.feature similarity index 100% rename from Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo0.feature rename to Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo0.feature diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs similarity index 80% rename from Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs rename to Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs index 17b05a33..d52ddcbe 100644 --- a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs +++ b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs @@ -9,17 +9,15 @@ // // ------------------------------------------------------------------------------ #region Designer generated code + +using TechTalk.SpecFlow; + #pragma warning disable -namespace TestProject1Lab.StepDependencyInjection.Test.Features +namespace Lab.StepDependencyInjection.Test.Features { - using TechTalk.SpecFlow; - using System; - using System.Linq; - - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute()] + [TestClass()] public partial class Demo0Feature { @@ -44,7 +42,7 @@ public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestCont } } - [Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute()] + [ClassInitialize()] public static void FeatureSetup(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext testContext) { testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); @@ -52,24 +50,24 @@ public static void FeatureSetup(Microsoft.VisualStudio.TestTools.UnitTesting.Tes testRunner.OnFeatureStart(featureInfo); } - [Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute()] + [ClassCleanup()] public static void FeatureTearDown() { testRunner.OnFeatureEnd(); testRunner = null; } - [Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute()] + [TestInitialize()] public void TestInitialize() { if (((testRunner.FeatureContext != null) && (testRunner.FeatureContext.FeatureInfo.Title != "Demo0"))) { - global::TestProject1Lab.StepDependencyInjection.Test.Features.Demo0Feature.FeatureSetup(null); + global::Lab.StepDependencyInjection.Test.Features.Demo0Feature.FeatureSetup(null); } } - [Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute()] + [TestCleanup()] public void TestTearDown() { testRunner.OnScenarioEnd(); @@ -78,7 +76,7 @@ public void TestTearDown() public void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) { testRunner.OnScenarioInitialize(scenarioInfo); - testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(_testContext); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(this._testContext); } public void ScenarioStart() @@ -91,9 +89,9 @@ public void ScenarioCleanup() testRunner.CollectScenarioErrors(); } - [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()] - [Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("測試步驟注入")] - [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "Demo0")] + [TestMethod()] + [Description("測試步驟注入")] + [TestProperty("FeatureTitle", "Demo0")] public void 測試步驟注入() { string[] tagsOfScenario = ((string[])(null)); diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo1.feature b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo1.feature similarity index 100% rename from Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo1.feature rename to Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo1.feature diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo1.feature.cs b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo1.feature.cs similarity index 80% rename from Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo1.feature.cs rename to Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo1.feature.cs index b6472c2d..16e71765 100644 --- a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo1.feature.cs +++ b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo1.feature.cs @@ -9,17 +9,15 @@ // // ------------------------------------------------------------------------------ #region Designer generated code + +using TechTalk.SpecFlow; + #pragma warning disable -namespace TestProject1Lab.StepDependencyInjection.Test.Features +namespace Lab.StepDependencyInjection.Test.Features { - using TechTalk.SpecFlow; - using System; - using System.Linq; - - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute()] + [TestClass()] public partial class Demo1Feature { @@ -44,7 +42,7 @@ public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestCont } } - [Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute()] + [ClassInitialize()] public static void FeatureSetup(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext testContext) { testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); @@ -52,24 +50,24 @@ public static void FeatureSetup(Microsoft.VisualStudio.TestTools.UnitTesting.Tes testRunner.OnFeatureStart(featureInfo); } - [Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute()] + [ClassCleanup()] public static void FeatureTearDown() { testRunner.OnFeatureEnd(); testRunner = null; } - [Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute()] + [TestInitialize()] public void TestInitialize() { if (((testRunner.FeatureContext != null) && (testRunner.FeatureContext.FeatureInfo.Title != "Demo1"))) { - global::TestProject1Lab.StepDependencyInjection.Test.Features.Demo1Feature.FeatureSetup(null); + global::Lab.StepDependencyInjection.Test.Features.Demo1Feature.FeatureSetup(null); } } - [Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute()] + [TestCleanup()] public void TestTearDown() { testRunner.OnScenarioEnd(); @@ -78,7 +76,7 @@ public void TestTearDown() public void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) { testRunner.OnScenarioInitialize(scenarioInfo); - testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(_testContext); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(this._testContext); } public void ScenarioStart() @@ -91,9 +89,9 @@ public void ScenarioCleanup() testRunner.CollectScenarioErrors(); } - [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()] - [Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("測試步驟注入")] - [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "Demo1")] + [TestMethod()] + [Description("測試步驟注入")] + [TestProperty("FeatureTitle", "Demo1")] public void 測試步驟注入() { string[] tagsOfScenario = ((string[])(null)); diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo2.feature b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo2.feature similarity index 100% rename from Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Features/Demo2.feature rename to Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo2.feature diff --git a/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo2.feature.cs b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo2.feature.cs new file mode 100644 index 00000000..8dbce9f2 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Features/Demo2.feature.cs @@ -0,0 +1,122 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (https://www.specflow.org/). +// SpecFlow Version:3.9.0.0 +// SpecFlow Generator Version:3.9.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code + +using TechTalk.SpecFlow; + +#pragma warning disable +namespace Lab.StepDependencyInjection.Test.Features +{ + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [TestClass()] + public partial class Demo2Feature + { + + private static TechTalk.SpecFlow.ITestRunner testRunner; + + private Microsoft.VisualStudio.TestTools.UnitTesting.TestContext _testContext; + + private static string[] featureTags = ((string[])(null)); + +#line 1 "Demo2.feature" +#line hidden + + public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestContext + { + get + { + return this._testContext; + } + set + { + this._testContext = value; + } + } + + [ClassInitialize()] + public static void FeatureSetup(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext testContext) + { + testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); + TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Features", "Demo2", null, ProgrammingLanguage.CSharp, featureTags); + testRunner.OnFeatureStart(featureInfo); + } + + [ClassCleanup()] + public static void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + [TestInitialize()] + public void TestInitialize() + { + if (((testRunner.FeatureContext != null) + && (testRunner.FeatureContext.FeatureInfo.Title != "Demo2"))) + { + global::Lab.StepDependencyInjection.Test.Features.Demo2Feature.FeatureSetup(null); + } + } + + [TestCleanup()] + public void TestTearDown() + { + testRunner.OnScenarioEnd(); + } + + public void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioInitialize(scenarioInfo); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(this._testContext); + } + + public void ScenarioStart() + { + testRunner.OnScenarioStart(); + } + + public void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + [TestMethod()] + [Description("測試步驟注入")] + [TestProperty("FeatureTitle", "Demo2")] + public void 測試步驟注入() + { + string[] tagsOfScenario = ((string[])(null)); + System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("測試步驟注入", null, tagsOfScenario, argumentsOfScenario, featureTags); +#line 3 + this.ScenarioInitialize(scenarioInfo); +#line hidden + if ((TagHelper.ContainsIgnoreTag(tagsOfScenario) || TagHelper.ContainsIgnoreTag(featureTags))) + { + testRunner.SkipScenario(); + } + else + { + this.ScenarioStart(); +#line 4 + testRunner.When("取得檔案路徑", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line hidden +#line 5 + testRunner.Then("預期得到 \"fake provider:SysFileProvider\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + } + this.ScenarioCleanup(); + } + } +} +#pragma warning restore +#endregion diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/FileProvider.cs b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/FileProvider.cs similarity index 63% rename from Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/FileProvider.cs rename to Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/FileProvider.cs index 4879839f..caead342 100644 --- a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/FileProvider.cs +++ b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/FileProvider.cs @@ -1,4 +1,4 @@ -namespace TestProject1Lab.StepDependencyInjection.Test; +namespace Lab.StepDependencyInjection.Test; public class FileProvider { diff --git a/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Lab.StepDependencyInjection.Test.csproj b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Lab.StepDependencyInjection.Test.csproj new file mode 100644 index 00000000..d3d8c81c --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Lab.StepDependencyInjection.Test.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + + diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs similarity index 94% rename from Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs rename to Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs index 83aa2124..775ba6b8 100644 --- a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs +++ b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs @@ -1,6 +1,6 @@ using TechTalk.SpecFlow; -namespace TestProject1Lab.StepDependencyInjection.Test.Steps; +namespace Lab.StepDependencyInjection.Test.Steps; [Binding] [Scope(Feature = "Demo0")] diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo1Steps.cs b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Steps/Demo1Steps.cs similarity index 94% rename from Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo1Steps.cs rename to Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Steps/Demo1Steps.cs index b4683059..aadd1733 100644 --- a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo1Steps.cs +++ b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Steps/Demo1Steps.cs @@ -1,7 +1,7 @@ using BoDi; using TechTalk.SpecFlow; -namespace TestProject1Lab.StepDependencyInjection.Test.Steps; +namespace Lab.StepDependencyInjection.Test.Steps; [Binding] [Scope(Feature = "Demo1")] diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo2Steps.cs b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Steps/Demo2Steps.cs similarity index 94% rename from Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo2Steps.cs rename to Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Steps/Demo2Steps.cs index d0b97811..8f6307a8 100644 --- a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Steps/Demo2Steps.cs +++ b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Steps/Demo2Steps.cs @@ -2,7 +2,7 @@ using SolidToken.SpecFlow.DependencyInjection; using TechTalk.SpecFlow; -namespace TestProject1Lab.StepDependencyInjection.Test.Steps; +namespace Lab.StepDependencyInjection.Test.Steps; [Binding] [Scope(Feature = "Demo2")] diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/SysFileProvider.cs b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/SysFileProvider.cs similarity index 80% rename from Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/SysFileProvider.cs rename to Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/SysFileProvider.cs index 2cbb1770..b95858c1 100644 --- a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/SysFileProvider.cs +++ b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/SysFileProvider.cs @@ -1,4 +1,4 @@ -namespace TestProject1Lab.StepDependencyInjection.Test; +namespace Lab.StepDependencyInjection.Test; public class SysFileProvider { diff --git a/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/UnitTest1.cs b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/UnitTest1.cs new file mode 100644 index 00000000..359bd5b8 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace Lab.StepDependencyInjection.Test; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestMethod1() + { + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Usings.cs b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/specflow.json b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/specflow.json similarity index 100% rename from Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/specflow.json rename to Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.Test/specflow.json diff --git a/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.sln b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.sln index cf12eb8f..2c2829db 100644 --- a/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.sln +++ b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.sln @@ -2,6 +2,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject1Lab.StepDependencyInjection.Test", "TestProject1Lab.StepDependencyInjection.Test\TestProject1Lab.StepDependencyInjection.Test.csproj", "{0347B68D-50D6-46EB-A366-747CF121C24B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.StepDependencyInjection.Test", "Lab.StepDependencyInjection.Test\Lab.StepDependencyInjection.Test.csproj", "{B44A3BAF-10A5-499A-B1D4-48F9BA333884}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -12,5 +14,9 @@ Global {0347B68D-50D6-46EB-A366-747CF121C24B}.Debug|Any CPU.Build.0 = Debug|Any CPU {0347B68D-50D6-46EB-A366-747CF121C24B}.Release|Any CPU.ActiveCfg = Release|Any CPU {0347B68D-50D6-46EB-A366-747CF121C24B}.Release|Any CPU.Build.0 = Release|Any CPU + {B44A3BAF-10A5-499A-B1D4-48F9BA333884}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B44A3BAF-10A5-499A-B1D4-48F9BA333884}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B44A3BAF-10A5-499A-B1D4-48F9BA333884}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B44A3BAF-10A5-499A-B1D4-48F9BA333884}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/TestProject1Lab.StepDependencyInjection.Test.csproj b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/TestProject1Lab.StepDependencyInjection.Test.csproj index d3d8c81c..ff2621cb 100644 --- a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/TestProject1Lab.StepDependencyInjection.Test.csproj +++ b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/TestProject1Lab.StepDependencyInjection.Test.csproj @@ -19,4 +19,16 @@ + + + + + + + + + + + + From a9fba682aa669f12d9dea516421975ef33c54ac6 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 11 Dec 2022 18:10:58 +0800 Subject: [PATCH 303/424] refactor --- .../Lab.StepDependencyInjection.sln | 6 ---- ...ct1Lab.StepDependencyInjection.Test.csproj | 34 ------------------- .../Usings.cs | 1 - 3 files changed, 41 deletions(-) delete mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/TestProject1Lab.StepDependencyInjection.Test.csproj delete mode 100644 Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Usings.cs diff --git a/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.sln b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.sln index 2c2829db..cd4d1aec 100644 --- a/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.sln +++ b/Test/Specflow3/Lab.StepDependencyInjection/Lab.StepDependencyInjection.sln @@ -1,7 +1,5 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject1Lab.StepDependencyInjection.Test", "TestProject1Lab.StepDependencyInjection.Test\TestProject1Lab.StepDependencyInjection.Test.csproj", "{0347B68D-50D6-46EB-A366-747CF121C24B}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.StepDependencyInjection.Test", "Lab.StepDependencyInjection.Test\Lab.StepDependencyInjection.Test.csproj", "{B44A3BAF-10A5-499A-B1D4-48F9BA333884}" EndProject Global @@ -10,10 +8,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0347B68D-50D6-46EB-A366-747CF121C24B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0347B68D-50D6-46EB-A366-747CF121C24B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0347B68D-50D6-46EB-A366-747CF121C24B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0347B68D-50D6-46EB-A366-747CF121C24B}.Release|Any CPU.Build.0 = Release|Any CPU {B44A3BAF-10A5-499A-B1D4-48F9BA333884}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B44A3BAF-10A5-499A-B1D4-48F9BA333884}.Debug|Any CPU.Build.0 = Debug|Any CPU {B44A3BAF-10A5-499A-B1D4-48F9BA333884}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/TestProject1Lab.StepDependencyInjection.Test.csproj b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/TestProject1Lab.StepDependencyInjection.Test.csproj deleted file mode 100644 index ff2621cb..00000000 --- a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/TestProject1Lab.StepDependencyInjection.Test.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - net6.0 - enable - enable - - false - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Usings.cs b/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Usings.cs deleted file mode 100644 index ab67c7ea..00000000 --- a/Test/Specflow3/Lab.StepDependencyInjection/TestProject1Lab.StepDependencyInjection.Test/Usings.cs +++ /dev/null @@ -1 +0,0 @@ -global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file From 27685a08ca4363e96195a30eac028520f638a109 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 11 Dec 2022 23:46:55 +0800 Subject: [PATCH 304/424] feat: add specflow+livingDoc project --- .../Lab.LivingDocs.Test/Calculation.cs | 9 +++++ .../Lab.LivingDocs.Test.csproj | 20 +++++++++++ .../Lab.LivingDocs.Test/UnitTest1.cs | 10 ++++++ .../Lab.LivingDocs.Test/Usings.cs | 1 + ...0\250\210\347\256\227\346\251\237.feature" | 20 +++++++++++ ...50\250\210\347\256\227\346\251\237Step.cs" | 36 +++++++++++++++++++ 6 files changed, 96 insertions(+) create mode 100644 Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/Calculation.cs create mode 100644 Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/Lab.LivingDocs.Test.csproj create mode 100644 Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/UnitTest1.cs create mode 100644 Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/Usings.cs create mode 100644 "Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/\350\250\210\347\256\227\346\251\237.feature" create mode 100644 "Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/\350\250\210\347\256\227\346\251\237Step.cs" diff --git a/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/Calculation.cs b/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/Calculation.cs new file mode 100644 index 00000000..b73f09db --- /dev/null +++ b/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/Calculation.cs @@ -0,0 +1,9 @@ +namespace Lab.LivingDocs.Test; + +public class Calculation +{ + public double Add(double firstNumber, double secondNumber) + { + return firstNumber + secondNumber; + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/Lab.LivingDocs.Test.csproj b/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/Lab.LivingDocs.Test.csproj new file mode 100644 index 00000000..6ccc2f40 --- /dev/null +++ b/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/Lab.LivingDocs.Test.csproj @@ -0,0 +1,20 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + diff --git a/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/UnitTest1.cs b/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/UnitTest1.cs new file mode 100644 index 00000000..ff4202d1 --- /dev/null +++ b/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace Lab.LivingDocs.Test; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestMethod1() + { + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/Usings.cs b/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git "a/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/\350\250\210\347\256\227\346\251\237.feature" "b/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/\350\250\210\347\256\227\346\251\237.feature" new file mode 100644 index 00000000..998d0f2e --- /dev/null +++ "b/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/\350\250\210\347\256\227\346\251\237.feature" @@ -0,0 +1,20 @@ +Feature: 計算機 +Simple calculator for adding two numbers + + Scenario: 相加兩個數字 + Given 第一個數字為 50 + And 第二個數字為 70 + When 兩個數字相加 + Then 結果應該為 120 + + Scenario Outline: 相加兩個數字(Examples) + Given 第一個數字為 + And 第二個數字為 + When 兩個數字相加 + Then 結果應該為 + + Examples: + | First | Second | Result | + | 50 | 70 | 120 | + | 30 | 40 | 70 | + | 60 | 30 | 90 | \ No newline at end of file diff --git "a/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/\350\250\210\347\256\227\346\251\237Step.cs" "b/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/\350\250\210\347\256\227\346\251\237Step.cs" new file mode 100644 index 00000000..4f830d23 --- /dev/null +++ "b/Test/Specflow3/Lab.LivingDocs/Lab.LivingDocs.Test/\350\250\210\347\256\227\346\251\237Step.cs" @@ -0,0 +1,36 @@ +using TechTalk.SpecFlow; + +namespace Lab.LivingDocs.Test; + +[Binding] +public class 計算機Step : Steps +{ + [Given(@"第一個數字為 (.*)")] + public void Given第一個數字為(double firstNumber) + { + this.ScenarioContext.Set(firstNumber, "firstNumber"); + } + + [Given(@"第二個數字為 (.*)")] + public void Given第二個數字為(double secondNumber) + { + this.ScenarioContext.Set(secondNumber, "secondNumber"); + } + + [Then(@"結果應該為 (.*)")] + public void Then結果應該為(double expected) + { + var actual = this.ScenarioContext.Get("actual"); + Assert.AreEqual(expected, actual); + } + + [When(@"兩個數字相加")] + public void When兩個數字相加() + { + var firstNumber = this.ScenarioContext.Get("firstNumber"); + var secondNumber = this.ScenarioContext.Get("secondNumber"); + var calculation = new Calculation(); + var actual = calculation.Add(firstNumber, secondNumber); + this.ScenarioContext.Set(actual, "actual"); + } +} \ No newline at end of file From 9ff395ea937c2f8347659ba5e48f0033bc3a8a3b Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 14 Dec 2022 22:35:40 +0800 Subject: [PATCH 305/424] add project --- .../EntityModel/Employee.cs | 29 ++++ .../EntityModel/EmployeeDbContext.cs | 25 ++++ .../EmployeeDbContextContextFactory.cs | 17 +++ .../Features/Demo0.feature | 5 + .../Features/Demo0.feature.cs | 124 ++++++++++++++++++ .../FileProvider.cs | 9 ++ .../GlobalSteps.cs | 32 +++++ .../InstanceManager.cs | 42 ++++++ .../Lab.StepDependencyInjection.Test.csproj | 26 ++++ .../Steps/Demo0Steps.cs | 59 +++++++++ .../SysFileProvider.cs | 16 +++ .../UnitTest1.cs | 10 ++ .../Usings.cs | 1 + .../specflow.json | 10 ++ ...ab.StepDependencyInjectionAndDbContext.sln | 16 +++ 15 files changed, 421 insertions(+) create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/Employee.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/EmployeeDbContext.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/EmployeeDbContextContextFactory.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo0.feature create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/FileProvider.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/GlobalSteps.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/InstanceManager.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Lab.StepDependencyInjection.Test.csproj create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/SysFileProvider.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/UnitTest1.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Usings.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/specflow.json create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjectionAndDbContext.sln diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/Employee.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/Employee.cs new file mode 100644 index 00000000..bfa54054 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/Employee.cs @@ -0,0 +1,29 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Lab.StepDependencyInjection.Test.EntityModel +{ + [Table("Employee")] + public class Employee + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public Guid Id { get; set; } + + [Required] + public string Name { get; set; } + + public int? Age { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } + + public string Remark { get; set; } + + [Required] + public DateTimeOffset CreateAt { get; set; } + + [Required] + public string CreateBy { get; set; } + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/EmployeeDbContext.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/EmployeeDbContext.cs new file mode 100644 index 00000000..bd85ef9a --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/EmployeeDbContext.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; + +namespace Lab.StepDependencyInjection.Test.EntityModel +{ + public class EmployeeDbContext : DbContext + { + public virtual DbSet Employees { get; set; } + + public EmployeeDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(p => + { + p.HasKey(e => e.Id); + p.HasIndex(e => e.SequenceId) + .IsUnique(); + p.Property(p => p.Remark).IsRequired(false); + }); + } + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/EmployeeDbContextContextFactory.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/EmployeeDbContextContextFactory.cs new file mode 100644 index 00000000..0e3850c0 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/EmployeeDbContextContextFactory.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace Lab.StepDependencyInjection.Test.EntityModel; + +public class EmployeeDbContextContextFactory : IDesignTimeDbContextFactory +{ + public EmployeeDbContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder + .UseNpgsql("Host=localhost;Port=5432;Database=employee;Username=postgres;Password=guest") + .UseSnakeCaseNamingConvention(); + + return new EmployeeDbContext(optionsBuilder.Options); + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo0.feature b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo0.feature new file mode 100644 index 00000000..23b60576 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo0.feature @@ -0,0 +1,5 @@ +Feature: Demo0 + + Scenario: 測試步驟注入 + When 寫入資料表 + When 讀取資料表 \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs new file mode 100644 index 00000000..d6d97f0a --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs @@ -0,0 +1,124 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (https://www.specflow.org/). +// SpecFlow Version:3.9.0.0 +// SpecFlow Generator Version:3.9.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace Lab.StepDependencyInjection.Test.Features +{ + using TechTalk.SpecFlow; + using System; + using System.Linq; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute()] + public partial class Demo0Feature + { + + private static TechTalk.SpecFlow.ITestRunner testRunner; + + private Microsoft.VisualStudio.TestTools.UnitTesting.TestContext _testContext; + + private static string[] featureTags = ((string[])(null)); + +#line 1 "Demo0.feature" +#line hidden + + public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestContext + { + get + { + return this._testContext; + } + set + { + this._testContext = value; + } + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute()] + public static void FeatureSetup(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext testContext) + { + testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); + TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Features", "Demo0", null, ProgrammingLanguage.CSharp, featureTags); + testRunner.OnFeatureStart(featureInfo); + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute()] + public static void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute()] + public void TestInitialize() + { + if (((testRunner.FeatureContext != null) + && (testRunner.FeatureContext.FeatureInfo.Title != "Demo0"))) + { + global::Lab.StepDependencyInjection.Test.Features.Demo0Feature.FeatureSetup(null); + } + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute()] + public void TestTearDown() + { + testRunner.OnScenarioEnd(); + } + + public void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioInitialize(scenarioInfo); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(_testContext); + } + + public void ScenarioStart() + { + testRunner.OnScenarioStart(); + } + + public void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()] + [Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("測試步驟注入")] + [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "Demo0")] + public void 測試步驟注入() + { + string[] tagsOfScenario = ((string[])(null)); + System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("測試步驟注入", null, tagsOfScenario, argumentsOfScenario, featureTags); +#line 3 + this.ScenarioInitialize(scenarioInfo); +#line hidden + if ((TagHelper.ContainsIgnoreTag(tagsOfScenario) || TagHelper.ContainsIgnoreTag(featureTags))) + { + testRunner.SkipScenario(); + } + else + { + this.ScenarioStart(); +#line 4 + testRunner.When("取得檔案路徑", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line hidden +#line 5 + testRunner.Then("預期得到 \"File Provider\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + } + this.ScenarioCleanup(); + } + } +} +#pragma warning restore +#endregion diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/FileProvider.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/FileProvider.cs new file mode 100644 index 00000000..caead342 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/FileProvider.cs @@ -0,0 +1,9 @@ +namespace Lab.StepDependencyInjection.Test; + +public class FileProvider +{ + public string GetPath() + { + return "File Provider"; + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/GlobalSteps.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/GlobalSteps.cs new file mode 100644 index 00000000..8bd0c3e2 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/GlobalSteps.cs @@ -0,0 +1,32 @@ +using Lab.StepDependencyInjection.Test.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using TechTalk.SpecFlow; + +namespace Lab.StepDependencyInjection.Test.Steps; + +[Binding] +public class GlobalSteps +{ + internal static ServiceProvider ServiceProvider; + + [BeforeTestRun] + public static void BeforeTestRun() + { + var service = InstanceManager.InitializeServices(); + ServiceProvider = service.BuildServiceProvider(); + var factory = ServiceProvider.GetService>(); + using var dbContext = factory.CreateDbContext(); + dbContext.Database.EnsureDeleted(); + dbContext.Database.EnsureCreated(); + } + + [AfterTestRun] + public static void AfterTestRun() + { + var factory = ServiceProvider.GetService>(); + using var dbContext = factory.CreateDbContext(); + dbContext.Database.EnsureDeleted(); + dbContext.Database.EnsureCreated(); + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/InstanceManager.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/InstanceManager.cs new file mode 100644 index 00000000..287c2aac --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/InstanceManager.cs @@ -0,0 +1,42 @@ +using Lab.StepDependencyInjection.Test.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.StepDependencyInjection.Test; + +internal class InstanceManager +{ + public static IServiceCollection InitializeServices() + { + var services = new ServiceCollection(); + services.AddSingleton(); + + services.AddLogging(p => p.AddConsole()); + services.AddDbContextFactory((p, options) => + { + var loggerFactory = p.GetService(); + var logger = loggerFactory.CreateLogger("測試"); + logger.LogInformation("選用測試專案的的注入設定"); + var connectionString = + "Host=localhost;Port=5432;Database=employee_test;Username=postgres;Password=guest;"; + + // var connectionString = sp.GetService().Value; + options.UseNpgsql(connectionString, //只會呼叫一次 + builder => builder.EnableRetryOnFailure( + 10, + TimeSpan.FromSeconds(30), + new List { "57P01" }) + ) + + // .UseLazyLoadingProxies() + .UseSnakeCaseNamingConvention() + .EnableSensitiveDataLogging() + .UseLoggerFactory(loggerFactory) + ; + + //.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + }); + return services; + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Lab.StepDependencyInjection.Test.csproj b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Lab.StepDependencyInjection.Test.csproj new file mode 100644 index 00000000..7b5b8861 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Lab.StepDependencyInjection.Test.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + + + + + + diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs new file mode 100644 index 00000000..81f659f7 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs @@ -0,0 +1,59 @@ +using Lab.StepDependencyInjection.Test.EntityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using SolidToken.SpecFlow.DependencyInjection; +using TechTalk.SpecFlow; + +namespace Lab.StepDependencyInjection.Test.Steps; + +[Binding] +[Scope(Feature = "Demo0")] +public class Demo0Steps +{ + private readonly FileProvider _fileProvider; + + private ScenarioContext ScenarioContext { get; } + + private IDbContextFactory _dbContextFactory; + + public Demo0Steps(ScenarioContext scenarioContext, + FileProvider fileProvider, + IDbContextFactory dbContextFactory) + { + this.ScenarioContext = scenarioContext; + this._fileProvider = fileProvider; + this._dbContextFactory = dbContextFactory; + } + + [ScenarioDependencies] + public static IServiceCollection CreateServices() + { + return InstanceManager.InitializeServices(); + } + + [When(@"寫入資料表")] + public void When寫入資料表() + { + // var dbContextFactory = GlobalSteps.ServiceProvider.GetService>(); + var dbContextFactory = this._dbContextFactory; + using var dbContext = dbContextFactory.CreateDbContext(); + dbContext.Add(new Employee + { + Id = Guid.NewGuid(), + Name = "yao", + Age = 19, + CreateAt = DateTime.UtcNow, + CreateBy = "yao" + }); + dbContext.SaveChanges(); + } + + [When(@"讀取資料表")] + public void When讀取資料表() + { + // var dbContextFactory = GlobalSteps.ServiceProvider.GetService>(); + var dbContextFactory = this._dbContextFactory; + using var dbContext = dbContextFactory.CreateDbContext(); + var employees = dbContext.Employees.AsNoTracking().ToList(); + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/SysFileProvider.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/SysFileProvider.cs new file mode 100644 index 00000000..b95858c1 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/SysFileProvider.cs @@ -0,0 +1,16 @@ +namespace Lab.StepDependencyInjection.Test; + +public class SysFileProvider +{ + private readonly string _name; + + public SysFileProvider(string name) + { + this._name = name; + } + + public string GetPath() + { + return $"{this._name}:SysFileProvider"; + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/UnitTest1.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/UnitTest1.cs new file mode 100644 index 00000000..359bd5b8 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace Lab.StepDependencyInjection.Test; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestMethod1() + { + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Usings.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/specflow.json b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/specflow.json new file mode 100644 index 00000000..06565c4c --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/specflow.json @@ -0,0 +1,10 @@ +{ + "language": { + "feature": "en-US" + }, + "stepAssemblies": [ + { + "assembly": "Allure.SpecFlowPlugin" + } + ] +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjectionAndDbContext.sln b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjectionAndDbContext.sln new file mode 100644 index 00000000..cd4d1aec --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjectionAndDbContext.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.StepDependencyInjection.Test", "Lab.StepDependencyInjection.Test\Lab.StepDependencyInjection.Test.csproj", "{B44A3BAF-10A5-499A-B1D4-48F9BA333884}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B44A3BAF-10A5-499A-B1D4-48F9BA333884}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B44A3BAF-10A5-499A-B1D4-48F9BA333884}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B44A3BAF-10A5-499A-B1D4-48F9BA333884}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B44A3BAF-10A5-499A-B1D4-48F9BA333884}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From aa719c100e657be9fa1e168bf4188768a1075842 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 14 Dec 2022 22:41:49 +0800 Subject: [PATCH 306/424] =?UTF-8?q?dbcontext=20pool=20=E7=9A=84=E7=94=9F?= =?UTF-8?q?=E5=91=BD=E9=80=B1=E6=9C=9F=E8=A8=AD=E5=AE=9A=E7=82=BA=20Transi?= =?UTF-8?q?ent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/Demo0.feature.cs | 4 ++-- .../Lab.StepDependencyInjection.Test/InstanceManager.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs index d6d97f0a..b8d63808 100644 --- a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo0.feature.cs @@ -110,10 +110,10 @@ public void 測試步驟注入() { this.ScenarioStart(); #line 4 - testRunner.When("取得檔案路徑", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); + testRunner.When("寫入資料表", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line hidden #line 5 - testRunner.Then("預期得到 \"File Provider\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); + testRunner.When("讀取資料表", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line hidden } this.ScenarioCleanup(); diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/InstanceManager.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/InstanceManager.cs index 287c2aac..66b8960a 100644 --- a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/InstanceManager.cs +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/InstanceManager.cs @@ -36,7 +36,7 @@ public static IServiceCollection InitializeServices() ; //.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); - }); + }, lifetime: ServiceLifetime.Transient); return services; } } \ No newline at end of file From 1ad133e245ac24fb2fc32e31158080e944caee0f Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 10 Mar 2023 09:19:13 +0800 Subject: [PATCH 307/424] add project --- .../AppSettings.cs | 6 ++++ .../Controllers/DefaultController.cs | 26 ++++++++++++++++ .../EntityModel/Employee.cs | 2 +- .../EntityModel/EmployeeDbContext.cs | 2 +- .../EmployeeDbContextContextFactory.cs | 2 +- .../Lab.StepDependencyInjection.WebAPI.csproj | 16 ++++++++++ .../Program.cs | 31 +++++++++++++++++++ .../Properties/launchSettings.json | 31 +++++++++++++++++++ .../ServiceCollectionExtensions.cs | 22 +++++++++++++ .../WeatherForecast.cs | 12 +++++++ .../appsettings.Development.json | 8 +++++ .../appsettings.json | 9 ++++++ ...ab.StepDependencyInjectionAndDbContext.sln | 6 ++++ 13 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/AppSettings.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Controllers/DefaultController.cs rename Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/{Lab.StepDependencyInjection.Test => Lab.StepDependencyInjection.WebAPI}/EntityModel/Employee.cs (92%) rename Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/{Lab.StepDependencyInjection.Test => Lab.StepDependencyInjection.WebAPI}/EntityModel/EmployeeDbContext.cs (91%) rename Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/{Lab.StepDependencyInjection.Test => Lab.StepDependencyInjection.WebAPI}/EntityModel/EmployeeDbContextContextFactory.cs (90%) create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Lab.StepDependencyInjection.WebAPI.csproj create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Program.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Properties/launchSettings.json create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/ServiceCollectionExtensions.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/WeatherForecast.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/appsettings.Development.json create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/appsettings.json diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/AppSettings.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/AppSettings.cs new file mode 100644 index 00000000..07a7461b --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/AppSettings.cs @@ -0,0 +1,6 @@ +namespace Lab.StepDependencyInjection.WebAPI; + +public class AppSettings +{ + public string ConnectionString { get; init; } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Controllers/DefaultController.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Controllers/DefaultController.cs new file mode 100644 index 00000000..19d3a113 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Controllers/DefaultController.cs @@ -0,0 +1,26 @@ +using Lab.StepDependencyInjection.WebAPI.EntityModel; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace Lab.StepDependencyInjection.WebAPI.Controllers; + +[ApiController] +[Route("[controller]")] +public class DefaultController : ControllerBase +{ + IDbContextFactory _employeeDbContextFactory; + + public DefaultController(IDbContextFactory employeeDbContextFactory) + { + this._employeeDbContextFactory = employeeDbContextFactory; + } + + [HttpGet()] + public async Task Get() + { + await using var dbContext = await this._employeeDbContextFactory.CreateDbContextAsync(); + await dbContext.Database.EnsureCreatedAsync(); + var data = await dbContext.Employees.AsNoTracking().ToListAsync(); + return this.Ok(data); + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/Employee.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/EntityModel/Employee.cs similarity index 92% rename from Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/Employee.cs rename to Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/EntityModel/Employee.cs index bfa54054..99c9115e 100644 --- a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/Employee.cs +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/EntityModel/Employee.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Lab.StepDependencyInjection.Test.EntityModel +namespace Lab.StepDependencyInjection.WebAPI.EntityModel { [Table("Employee")] public class Employee diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/EmployeeDbContext.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/EntityModel/EmployeeDbContext.cs similarity index 91% rename from Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/EmployeeDbContext.cs rename to Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/EntityModel/EmployeeDbContext.cs index bd85ef9a..8df2c3fa 100644 --- a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/EmployeeDbContext.cs +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/EntityModel/EmployeeDbContext.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; -namespace Lab.StepDependencyInjection.Test.EntityModel +namespace Lab.StepDependencyInjection.WebAPI.EntityModel { public class EmployeeDbContext : DbContext { diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/EmployeeDbContextContextFactory.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/EntityModel/EmployeeDbContextContextFactory.cs similarity index 90% rename from Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/EmployeeDbContextContextFactory.cs rename to Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/EntityModel/EmployeeDbContextContextFactory.cs index 0e3850c0..4075e8c6 100644 --- a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/EntityModel/EmployeeDbContextContextFactory.cs +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/EntityModel/EmployeeDbContextContextFactory.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; -namespace Lab.StepDependencyInjection.Test.EntityModel; +namespace Lab.StepDependencyInjection.WebAPI.EntityModel; public class EmployeeDbContextContextFactory : IDesignTimeDbContextFactory { diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Lab.StepDependencyInjection.WebAPI.csproj b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Lab.StepDependencyInjection.WebAPI.csproj new file mode 100644 index 00000000..19c25f17 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Lab.StepDependencyInjection.WebAPI.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + + + + + + + + + + diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Program.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Program.cs new file mode 100644 index 00000000..bedf26bc --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Program.cs @@ -0,0 +1,31 @@ +using Lab.StepDependencyInjection.WebAPI; +using Lab.StepDependencyInjection.WebAPI.EntityModel; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.ConfigureServices(); +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); +public partial class Program { } diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Properties/launchSettings.json b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Properties/launchSettings.json new file mode 100644 index 00000000..d33a047d --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:58129", + "sslPort": 44362 + } + }, + "profiles": { + "Lab.StepDependencyInjection.WebAPI": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7013;http://localhost:5088", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/ServiceCollectionExtensions.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..e02cabb8 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/ServiceCollectionExtensions.cs @@ -0,0 +1,22 @@ +using Lab.StepDependencyInjection.WebAPI.EntityModel; +using Microsoft.EntityFrameworkCore; + +namespace Lab.StepDependencyInjection.WebAPI; + +public static class ServiceCollectionExtension +{ + public static IServiceCollection ConfigureServices(this IServiceCollection services) + { + services.AddSingleton(p => new AppSettings + { + ConnectionString = "Host=localhost;Port=5432;Database=employee;Username=postgres;Password=guest" + }); + services.AddDbContextFactory((provider, builder) => + { + var appSettings = provider.GetService(); + var connectionString = appSettings.ConnectionString; + builder.UseNpgsql(connectionString); + }); + return services; + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/WeatherForecast.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/WeatherForecast.cs new file mode 100644 index 00000000..1635976b --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace Lab.StepDependencyInjection.WebAPI; + +public class WeatherForecast +{ + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/appsettings.Development.json b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/appsettings.json b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjectionAndDbContext.sln b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjectionAndDbContext.sln index cd4d1aec..1edbba98 100644 --- a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjectionAndDbContext.sln +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjectionAndDbContext.sln @@ -2,6 +2,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.StepDependencyInjection.Test", "Lab.StepDependencyInjection.Test\Lab.StepDependencyInjection.Test.csproj", "{B44A3BAF-10A5-499A-B1D4-48F9BA333884}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.StepDependencyInjection.WebAPI", "Lab.StepDependencyInjection.WebAPI\Lab.StepDependencyInjection.WebAPI.csproj", "{794E332B-0A00-4FFB-A951-877B8DD4B927}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -12,5 +14,9 @@ Global {B44A3BAF-10A5-499A-B1D4-48F9BA333884}.Debug|Any CPU.Build.0 = Debug|Any CPU {B44A3BAF-10A5-499A-B1D4-48F9BA333884}.Release|Any CPU.ActiveCfg = Release|Any CPU {B44A3BAF-10A5-499A-B1D4-48F9BA333884}.Release|Any CPU.Build.0 = Release|Any CPU + {794E332B-0A00-4FFB-A951-877B8DD4B927}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {794E332B-0A00-4FFB-A951-877B8DD4B927}.Debug|Any CPU.Build.0 = Debug|Any CPU + {794E332B-0A00-4FFB-A951-877B8DD4B927}.Release|Any CPU.ActiveCfg = Release|Any CPU + {794E332B-0A00-4FFB-A951-877B8DD4B927}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From f1bb6bb7d0879a850876b021e7703edf16ba6620 Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 10 Mar 2023 23:55:44 +0800 Subject: [PATCH 308/424] add test case --- .../Features/Demo1.feature | 4 + .../Features/Demo1.feature.cs | 121 ++++++++++++++++++ .../GlobalSteps.cs | 4 +- .../InstanceManager.cs | 2 +- .../Lab.StepDependencyInjection.Test.csproj | 27 ++-- .../Steps/Demo0Steps.cs | 2 +- .../Steps/Demo1Steps.cs | 16 +++ .../TestServer.cs | 21 +++ .../Controllers/DefaultController.cs | 4 +- 9 files changed, 183 insertions(+), 18 deletions(-) create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo1.feature create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo1.feature.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Steps/Demo1Steps.cs create mode 100644 Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/TestServer.cs diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo1.feature b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo1.feature new file mode 100644 index 00000000..9dde1711 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo1.feature @@ -0,0 +1,4 @@ +Feature: Demo1 + + Scenario: 測試步驟注入 + When 呼叫 Web API \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo1.feature.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo1.feature.cs new file mode 100644 index 00000000..54cc09bc --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Features/Demo1.feature.cs @@ -0,0 +1,121 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (https://www.specflow.org/). +// SpecFlow Version:3.9.0.0 +// SpecFlow Generator Version:3.9.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace Lab.StepDependencyInjection.Test.Features +{ + using TechTalk.SpecFlow; + using System; + using System.Linq; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute()] + public partial class Demo1Feature + { + + private static TechTalk.SpecFlow.ITestRunner testRunner; + + private Microsoft.VisualStudio.TestTools.UnitTesting.TestContext _testContext; + + private static string[] featureTags = ((string[])(null)); + +#line 1 "Demo1.feature" +#line hidden + + public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestContext + { + get + { + return this._testContext; + } + set + { + this._testContext = value; + } + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute()] + public static void FeatureSetup(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext testContext) + { + testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); + TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Features", "Demo1", null, ProgrammingLanguage.CSharp, featureTags); + testRunner.OnFeatureStart(featureInfo); + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute()] + public static void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute()] + public void TestInitialize() + { + if (((testRunner.FeatureContext != null) + && (testRunner.FeatureContext.FeatureInfo.Title != "Demo1"))) + { + global::Lab.StepDependencyInjection.Test.Features.Demo1Feature.FeatureSetup(null); + } + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute()] + public void TestTearDown() + { + testRunner.OnScenarioEnd(); + } + + public void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioInitialize(scenarioInfo); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(_testContext); + } + + public void ScenarioStart() + { + testRunner.OnScenarioStart(); + } + + public void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()] + [Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("測試步驟注入")] + [Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "Demo1")] + public void 測試步驟注入() + { + string[] tagsOfScenario = ((string[])(null)); + System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("測試步驟注入", null, tagsOfScenario, argumentsOfScenario, featureTags); +#line 3 + this.ScenarioInitialize(scenarioInfo); +#line hidden + if ((TagHelper.ContainsIgnoreTag(tagsOfScenario) || TagHelper.ContainsIgnoreTag(featureTags))) + { + testRunner.SkipScenario(); + } + else + { + this.ScenarioStart(); +#line 4 + testRunner.When("呼叫 Web API", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line hidden + } + this.ScenarioCleanup(); + } + } +} +#pragma warning restore +#endregion diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/GlobalSteps.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/GlobalSteps.cs index 8bd0c3e2..0d1884a4 100644 --- a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/GlobalSteps.cs +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/GlobalSteps.cs @@ -1,9 +1,9 @@ -using Lab.StepDependencyInjection.Test.EntityModel; +using Lab.StepDependencyInjection.WebAPI.EntityModel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using TechTalk.SpecFlow; -namespace Lab.StepDependencyInjection.Test.Steps; +namespace Lab.StepDependencyInjection.Test; [Binding] public class GlobalSteps diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/InstanceManager.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/InstanceManager.cs index 66b8960a..8759d833 100644 --- a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/InstanceManager.cs +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/InstanceManager.cs @@ -1,4 +1,4 @@ -using Lab.StepDependencyInjection.Test.EntityModel; +using Lab.StepDependencyInjection.WebAPI.EntityModel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Lab.StepDependencyInjection.Test.csproj b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Lab.StepDependencyInjection.Test.csproj index 7b5b8861..f570fb3f 100644 --- a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Lab.StepDependencyInjection.Test.csproj +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Lab.StepDependencyInjection.Test.csproj @@ -9,18 +9,21 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs index 81f659f7..e1f80317 100644 --- a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Steps/Demo0Steps.cs @@ -1,4 +1,4 @@ -using Lab.StepDependencyInjection.Test.EntityModel; +using Lab.StepDependencyInjection.WebAPI.EntityModel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using SolidToken.SpecFlow.DependencyInjection; diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Steps/Demo1Steps.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Steps/Demo1Steps.cs new file mode 100644 index 00000000..8a9f895e --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/Steps/Demo1Steps.cs @@ -0,0 +1,16 @@ +using TechTalk.SpecFlow; + +namespace Lab.StepDependencyInjection.Test.Steps; + +[Binding] +public class Demo1Steps +{ + [When(@"呼叫 Web API")] + public async Task When呼叫WebApi() + { + var testServer = new TestServer(); + var client = testServer.CreateClient(); + var response = await client.GetAsync("Default"); + var response1 = await client.GetAsync("Demo"); + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/TestServer.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/TestServer.cs new file mode 100644 index 00000000..a995a168 --- /dev/null +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.Test/TestServer.cs @@ -0,0 +1,21 @@ +using Lab.StepDependencyInjection.WebAPI; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.StepDependencyInjection.Test; + +public class TestServer : WebApplicationFactory +{ + private void ConfigureServices(IServiceCollection services) + { + services.AddLogging(p => p.AddConsole()); + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureServices(this.ConfigureServices); + builder.UseSetting("https_port", "9527"); + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Controllers/DefaultController.cs b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Controllers/DefaultController.cs index 19d3a113..fa82210c 100644 --- a/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Controllers/DefaultController.cs +++ b/Test/Specflow3/Lab.StepDependencyInjectionAndDbContext/Lab.StepDependencyInjection.WebAPI/Controllers/DefaultController.cs @@ -8,7 +8,7 @@ namespace Lab.StepDependencyInjection.WebAPI.Controllers; [Route("[controller]")] public class DefaultController : ControllerBase { - IDbContextFactory _employeeDbContextFactory; + private readonly IDbContextFactory _employeeDbContextFactory; public DefaultController(IDbContextFactory employeeDbContextFactory) { @@ -20,7 +20,7 @@ public async Task Get() { await using var dbContext = await this._employeeDbContextFactory.CreateDbContextAsync(); await dbContext.Database.EnsureCreatedAsync(); - var data = await dbContext.Employees.AsNoTracking().ToListAsync(); + var data = await dbContext.Employees.Where(p => p.Age == 10).AsNoTracking().ToListAsync(); return this.Ok(data); } } \ No newline at end of file From 856015c5dd7d2a00007e8f949498ac89e20b3ceb Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 24 Mar 2023 00:06:37 +0800 Subject: [PATCH 309/424] add feature toggle project --- .../DemoUnitTest.cs | 40 ++++++++++++ .../Lab.FeatureToggle.TestProject.csproj | 30 +++++++++ .../Lab.FeatureToggle.TestProject/Usings.cs | 1 + .../appsettings.json | 23 +++++++ .../Controllers/DemoController.cs | 33 ++++++++++ .../Lab.FeatureToggle.WebAPI/Demo.cs | 35 ++++++++++ .../DemoAsyncActionFilter.cs | 16 +++++ .../DemoFeatureFilter.cs | 12 ++++ .../Lab.FeatureToggle.WebAPI/FeatureFlags.cs | 8 +++ .../Lab.FeatureToggle.WebAPI.csproj | 15 +++++ .../Lab.FeatureToggle.WebAPI/Program.cs | 65 +++++++++++++++++++ .../Properties/launchSettings.json | 41 ++++++++++++ .../WeatherForecast.cs | 12 ++++ .../appsettings.Development.json | 8 +++ .../Lab.FeatureToggle.WebAPI/appsettings.json | 23 +++++++ .../Lab.FeatureToggle/Lab.FeatureToggle.sln | 22 +++++++ 16 files changed, 384 insertions(+) create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/DemoUnitTest.cs create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/Lab.FeatureToggle.TestProject.csproj create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/Usings.cs create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/appsettings.json create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Controllers/DemoController.cs create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Demo.cs create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/DemoAsyncActionFilter.cs create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/DemoFeatureFilter.cs create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/FeatureFlags.cs create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Lab.FeatureToggle.WebAPI.csproj create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Program.cs create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Properties/launchSettings.json create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/WeatherForecast.cs create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/appsettings.Development.json create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/appsettings.json create mode 100644 Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.sln diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/DemoUnitTest.cs b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/DemoUnitTest.cs new file mode 100644 index 00000000..590d1e06 --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/DemoUnitTest.cs @@ -0,0 +1,40 @@ +using Lab.FeatureToggle.WebAPI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.FeatureManagement; + +namespace Lab.FeatureToggle.TestProject; + +[TestClass] +public class DemoUnitTest +{ + [TestMethod] + public async Task CreateFeatureA() + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddFeatureManagement(configBuilder.Build()); + var serviceProvider = services.BuildServiceProvider(); + var target = serviceProvider.GetService(); + var actual = await target.CreateFeatureA(); + Assert.AreEqual("OK", actual); + } + + [TestMethod] + public async Task CreateFeatureB() + { + var configBuilder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + var services = new ServiceCollection(); + services.AddSingleton(); + services.AddFeatureManagement(configBuilder.Build()); + var serviceProvider = services.BuildServiceProvider(); + var target = serviceProvider.GetService(); + var actual = await target.CreateFeatureB(); + Assert.AreEqual(null, actual); + } +} \ No newline at end of file diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/Lab.FeatureToggle.TestProject.csproj b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/Lab.FeatureToggle.TestProject.csproj new file mode 100644 index 00000000..a870d683 --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/Lab.FeatureToggle.TestProject.csproj @@ -0,0 +1,30 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + true + PreserveNewest + PreserveNewest + + + + + + + diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/Usings.cs b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/appsettings.json b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/appsettings.json new file mode 100644 index 00000000..0b7b878a --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.TestProject/appsettings.json @@ -0,0 +1,23 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "FeatureManagement": { + "FeatureA": true, + "FeatureB": false, + "FeatureC": { + "EnabledFor": [ + { + "Name": "Percentage", + "Parameters": { + "Value": 50 + } + } + ] + } + }, + "AllowedHosts": "*" +} diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Controllers/DemoController.cs b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Controllers/DemoController.cs new file mode 100644 index 00000000..e402ea34 --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Controllers/DemoController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.FeatureManagement; +using Microsoft.FeatureManagement.Mvc; + +namespace Lab.FeatureToggle.WebAPI.Controllers; + +[ApiController] +[Route("[controller]")] +public class DemoController : ControllerBase +{ + + private readonly ILogger _logger; + private readonly IFeatureManager _featureManager; + public DemoController(ILogger logger, + IFeatureManager featureManager) + { + _logger = logger; + this._featureManager = featureManager; + } + + [HttpGet] + [FeatureGate(FeatureFlags.FeatureB)] + public async Task Get() + { + if (await _featureManager.IsEnabledAsync(FeatureFlags.FeatureA)) + { + // Run the following code + } + + return this.Ok(); + } +} + diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Demo.cs b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Demo.cs new file mode 100644 index 00000000..c4d40705 --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Demo.cs @@ -0,0 +1,35 @@ +using Microsoft.FeatureManagement; + +namespace Lab.FeatureToggle.WebAPI; + +public class Demo +{ + private readonly IFeatureManager _featureManager; + + public Demo(IFeatureManager featureManager) + { + this._featureManager = featureManager; + } + + public async Task CreateFeatureA() + { + if (await this._featureManager.IsEnabledAsync(FeatureFlags.FeatureA)) + { + //do something + return "OK"; + } + + return null; + } + public async Task CreateFeatureB() + { + if (await this._featureManager.IsEnabledAsync(FeatureFlags.FeatureB)) + { + //do something + return "OK"; + } + + return null; + } + +} \ No newline at end of file diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/DemoAsyncActionFilter.cs b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/DemoAsyncActionFilter.cs new file mode 100644 index 00000000..96825813 --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/DemoAsyncActionFilter.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Lab.FeatureToggle.WebAPI; + +public class DemoAsyncActionFilter : IAsyncActionFilter +{ + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + Console.WriteLine("on action execution"); + + // Do something before the action executes. + await next(); + + // Do something after the action executes. + } +} \ No newline at end of file diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/DemoFeatureFilter.cs b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/DemoFeatureFilter.cs new file mode 100644 index 00000000..bc7a50a3 --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/DemoFeatureFilter.cs @@ -0,0 +1,12 @@ +using Microsoft.FeatureManagement; + +namespace Lab.FeatureToggle.WebAPI; + +public class DemoFeatureFilter : IFeatureFilter +{ + public async Task EvaluateAsync(FeatureFilterEvaluationContext context) + { + // Your implementation here + return true; + } +} \ No newline at end of file diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/FeatureFlags.cs b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/FeatureFlags.cs new file mode 100644 index 00000000..d17f8be4 --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/FeatureFlags.cs @@ -0,0 +1,8 @@ +namespace Lab.FeatureToggle.WebAPI; + +public static class FeatureFlags +{ + public const string FeatureA = "FeatureA"; + public const string FeatureB = "FeatureB"; + public const string FeatureC = "FeatureC"; +} \ No newline at end of file diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Lab.FeatureToggle.WebAPI.csproj b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Lab.FeatureToggle.WebAPI.csproj new file mode 100644 index 00000000..ce5f9d3e --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Lab.FeatureToggle.WebAPI.csproj @@ -0,0 +1,15 @@ + + + + net7.0 + enable + enable + + + + + + + + + diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Program.cs b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Program.cs new file mode 100644 index 00000000..3ed18f56 --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Program.cs @@ -0,0 +1,65 @@ +using System.Net.Http.Headers; +using System.Text; +using Lab.FeatureToggle.WebAPI; +using Microsoft.AspNetCore.Mvc; +using Microsoft.FeatureManagement; +using Microsoft.FeatureManagement.FeatureFilters; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +var environmentName = builder.Environment.EnvironmentName; +builder.Configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); +builder.Configuration.AddJsonFile($"appsettings.{environmentName}.json", optional: true, reloadOnChange: true); +builder.Services.AddFeatureManagement(); +builder.Services.AddFeatureManagement() + .UseDisabledFeaturesHandler((features, context) => + { + context.Result = new ObjectResult(new + { + FailureCode = "FeatureDisabled", + FailureMessage = $"The feature {features.First()} is disabled.", + TraceId = context.HttpContext.TraceIdentifier + }) + { + StatusCode = 404 + }; + }); +builder.Services.AddFeatureManagement().AddFeatureFilter(); +builder.Services.AddFeatureManagement().AddFeatureFilter(); + +builder.Services.AddControllers(p => p.Filters.AddForFeature(FeatureFlags.FeatureB)); + +// builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); +app.UseForFeature(FeatureFlags.FeatureA, appBuilder => +{ + appBuilder.Use(async (context, next) => + { + Console.WriteLine("on middleware execution"); + + // Do something with the request + await next.Invoke(); + + // Do something with the response + }); +}); +app.Run(); \ No newline at end of file diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Properties/launchSettings.json b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Properties/launchSettings.json new file mode 100644 index 00000000..6669fd61 --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:63863", + "sslPort": 44364 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5259", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7052;http://localhost:5259", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/WeatherForecast.cs b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/WeatherForecast.cs new file mode 100644 index 00000000..d752eeb4 --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace Lab.FeatureToggle.WebAPI; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/appsettings.Development.json b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/appsettings.json b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/appsettings.json new file mode 100644 index 00000000..0b7b878a --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.WebAPI/appsettings.json @@ -0,0 +1,23 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "FeatureManagement": { + "FeatureA": true, + "FeatureB": false, + "FeatureC": { + "EnabledFor": [ + { + "Name": "Percentage", + "Parameters": { + "Value": 50 + } + } + ] + } + }, + "AllowedHosts": "*" +} diff --git a/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.sln b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.sln new file mode 100644 index 00000000..fdbcc79c --- /dev/null +++ b/Feature Toggle/Lab.FeatureToggle/Lab.FeatureToggle.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.FeatureToggle.WebAPI", "Lab.FeatureToggle.WebAPI\Lab.FeatureToggle.WebAPI.csproj", "{2CC9FEB5-2BF6-4E6A-998C-B26880EF3A72}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.FeatureToggle.TestProject", "Lab.FeatureToggle.TestProject\Lab.FeatureToggle.TestProject.csproj", "{B34F3DC3-E2D0-45A6-BE92-74DF131FE88B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2CC9FEB5-2BF6-4E6A-998C-B26880EF3A72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CC9FEB5-2BF6-4E6A-998C-B26880EF3A72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CC9FEB5-2BF6-4E6A-998C-B26880EF3A72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CC9FEB5-2BF6-4E6A-998C-B26880EF3A72}.Release|Any CPU.Build.0 = Release|Any CPU + {B34F3DC3-E2D0-45A6-BE92-74DF131FE88B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B34F3DC3-E2D0-45A6-BE92-74DF131FE88B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B34F3DC3-E2D0-45A6-BE92-74DF131FE88B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B34F3DC3-E2D0-45A6-BE92-74DF131FE88B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From ebd22aff2c6244b9090cb5917e7fa9e46523ce89 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 1 Apr 2023 12:40:15 +0800 Subject: [PATCH 310/424] add project --- .../Jetbrains HttpClient.sln | 16 +++++ .../Controllers/WeatherForecastController.cs | 58 +++++++++++++++++++ .../WebApplication1/Program.cs | 26 +++++++++ .../Properties/launchSettings.json | 41 +++++++++++++ .../WebApplication1/WeatherForecast.cs | 14 +++++ .../WebApplication1/WebApplication1.csproj | 14 +++++ .../appsettings.Development.json | 8 +++ .../WebApplication1/appsettings.json | 9 +++ .../WebApplication1/http-client.env.json | 8 +++ .../WebApplication1/test.http | 25 ++++++++ 10 files changed, 219 insertions(+) create mode 100644 Jetbrains HttpClient/Jetbrains HttpClient/Jetbrains HttpClient.sln create mode 100644 Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/Controllers/WeatherForecastController.cs create mode 100644 Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/Program.cs create mode 100644 Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/Properties/launchSettings.json create mode 100644 Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/WeatherForecast.cs create mode 100644 Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/WebApplication1.csproj create mode 100644 Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/appsettings.Development.json create mode 100644 Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/appsettings.json create mode 100644 Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/http-client.env.json create mode 100644 Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/test.http diff --git a/Jetbrains HttpClient/Jetbrains HttpClient/Jetbrains HttpClient.sln b/Jetbrains HttpClient/Jetbrains HttpClient/Jetbrains HttpClient.sln new file mode 100644 index 00000000..5402b47d --- /dev/null +++ b/Jetbrains HttpClient/Jetbrains HttpClient/Jetbrains HttpClient.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApplication1", "WebApplication1\WebApplication1.csproj", "{EC875AA1-C496-41D9-A613-BBDF2D0CE3CF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EC875AA1-C496-41D9-A613-BBDF2D0CE3CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC875AA1-C496-41D9-A613-BBDF2D0CE3CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC875AA1-C496-41D9-A613-BBDF2D0CE3CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC875AA1-C496-41D9-A613-BBDF2D0CE3CF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/Controllers/WeatherForecastController.cs b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..3237925c --- /dev/null +++ b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/Controllers/WeatherForecastController.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Mvc; + +namespace WebApplication1.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + + [HttpGet] + [Route("get:id")] + public ActionResult GetId() + { + return this.Ok(new + { + Id = new Random().Next(1, 5) + }); + } + + [HttpGet] + [Route("{id:int}")] + public ActionResult Get(int id) + { + var data = Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Id = index, + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + var result = data.FirstOrDefault(p => p.Id == id); + return this.Ok(result); + } +} \ No newline at end of file diff --git a/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/Program.cs b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/Program.cs new file mode 100644 index 00000000..329fe361 --- /dev/null +++ b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/Program.cs @@ -0,0 +1,26 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/Properties/launchSettings.json b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/Properties/launchSettings.json new file mode 100644 index 00000000..df2005f4 --- /dev/null +++ b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:14966", + "sslPort": 44347 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5109", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7072;http://localhost:5109", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/WeatherForecast.cs b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/WeatherForecast.cs new file mode 100644 index 00000000..f9419da2 --- /dev/null +++ b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/WeatherForecast.cs @@ -0,0 +1,14 @@ +namespace WebApplication1; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + + public int Id { get; set; } +} \ No newline at end of file diff --git a/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/WebApplication1.csproj b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/WebApplication1.csproj new file mode 100644 index 00000000..fe2231a3 --- /dev/null +++ b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/WebApplication1.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + enable + enable + + + + + + + + diff --git a/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/appsettings.Development.json b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/appsettings.json b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/http-client.env.json b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/http-client.env.json new file mode 100644 index 00000000..7b87960a --- /dev/null +++ b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/http-client.env.json @@ -0,0 +1,8 @@ +{ + "Dev": { + "BaseUrl": "https://localhost:44347/" + }, + "Staging": { + "BaseUrl": "https://staging.example.com/" + } +} \ No newline at end of file diff --git a/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/test.http b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/test.http new file mode 100644 index 00000000..7df4dd90 --- /dev/null +++ b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/test.http @@ -0,0 +1,25 @@ + +### 宣告變數 +< {% + request.variables.set("BaseUrl", "https://localhost:44347/") +%} +GET {{BaseUrl}}/WeatherForecast + + +### 取得所有 +GET {{BaseUrl}}/WeatherForecast + +### 取得id +GET {{BaseUrl}}/WeatherForecast/get:id + +> {% + client.global.set("id", response.body.id); + client.log("result: " + response.body.id); + client.test("Request executed successfully", function () { + client.log(response.body.id); + client.assert(response.status === 201, "Response status is not 200"); + }); +%} + +### 依照上一動作取得的 id 取得資料 +GET {{BaseUrl}}/WeatherForecast/{{id}} \ No newline at end of file From 3a55d590d0539d1fb9e3ab1a64300f1bf53f2f16 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 27 Apr 2023 13:33:01 +0800 Subject: [PATCH 311/424] add project --- .../Lab.SpectreConsole/Lab.SpectreConsole.sln | 16 +++++ .../Lab.SpectreConsole/AddSettings.cs | 41 +++++++++++ .../CancellableAsyncCommand.cs | 72 +++++++++++++++++++ .../FileSizeAsyncCommand.cs | 55 ++++++++++++++ .../Lab.SpectreConsole.csproj | 23 ++++++ .../Lab.SpectreConsole/Program.cs | 42 +++++++++++ 6 files changed, 249 insertions(+) create mode 100644 Args/Lab.SpectreConsole/Lab.SpectreConsole.sln create mode 100644 Args/Lab.SpectreConsole/Lab.SpectreConsole/AddSettings.cs create mode 100644 Args/Lab.SpectreConsole/Lab.SpectreConsole/CancellableAsyncCommand.cs create mode 100644 Args/Lab.SpectreConsole/Lab.SpectreConsole/FileSizeAsyncCommand.cs create mode 100644 Args/Lab.SpectreConsole/Lab.SpectreConsole/Lab.SpectreConsole.csproj create mode 100644 Args/Lab.SpectreConsole/Lab.SpectreConsole/Program.cs diff --git a/Args/Lab.SpectreConsole/Lab.SpectreConsole.sln b/Args/Lab.SpectreConsole/Lab.SpectreConsole.sln new file mode 100644 index 00000000..d8e6c9a4 --- /dev/null +++ b/Args/Lab.SpectreConsole/Lab.SpectreConsole.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SpectreConsole", "Lab.SpectreConsole\Lab.SpectreConsole.csproj", "{25BBDAD7-1286-4D7C-9AEA-948810AC146F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {25BBDAD7-1286-4D7C-9AEA-948810AC146F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25BBDAD7-1286-4D7C-9AEA-948810AC146F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25BBDAD7-1286-4D7C-9AEA-948810AC146F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25BBDAD7-1286-4D7C-9AEA-948810AC146F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Args/Lab.SpectreConsole/Lab.SpectreConsole/AddSettings.cs b/Args/Lab.SpectreConsole/Lab.SpectreConsole/AddSettings.cs new file mode 100644 index 00000000..d97e541e --- /dev/null +++ b/Args/Lab.SpectreConsole/Lab.SpectreConsole/AddSettings.cs @@ -0,0 +1,41 @@ +using System.ComponentModel; +using Spectre.Console.Cli; + +namespace Lab.SpectreConsole; +public class AddPackageCommand : Command +{ + public override int Execute(CommandContext context, AddPackageSettings settings) + { + // Omitted + return 0; + } +} + +public class AddReferenceCommand : Command +{ + public override int Execute(CommandContext context, AddReferenceSettings settings) + { + // Omitted + return 0; + } +} +public class AddSettings : CommandSettings +{ + [CommandArgument(0, "[PROJECT]")] + public string Project { get; set; } +} + +public class AddPackageSettings : AddSettings +{ + [CommandArgument(0, "")] + public string PackageName { get; set; } + + [CommandOption("-v|--version ")] + public string Version { get; set; } +} + +public class AddReferenceSettings : AddSettings +{ + [CommandArgument(0, "")] + public string ProjectReference { get; set; } +} \ No newline at end of file diff --git a/Args/Lab.SpectreConsole/Lab.SpectreConsole/CancellableAsyncCommand.cs b/Args/Lab.SpectreConsole/Lab.SpectreConsole/CancellableAsyncCommand.cs new file mode 100644 index 00000000..33740863 --- /dev/null +++ b/Args/Lab.SpectreConsole/Lab.SpectreConsole/CancellableAsyncCommand.cs @@ -0,0 +1,72 @@ +using Microsoft.Extensions.Logging; +using Spectre.Console.Cli; + +namespace Lab.SpectreConsole; + +public abstract class CancellableAsyncCommand : AsyncCommand + where TSettings : CommandSettings +{ + private readonly ILogger> _logger; + + protected CancellableAsyncCommand(ILogger> logger) + { + this._logger = logger; + } + + public abstract Task ExecuteAsync(CommandContext context, TSettings settings, CancellationToken cancellation); + + public override async Task ExecuteAsync(CommandContext context, TSettings settings) + { + using var cancellationSource = new CancellationTokenSource(); + + Console.CancelKeyPress += OnCancelKeyPress; + AppDomain.CurrentDomain.ProcessExit += OnProcessExit; + + using var _ = cancellationSource.Token.Register( + () => + { + AppDomain.CurrentDomain.ProcessExit -= OnProcessExit; + Console.CancelKeyPress -= OnCancelKeyPress; + } + ); + int exitCode = -1; + try + { + this._logger.LogInformation("執行任務中..."); + var executeTask = this.ExecuteAsync(context, settings, cancellationSource.Token); + exitCode = await executeTask; + this._logger.LogInformation("執行完成!!!"); + AppDomain.CurrentDomain.ProcessExit -= OnProcessExit; + Console.CancelKeyPress -= OnCancelKeyPress; + } + catch (OperationCanceledException) + { + exitCode = 0; + } + catch (Exception e) + { + this._logger.LogError(e, "執行命令時發生非預期的錯誤"); + } + + return exitCode; + + void OnCancelKeyPress(object? sender, ConsoleCancelEventArgs e) + { + // NOTE: cancel event, don't terminate the process + e.Cancel = true; + + cancellationSource.Cancel(); + } + + void OnProcessExit(object? sender, EventArgs e) + { + if (cancellationSource.IsCancellationRequested) + { + // NOTE: SIGINT (cancel key was pressed, this shouldn't ever actually hit however, as we remove the event handler upon cancellation of the `cancellationSource`) + return; + } + + cancellationSource.Cancel(); + } + } +} \ No newline at end of file diff --git a/Args/Lab.SpectreConsole/Lab.SpectreConsole/FileSizeAsyncCommand.cs b/Args/Lab.SpectreConsole/Lab.SpectreConsole/FileSizeAsyncCommand.cs new file mode 100644 index 00000000..fafa5355 --- /dev/null +++ b/Args/Lab.SpectreConsole/Lab.SpectreConsole/FileSizeAsyncCommand.cs @@ -0,0 +1,55 @@ +using System.ComponentModel; +using Microsoft.Extensions.Logging; +using Spectre.Console; +using Spectre.Console.Cli; + +namespace Lab.SpectreConsole; + +internal sealed class FileSizeAsyncCommand : CancellableAsyncCommand +{ + internal sealed class Settings : CommandSettings + { + [Description("Path to search. Defaults to current directory.")] + [CommandArgument(0, "[searchPath]")] + public string? SearchPath { get; init; } + + [CommandOption("-p|--pattern")] + public string? SearchPattern { get; init; } + + [CommandOption("--hidden")] + [DefaultValue(true)] + public bool IncludeHidden { get; init; } + } + + public override async Task ExecuteAsync(CommandContext context, + Settings settings, + CancellationToken cancellation) + { + await Task.Delay(5000, cancellation); + + var searchOptions = new EnumerationOptions + { + AttributesToSkip = settings.IncludeHidden + ? FileAttributes.Hidden | FileAttributes.System + : FileAttributes.System + }; + + var searchPattern = settings.SearchPattern ?? "*.*"; + var searchPath = settings.SearchPath ?? Directory.GetCurrentDirectory(); + var files = new DirectoryInfo(searchPath) + .GetFiles(searchPattern, searchOptions); + + var totalFileSize = files + .Sum(fileInfo => fileInfo.Length); + + AnsiConsole.MarkupLine( + $"Total file size for [green]{searchPattern}[/] files in [green]{searchPath}[/]: [blue]{totalFileSize:N0}[/] bytes"); + + return 0; + } + + public FileSizeAsyncCommand(ILogger> logger) + : base(logger) + { + } +} \ No newline at end of file diff --git a/Args/Lab.SpectreConsole/Lab.SpectreConsole/Lab.SpectreConsole.csproj b/Args/Lab.SpectreConsole/Lab.SpectreConsole/Lab.SpectreConsole.csproj new file mode 100644 index 00000000..784b0165 --- /dev/null +++ b/Args/Lab.SpectreConsole/Lab.SpectreConsole/Lab.SpectreConsole.csproj @@ -0,0 +1,23 @@ + + + + Exe + net7.0 + enable + enable + app + + + + + + + + + + + + + + + diff --git a/Args/Lab.SpectreConsole/Lab.SpectreConsole/Program.cs b/Args/Lab.SpectreConsole/Lab.SpectreConsole/Program.cs new file mode 100644 index 00000000..07291e12 --- /dev/null +++ b/Args/Lab.SpectreConsole/Lab.SpectreConsole/Program.cs @@ -0,0 +1,42 @@ +// See https://aka.ms/new-console-template for more information + +using Lab.SpectreConsole; +using Microsoft.Extensions.Hosting; +using Serilog; +using Serilog.Templates; +using Spectre.Console.Extensions.Hosting; + +// var formatter = new CompactJsonFormatter(); +var formatter = new ExpressionTemplate( + "{ {_t: @t, _msg: @m, _props: @p} }\n"); +Log.Logger = new LoggerConfiguration() + // .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console(formatter) + .WriteTo.File(formatter, "logs/host-.txt", rollingInterval: RollingInterval.Hour) + .CreateBootstrapLogger(); + +var currentDomain = AppDomain.CurrentDomain; +currentDomain.UnhandledException += (_, eventArgs) => +{ + var e = (Exception)eventArgs.ExceptionObject; + Log.Error(e, "執行命令時發生非預期的錯誤"); +}; + +try +{ + Log.Information("程序開始"); + await Host.CreateDefaultBuilder(args) + .UseSerilog() + .UseConsoleLifetime() + .UseSpectreConsole(config => { config.AddCommand("filesize"); }) + .RunConsoleAsync() + ; + Console.WriteLine("程序結束"); + return Environment.ExitCode; +} +catch (Exception e) +{ + Log.Error(e, "執行命令時發生非預期的錯誤"); + return -1; +} \ No newline at end of file From 257ff3b91651b01c9c33778b642c10b3318589a6 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 11 May 2023 09:12:38 +0800 Subject: [PATCH 312/424] add Lab.JsonMergePatch project --- Json/Lab.JsonMergePatch/.gitignore | 1 + .../Lab.JsonMergePatch/Lab.JsonMergePatch.sln | 16 +++++ .../Controllers/EmployeeController.cs | 59 +++++++++++++++++++ .../JsonSerializeFactory.cs | 25 ++++++++ .../Lab.JsonMergePatch.csproj | 16 +++++ .../Lab.JsonMergePatch/Models/Address.cs | 10 ++++ .../Lab.JsonMergePatch/Models/Employee.cs | 22 +++++++ .../Lab.JsonMergePatch/Models/Name.cs | 10 ++++ .../Models/PatchEmployeeRequest.cs | 33 +++++++++++ .../Lab.JsonMergePatch/Program.cs | 34 +++++++++++ .../appsettings.Development.json | 8 +++ .../Lab.JsonMergePatch/appsettings.json | 9 +++ 12 files changed, 243 insertions(+) create mode 100644 Json/Lab.JsonMergePatch/.gitignore create mode 100644 Json/Lab.JsonMergePatch/Lab.JsonMergePatch.sln create mode 100644 Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Controllers/EmployeeController.cs create mode 100644 Json/Lab.JsonMergePatch/Lab.JsonMergePatch/JsonSerializeFactory.cs create mode 100644 Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Lab.JsonMergePatch.csproj create mode 100644 Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/Address.cs create mode 100644 Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/Employee.cs create mode 100644 Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/Name.cs create mode 100644 Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/PatchEmployeeRequest.cs create mode 100644 Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Program.cs create mode 100644 Json/Lab.JsonMergePatch/Lab.JsonMergePatch/appsettings.Development.json create mode 100644 Json/Lab.JsonMergePatch/Lab.JsonMergePatch/appsettings.json diff --git a/Json/Lab.JsonMergePatch/.gitignore b/Json/Lab.JsonMergePatch/.gitignore new file mode 100644 index 00000000..031950aa --- /dev/null +++ b/Json/Lab.JsonMergePatch/.gitignore @@ -0,0 +1 @@ +launchSettings.json diff --git a/Json/Lab.JsonMergePatch/Lab.JsonMergePatch.sln b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch.sln new file mode 100644 index 00000000..8ade454a --- /dev/null +++ b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.JsonMergePatch", "Lab.JsonMergePatch\Lab.JsonMergePatch.csproj", "{ED23C1AA-6707-4019-B427-EEEEB754B140}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ED23C1AA-6707-4019-B427-EEEEB754B140}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED23C1AA-6707-4019-B427-EEEEB754B140}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED23C1AA-6707-4019-B427-EEEEB754B140}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED23C1AA-6707-4019-B427-EEEEB754B140}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Controllers/EmployeeController.cs b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Controllers/EmployeeController.cs new file mode 100644 index 00000000..ba488b42 --- /dev/null +++ b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Controllers/EmployeeController.cs @@ -0,0 +1,59 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Lab.JsonMergePatch.Models; +using Microsoft.AspNetCore.Mvc; +using Morcatko.AspNetCore.JsonMergePatch; +using Swashbuckle.AspNetCore.Filters; + +namespace Lab.JsonMergePatch.Controllers; + +[ApiController] +[Route("[controller]")] +public class EmployeeController : ControllerBase +{ + private readonly ILogger _logger; + private readonly JsonSerializerOptions _jsonSerializerOptions; + + public EmployeeController(ILogger logger, + JsonSerializerOptions jsonSerializerOptions) + { + this._logger = logger; + this._jsonSerializerOptions = jsonSerializerOptions; + } + + [HttpGet] + public async Task Get() + { + return this.Ok(this.GetEmployee()); + } + + [HttpPatch] + [SwaggerRequestExample(typeof(PatchEmployeeRequest), typeof(PatchEmployeeRequest.PatchEmployeeRequestExample))] + public async Task Patch(JsonMergePatchDocument request) + { + var original = this.GetEmployee(); + var patchResult = request.ApplyToT(original); + return this.Ok(patchResult); + } + + Employee GetEmployee() + { + var now = DateTimeOffset.Now; + var userId = "Sys"; + return new Employee + { + Id = Guid.NewGuid(), + Address = new Address + { + Address1 = "台北市", + Address2 = "大安區", + Street = "忠孝東路" + }, + Birthday = new DateTime(2009, 12, 25), + CreatedAt = now, + CreatedBy = userId, + ModifiedAt = now, + ModifiedBy = userId, + }; + } +} \ No newline at end of file diff --git a/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/JsonSerializeFactory.cs b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/JsonSerializeFactory.cs new file mode 100644 index 00000000..a62de48c --- /dev/null +++ b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/JsonSerializeFactory.cs @@ -0,0 +1,25 @@ +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Lab.JsonMergePatch; + +public class JsonSerializeFactory +{ + public static JsonSerializerOptions CreateDefaultOptions() + { + var options = new JsonSerializerOptions(); + Apply(options); + return options; + } + + public static JsonSerializerOptions Apply(JsonSerializerOptions options) + { + options.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; + options.PropertyNameCaseInsensitive = true; + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; + options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + return options; + } +} \ No newline at end of file diff --git a/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Lab.JsonMergePatch.csproj b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Lab.JsonMergePatch.csproj new file mode 100644 index 00000000..62fc19d6 --- /dev/null +++ b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Lab.JsonMergePatch.csproj @@ -0,0 +1,16 @@ + + + + net7.0 + enable + enable + + + + + + + + + + diff --git a/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/Address.cs b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/Address.cs new file mode 100644 index 00000000..d79b75ed --- /dev/null +++ b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/Address.cs @@ -0,0 +1,10 @@ +namespace Lab.JsonMergePatch.Models; + +public class Address +{ + public string? Address1 { get; set; } + + public string? Address2 { get; set; } + + public string? Street { get; set; } +} \ No newline at end of file diff --git a/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/Employee.cs b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/Employee.cs new file mode 100644 index 00000000..f479776b --- /dev/null +++ b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/Employee.cs @@ -0,0 +1,22 @@ +namespace Lab.JsonMergePatch.Models; + +public class Employee +{ + public Guid Id { get; set; } + + public string? Name { get; set; } + + public Address? Address { get; set; } + + public DateTime? Birthday { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset? ModifiedAt { get; set; } + + public string? ModifiedBy { get; set; } + + public List Extensions { get; set; } = new(); +} \ No newline at end of file diff --git a/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/Name.cs b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/Name.cs new file mode 100644 index 00000000..5c42fd23 --- /dev/null +++ b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/Name.cs @@ -0,0 +1,10 @@ +namespace Lab.JsonMergePatch.Models; + +public class Name +{ + public string FirstName { get; set; } + + public string LastName { get; set; } + + public string NickName { get; set; } +} \ No newline at end of file diff --git a/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/PatchEmployeeRequest.cs b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/PatchEmployeeRequest.cs new file mode 100644 index 00000000..917bfdf9 --- /dev/null +++ b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Models/PatchEmployeeRequest.cs @@ -0,0 +1,33 @@ +using Swashbuckle.AspNetCore.Filters; + +namespace Lab.JsonMergePatch.Models; + +public class PatchEmployeeRequest +{ + public string? Name { get; set; } + + public Address? Address { get; set; } + + public DateTime? Birthday { get; set; } + + public List Extensions { get; set; } = new(); + + public class PatchEmployeeRequestExample : IExamplesProvider + { + public PatchEmployeeRequest GetExamples() + { + return new PatchEmployeeRequest + { + Name = "小章", + Address = new Address + { + Address1 = "台北市", + Address2 = "大安區", + Street = "忠孝東路" + }, + Birthday = DateTime.Today, + Extensions = new List() { "ex1", "ex2" } + }; + } + } +} \ No newline at end of file diff --git a/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Program.cs b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Program.cs new file mode 100644 index 00000000..4acf21f3 --- /dev/null +++ b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/Program.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using Lab.JsonMergePatch; +using Morcatko.AspNetCore.JsonMergePatch; +using Swashbuckle.AspNetCore.Filters; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers() + .AddJsonOptions(opts => JsonSerializeFactory.Apply(opts.JsonSerializerOptions)) + .AddSystemTextJsonMergePatch(p => p.EnableDelete = true); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(p => p.ExampleFilters()); +builder.Services.AddSwaggerExamplesFromAssemblies(Assembly.GetEntryAssembly()); +builder.Services.AddSingleton(p => JsonSerializeFactory.CreateDefaultOptions()); +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/appsettings.Development.json b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/appsettings.json b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Json/Lab.JsonMergePatch/Lab.JsonMergePatch/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From be2f6c7e7e220e5d91c9464e2b0321d796a576ce Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 3 Aug 2023 19:52:55 +0800 Subject: [PATCH 313/424] refactor --- .../Lab.GracefulShutdown/.dockerignore | 25 +++++++++ .../Lab.GracefulShutdown.Net6/Dockerfile | 18 +++++++ .../GracefulShutdownService.cs | 25 +++++---- .../GracefulShutdownService1.cs | 28 ++++++++++ .../Lab.GracefulShutdown.Net6.csproj | 10 ++++ .../Lab.GracefulShutdown.Net6/Program.cs | 54 ++++++++++++------- 6 files changed, 133 insertions(+), 27 deletions(-) create mode 100644 Graceful Shutdown/Lab.GracefulShutdown/.dockerignore create mode 100644 Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Dockerfile create mode 100644 Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService1.cs diff --git a/Graceful Shutdown/Lab.GracefulShutdown/.dockerignore b/Graceful Shutdown/Lab.GracefulShutdown/.dockerignore new file mode 100644 index 00000000..cd967fc3 --- /dev/null +++ b/Graceful Shutdown/Lab.GracefulShutdown/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Dockerfile b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Dockerfile new file mode 100644 index 00000000..2d8402c0 --- /dev/null +++ b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["Lab.GracefulShutdown.Net6/Lab.GracefulShutdown.Net6.csproj", "Lab.GracefulShutdown.Net6/"] +RUN dotnet restore "Lab.GracefulShutdown.Net6/Lab.GracefulShutdown.Net6.csproj" +COPY . . +WORKDIR "/src/Lab.GracefulShutdown.Net6" +RUN dotnet build "Lab.GracefulShutdown.Net6.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Lab.GracefulShutdown.Net6.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Lab.GracefulShutdown.Net6.dll"] diff --git a/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService.cs b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService.cs index 0b5a46eb..3f5ea458 100644 --- a/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService.cs +++ b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; namespace Lab.GracefulShutdown.Net6; @@ -7,15 +8,18 @@ internal class GracefulShutdownService : IHostedService private readonly IHostApplicationLifetime _appLifetime; private Task _backgroundTask; private bool _stop; + private ILogger _logger; - public GracefulShutdownService(IHostApplicationLifetime appLifetime) + public GracefulShutdownService(IHostApplicationLifetime appLifetime, + ILogger logger) { this._appLifetime = appLifetime; + this._logger = logger; } public Task StartAsync(CancellationToken cancel) { - Console.WriteLine($"{DateTime.Now} 服務啟動中..."); + this._logger.LogInformation($"{DateTime.Now} 服務啟動中..."); this._backgroundTask = Task.Run(async () => { await this.ExecuteAsync(cancel); }, cancel); @@ -24,24 +28,27 @@ public Task StartAsync(CancellationToken cancel) public async Task StopAsync(CancellationToken cancel) { - Console.WriteLine($"{DateTime.Now} 服務停止中..."); + this._logger.LogInformation($"{DateTime.Now} 服務停止中..."); this._stop = true; await this._backgroundTask; - - Console.WriteLine($"{DateTime.Now} 服務已停止"); + + this._logger.LogInformation($"{DateTime.Now} 服務已停止"); } private async Task ExecuteAsync(CancellationToken cancel) { - Console.WriteLine($"{DateTime.Now} 服務已啟動!"); + this._logger.LogInformation($"{DateTime.Now} 服務已啟動!"); while (!this._stop) { - Console.WriteLine($"{DateTime.Now} 服務運行中..."); - await Task.Delay(TimeSpan.FromSeconds(1), cancel); + this._logger.LogInformation($"{DateTime.Now} 1.服務運行中..."); + this._logger.LogInformation($"1.IsCancel={cancel.IsCancellationRequested}"); + await Task.Delay(TimeSpan.FromSeconds(30), cancel); + this._logger.LogInformation($"2.IsCancel={cancel.IsCancellationRequested}"); + this._logger.LogInformation($"{DateTime.Now} 2.服務運行中..."); } - Console.WriteLine($"{DateTime.Now} 服務已完美的停止(Graceful Shutdown)"); + this._logger.LogInformation($"{DateTime.Now} 服務已完美的停止(Graceful Shutdown)"); } } \ No newline at end of file diff --git a/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService1.cs b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService1.cs new file mode 100644 index 00000000..dd59d00c --- /dev/null +++ b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/GracefulShutdownService1.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Lab.GracefulShutdown.Net6; + +class GracefulShutdownService1 : BackgroundService +{ + private readonly ILogger _logger; + + public GracefulShutdownService1(ILogger logger) + { + this._logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + this._logger.LogInformation($"{DateTime.Now} 服務已啟動!"); + while (!stoppingToken.IsCancellationRequested) + { + this._logger.LogInformation($"{DateTime.Now} 1.服務運行中..."); + this._logger.LogInformation($"1.IsCancel={stoppingToken.IsCancellationRequested}"); + await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); + this._logger.LogInformation($"2.IsCancel={stoppingToken.IsCancellationRequested}"); + this._logger.LogInformation($"{DateTime.Now} 2.服務運行中..."); + } + this._logger.LogInformation($"{DateTime.Now} 服務已完美的停止(Graceful Shutdown)"); + } +} \ No newline at end of file diff --git a/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Lab.GracefulShutdown.Net6.csproj b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Lab.GracefulShutdown.Net6.csproj index 1a94648c..01169661 100644 --- a/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Lab.GracefulShutdown.Net6.csproj +++ b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Lab.GracefulShutdown.Net6.csproj @@ -5,9 +5,19 @@ net6.0 enable enable + Linux + + + + + + + + .dockerignore + diff --git a/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Program.cs b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Program.cs index bec06acf..ce32bab2 100644 --- a/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Program.cs +++ b/Graceful Shutdown/Lab.GracefulShutdown/Lab.GracefulShutdown.Net6/Program.cs @@ -1,18 +1,27 @@ -using Lab.GracefulShutdown.Net6; +using System.Diagnostics; +using Lab.GracefulShutdown.Net6; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.Runtime.Loader; +using Serilog; +using Serilog.Formatting.Json; -var tcs = new TaskCompletionSource(); var sigintReceived = false; -Console.WriteLine("等待以下訊號 SIGINT/SIGTERM"); +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.File("logs/host-.txt", rollingInterval: RollingInterval.Day) + .CreateBootstrapLogger() + ; +Log.Information($"Process id: {Process.GetCurrentProcess().Id}"); +Log.Information("等待以下訊號 SIGINT/SIGTERM"); Console.CancelKeyPress += (sender, e) => { e.Cancel = true; - Console.WriteLine("已接收 SIGINT (Ctrl+C)"); - tcs.SetResult(); + Log.Information("已接收 SIGINT (Ctrl+C)"); sigintReceived = true; }; @@ -20,12 +29,11 @@ { if (!sigintReceived) { - Console.WriteLine("已接收 SIGTERM"); - tcs.SetResult(); + Log.Information("已接收 SIGTERM,AssemblyLoadContext.Default.Unloading"); } else { - Console.WriteLine("@AssemblyLoadContext.Default.Unloading,已處理 SIGINT,忽略 SIGTERM"); + Log.Information("@AssemblyLoadContext.Default.Unloading,已處理 SIGINT,忽略 SIGTERM"); } }; @@ -33,21 +41,31 @@ { if (!sigintReceived) { - Console.WriteLine("已接收 SIGTERM"); - tcs.SetResult(); + Log.Information("已接收 SIGTERM,ProcessExit"); } else { - Console.WriteLine("@AppDomain.CurrentDomain.ProcessExit,已處理 SIGINT,忽略 SIGTERM"); + Log.Information("@AppDomain.CurrentDomain.ProcessExit,已處理 SIGINT,忽略 SIGTERM"); } }; await Host.CreateDefaultBuilder(args) - .ConfigureServices((hostContext, services) => - { - // services.AddHostedService(); - services.AddHostedService(); - }) - .RunConsoleAsync(); -Console.WriteLine("下次再來唷~"); + .ConfigureServices((hostContext, services) => + { + // services.Configure(opts => opts.ShutdownTimeout = TimeSpan.FromSeconds(15)); + // services.AddHostedService(); + services.AddHostedService(); + // services.AddHostedService(); + }) + .UseSerilog((context, services, config) => + { + var formatter = new JsonFormatter(); + config.ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext() + .WriteTo.Console(formatter) + .WriteTo.File(formatter, "logs/app-.txt", rollingInterval: RollingInterval.Minute); + }) + .RunConsoleAsync(); +Log.Information("下次再來唷~"); \ No newline at end of file From 7d3e165539d35090187f4bc9b17755612b955d4b Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 28 Aug 2023 08:54:24 +0800 Subject: [PATCH 314/424] =?UTF-8?q?=E8=87=AA=E8=A8=82=E9=A9=97=E8=AD=89?= =?UTF-8?q?=E9=8C=AF=E8=AA=A4=E5=9B=9E=E5=82=B3=E8=A8=8A=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/FailureObjectResult.cs | 13 +++ .../Controllers/GenericController.cs | 31 +++++ .../Controllers/MembersController.cs | 35 ++++++ .../Lab.ModelValidation.API/Failure.cs | 24 ++++ .../Lab.ModelValidation.API/FailureCode.cs | 15 +++ .../Filters/ModelValidationFilter.cs | 43 +++++++ .../Lab.ModelValidation.API.csproj | 16 +++ .../Lab.ModelValidation.API/MemberService.cs | 55 +++++++++ .../Models/CreateMemberRequest.cs | 24 ++++ .../Models/GetMemberResult.cs | 12 ++ .../Lab.ModelValidation.API/Program.cs | 110 ++++++++++++++++++ .../appsettings.Development.json | 8 ++ .../Lab.ModelValidation.API/appsettings.json | 9 ++ .../net core model validation.sln | 16 +++ 14 files changed, 411 insertions(+) create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/FailureObjectResult.cs create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/GenericController.cs create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/Failure.cs create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/FailureCode.cs create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/Filters/ModelValidationFilter.cs create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/Lab.ModelValidation.API.csproj create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/Models/CreateMemberRequest.cs create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/Models/GetMemberResult.cs create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/Program.cs create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/appsettings.Development.json create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/appsettings.json create mode 100644 ModelValidation/net core model validation/net core model validation.sln diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/FailureObjectResult.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/FailureObjectResult.cs new file mode 100644 index 00000000..d21dd82d --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/FailureObjectResult.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.ModelValidation.API.Controllers; + +public class FailureObjectResult : ObjectResult +{ + public FailureObjectResult(Failure error, int statusCode = StatusCodes.Status400BadRequest) + : base(error) + { + this.StatusCode = statusCode; + this.Value = error; + } +} \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/GenericController.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/GenericController.cs new file mode 100644 index 00000000..1cbd4d48 --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/GenericController.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.ModelValidation.API.Controllers; + +public class GenericController : ControllerBase +{ + private static readonly Lazy> s_failureLookupLazy = new(() => new() + { + { FailureCode.InputValid, StatusCodes.Status400BadRequest }, + { FailureCode.MemberAlreadyExist, StatusCodes.Status400BadRequest }, + { FailureCode.MemberNotFound, StatusCodes.Status404NotFound }, + { FailureCode.DataNotFound, StatusCodes.Status404NotFound }, + { FailureCode.DataConcurrency, StatusCodes.Status429TooManyRequests }, + { FailureCode.ServerError, StatusCodes.Status500InternalServerError }, + { FailureCode.DbError, StatusCodes.Status500InternalServerError }, + { FailureCode.S3Error, StatusCodes.Status500InternalServerError }, + }); + + private static readonly Dictionary FailureLookup = s_failureLookupLazy.Value; + + [NonAction] + public FailureObjectResult GenericFailure(Failure failure) + { + if (FailureLookup.TryGetValue(failure.Code, out int statusCode)) + { + return new FailureObjectResult(failure, statusCode); + } + + return new FailureObjectResult(failure); + } +} \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs new file mode 100644 index 00000000..833a619a --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs @@ -0,0 +1,35 @@ +using Lab.ModelValidation.API.Models; +using Microsoft.AspNetCore.Mvc; + +namespace Lab.ModelValidation.API.Controllers; + +[ApiController] +[Route("[controller]")] +public class MembersController : GenericController +{ + private readonly ILogger _logger; + + public MembersController(ILogger logger) + { + this._logger = logger; + } + + [HttpPost(Name = "CreateData")] + public ActionResult Post(CreateMemberRequest request) + { + return this.NoContent(); + } + + [HttpGet("{id}", Name = "GetData")] + public ActionResult Get(int id) + { + var service = new MemberService(); + var (failure, _) = service.GetMember(id); + if (failure != null) + { + return this.GenericFailure(failure); + } + + return this.NoContent(); + } +} \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Failure.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Failure.cs new file mode 100644 index 00000000..892c247e --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Failure.cs @@ -0,0 +1,24 @@ +namespace Lab.ModelValidation.API; + +public class Failure +{ + public Failure() + { + } + + public Failure(FailureCode code, string message) + { + this.Code = code; + this.Message = message; + } + + public FailureCode Code { get; init; } + + public string Message { get; init; } + + public object Data { get; init; } + + public string TraceId { get; set; } + + public List Details { get; init; } +} \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/FailureCode.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/FailureCode.cs new file mode 100644 index 00000000..627ee927 --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/FailureCode.cs @@ -0,0 +1,15 @@ +namespace Lab.ModelValidation.API; + +public enum FailureCode +{ + Unknown = 0, + InputValid = 1, + MemberNotFound, + MemberAlreadyExist, + ServerError, + DataConflict, + DataConcurrency, + DataNotFound, + DbError, + S3Error +} \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Filters/ModelValidationFilter.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Filters/ModelValidationFilter.cs new file mode 100644 index 00000000..de3f3b6c --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Filters/ModelValidationFilter.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Lab.ModelValidation.API.Filters; + +public class ModelValidationFilter : IActionFilter +{ + public void OnActionExecuting(ActionExecutingContext context) + { + if (context.Result != null) + { + return; + } + + if (context.ModelState.IsValid) + { + return; + } + + // 获取败的验证信息列表 + var errors = context.ModelState + .Where(s => s.Value != null + && s.Value.ValidationState == ModelValidationState.Invalid) + .SelectMany(s => s.Value!.Errors.ToList()) + .Select(e => e.ErrorMessage) + .ToArray(); + + // 回傳自訂檢查錯誤 + var result = new Failure() + { + Code = FailureCode.InputValid, + Message = "input valid", + Data = errors + }; + + context.Result = new BadRequestObjectResult(result); + } + + public void OnActionExecuted(ActionExecutedContext context) + { + } +} \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Lab.ModelValidation.API.csproj b/ModelValidation/net core model validation/Lab.ModelValidation.API/Lab.ModelValidation.API.csproj new file mode 100644 index 00000000..11cb79ab --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Lab.ModelValidation.API.csproj @@ -0,0 +1,16 @@ + + + + net7.0 + enable + enable + + + + + + + + + + diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs new file mode 100644 index 00000000..29d12402 --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs @@ -0,0 +1,55 @@ +using Lab.ModelValidation.API.Models; + +namespace Lab.ModelValidation.API; + +public class MemberService +{ + //一個方法有多種可能的 Failure + public (Failure Failure, bool Data) GetMember(int id) + { + if (id == 1) + { + return (new Failure + { + Code = FailureCode.MemberNotFound, + Message = "Member not found.", + }, true); + } + + if (id == 2) + { + return (new Failure + { + Code = FailureCode.MemberAlreadyExist, + Message = "Member already exist.", + }, true); + } + + if (id == 3) + { + return (new Failure + { + Code = FailureCode.DataConcurrency, + Message = "Data concurrency error.", + }, true); + } + + return (null, false); + } + + //具有多個 Detail 的 Failure + public (Failure Failure, bool Data) GetMember1() + { + var failure = new Failure() + { + Code = FailureCode.InputValid, + Message = "view detail errors", + Details = new List() + { + new(code: FailureCode.MemberNotFound, message: "Member not found."), + new(code: FailureCode.MemberAlreadyExist, message: "Member already exist.") + } + }; + return (failure, false); + } +} \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Models/CreateMemberRequest.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Models/CreateMemberRequest.cs new file mode 100644 index 00000000..0e1c5a51 --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Models/CreateMemberRequest.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; +using Lab.ModelValidation.API.Controllers; + +namespace Lab.ModelValidation.API.Models; + +public enum MemberType +{ + None, + Member, + Vip, +} + +public class CreateMemberRequest +{ + [Required] + public string Name { get; set; } + + [Range(18, 200)] + public int Age { get; set; } + + [Required] + public MemberType Type { get; set; } + public MemberType Type1 { get; set; } +} \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Models/GetMemberResult.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Models/GetMemberResult.cs new file mode 100644 index 00000000..796f8aac --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Models/GetMemberResult.cs @@ -0,0 +1,12 @@ +using Lab.ModelValidation.API.Controllers; + +namespace Lab.ModelValidation.API.Models; + +public class GetMemberResult +{ + public Guid Id { get; set; } + + public string Name { get; set; } + + public int Age { get; set; } +} \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Program.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Program.cs new file mode 100644 index 00000000..0dd0ce42 --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Program.cs @@ -0,0 +1,110 @@ +using System.Diagnostics; +using System.Runtime.InteropServices.JavaScript; +using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; +using Lab.ModelValidation.API; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers().AddJsonOptions(options => +{ + options.JsonSerializerOptions.MaxDepth = 10; + options.JsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; + options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + options.AllowInputFormatterExceptionMessages = true; +}); +builder.Services.Configure(options => +{ + //停用 Model Validation + options.SuppressModelStateInvalidFilter = false; + + options.InvalidModelStateResponseFactory = actionContext => ValidationErrorHandler(options, actionContext); +}); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); + +IActionResult ValidationErrorHandler(ApiBehaviorOptions apiBehaviorOptions, ActionContext actionContext) +{ + var originalFactory = apiBehaviorOptions.InvalidModelStateResponseFactory; + if (actionContext.ModelState.IsValid) + { + return originalFactory(actionContext); + } + + var traceId = Activity.Current?.Id ?? actionContext.HttpContext.TraceIdentifier; + + //處理 JSON Path + var jsonPathKeys = actionContext.ModelState.Keys.Where(e => e.StartsWith("$.")).ToList(); + if (jsonPathKeys.Count > 0) + { + var errorData = new List(); + + foreach (var key in jsonPathKeys) + { + var normalizedKey = key.Substring(2); + foreach (var error in actionContext.ModelState[key].Errors) + { + if (error.Exception != null) + { + actionContext.ModelState.TryAddModelException(normalizedKey, error.Exception); + } + + actionContext.ModelState.TryAddModelError(normalizedKey, "The provided value is not valid."); + } + + actionContext.ModelState.Remove(key); + errorData.Add($"The {normalizedKey} field is not valid."); + } + + var failure = new Failure + { + Code = FailureCode.InputValid, + Message = "input valid", + Data = errorData + }; + + return new BadRequestObjectResult(failure); + } + + var errors = actionContext.ModelState.ToDictionary( + p => p.Key, + p => p.Value.Errors.Select(e => e.ErrorMessage).ToList()); + + //複寫錯誤內容 + return new BadRequestObjectResult(new Failure() + { + Code = FailureCode.InputValid, + Message = "input valid", + Data = errors, + TraceId = traceId + }); +} \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/appsettings.Development.json b/ModelValidation/net core model validation/Lab.ModelValidation.API/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/appsettings.json b/ModelValidation/net core model validation/Lab.ModelValidation.API/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/ModelValidation/net core model validation/net core model validation.sln b/ModelValidation/net core model validation/net core model validation.sln new file mode 100644 index 00000000..f5e28a4b --- /dev/null +++ b/ModelValidation/net core model validation/net core model validation.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ModelValidation.API", "Lab.ModelValidation.API\Lab.ModelValidation.API.csproj", "{010C4B0F-E5E4-4AB8-A3D4-80DF5A5DDB9A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {010C4B0F-E5E4-4AB8-A3D4-80DF5A5DDB9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {010C4B0F-E5E4-4AB8-A3D4-80DF5A5DDB9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {010C4B0F-E5E4-4AB8-A3D4-80DF5A5DDB9A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {010C4B0F-E5E4-4AB8-A3D4-80DF5A5DDB9A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From f8e4c65131b393501401e58cdaeed87dbd621130 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 30 Aug 2023 08:37:29 +0800 Subject: [PATCH 315/424] refactor --- .../Controllers/GenericController.cs | 2 +- .../Controllers/MembersController.cs | 4 +- .../Lab.ModelValidation.API/FailureCode.cs | 2 +- .../Filters/ModelValidationAttribute.cs | 70 +++++++++++++++++++ .../Filters/ModelValidationFilter.cs | 43 ------------ .../Lab.ModelValidation.API/MemberService.cs | 2 +- .../Lab.ModelValidation.API/Program.cs | 54 +++++++------- 7 files changed, 106 insertions(+), 71 deletions(-) create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/Filters/ModelValidationAttribute.cs delete mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/Filters/ModelValidationFilter.cs diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/GenericController.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/GenericController.cs index 1cbd4d48..2f65df86 100644 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/GenericController.cs +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/GenericController.cs @@ -6,7 +6,7 @@ public class GenericController : ControllerBase { private static readonly Lazy> s_failureLookupLazy = new(() => new() { - { FailureCode.InputValid, StatusCodes.Status400BadRequest }, + { FailureCode.InputInvalid, StatusCodes.Status400BadRequest }, { FailureCode.MemberAlreadyExist, StatusCodes.Status400BadRequest }, { FailureCode.MemberNotFound, StatusCodes.Status404NotFound }, { FailureCode.DataNotFound, StatusCodes.Status404NotFound }, diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs index 833a619a..83017c8d 100644 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs @@ -1,4 +1,5 @@ -using Lab.ModelValidation.API.Models; +using Lab.ModelValidation.API.Filters; +using Lab.ModelValidation.API.Models; using Microsoft.AspNetCore.Mvc; namespace Lab.ModelValidation.API.Controllers; @@ -14,6 +15,7 @@ public MembersController(ILogger logger) this._logger = logger; } + // [ModelValidation()] [HttpPost(Name = "CreateData")] public ActionResult Post(CreateMemberRequest request) { diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/FailureCode.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/FailureCode.cs index 627ee927..e61f26df 100644 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/FailureCode.cs +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/FailureCode.cs @@ -3,7 +3,7 @@ public enum FailureCode { Unknown = 0, - InputValid = 1, + InputInvalid = 1, MemberNotFound, MemberAlreadyExist, ServerError, diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Filters/ModelValidationAttribute.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Filters/ModelValidationAttribute.cs new file mode 100644 index 00000000..230972c4 --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Filters/ModelValidationAttribute.cs @@ -0,0 +1,70 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Lab.ModelValidation.API.Filters; + +public class ModelValidationAttribute : ActionFilterAttribute +{ + public override void OnActionExecuting(ActionExecutingContext actionContext) + { + if (actionContext.Result != null) + { + return; + } + + if (actionContext.ModelState.IsValid) + { + return; + } + + var traceId = Activity.Current?.Id ?? actionContext.HttpContext.TraceIdentifier; + + //處理 JSON Path + var jsonPathKeys = actionContext.ModelState.Keys.Where(e => e.StartsWith("$.")).ToList(); + if (jsonPathKeys.Count > 0) + { + var errorData = new Dictionary(); + foreach (var key in jsonPathKeys) + { + var normalizedKey = key.Substring(2); + foreach (var error in actionContext.ModelState[key].Errors) + { + if (error.Exception != null) + { + actionContext.ModelState.TryAddModelException(normalizedKey, error.Exception); + } + + actionContext.ModelState.TryAddModelError(normalizedKey, "The provided value is not valid."); + errorData.Add(normalizedKey, error.ErrorMessage); + } + + actionContext.ModelState.Remove(key); + } + + //複寫錯誤內容 + actionContext.Result = new BadRequestObjectResult(new Failure + { + Code = FailureCode.InputInvalid, + Message = "enum invalid", + Data = errorData, + TraceId = traceId + }); + return; + } + + var errors = actionContext.ModelState.ToDictionary( + p => p.Key, + p => p.Value.Errors.Select(e => e.ErrorMessage).ToList()); + + //複寫錯誤內容 + actionContext.Result = new BadRequestObjectResult(new Failure() + { + Code = FailureCode.InputInvalid, + Message = "input invalid", + Data = errors, + TraceId = traceId + }); + } +} \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Filters/ModelValidationFilter.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Filters/ModelValidationFilter.cs deleted file mode 100644 index de3f3b6c..00000000 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/Filters/ModelValidationFilter.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.Mvc.ModelBinding; - -namespace Lab.ModelValidation.API.Filters; - -public class ModelValidationFilter : IActionFilter -{ - public void OnActionExecuting(ActionExecutingContext context) - { - if (context.Result != null) - { - return; - } - - if (context.ModelState.IsValid) - { - return; - } - - // 获取败的验证信息列表 - var errors = context.ModelState - .Where(s => s.Value != null - && s.Value.ValidationState == ModelValidationState.Invalid) - .SelectMany(s => s.Value!.Errors.ToList()) - .Select(e => e.ErrorMessage) - .ToArray(); - - // 回傳自訂檢查錯誤 - var result = new Failure() - { - Code = FailureCode.InputValid, - Message = "input valid", - Data = errors - }; - - context.Result = new BadRequestObjectResult(result); - } - - public void OnActionExecuted(ActionExecutedContext context) - { - } -} \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs index 29d12402..21579dfd 100644 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs @@ -42,7 +42,7 @@ public class MemberService { var failure = new Failure() { - Code = FailureCode.InputValid, + Code = FailureCode.InputInvalid, Message = "view detail errors", Details = new List() { diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Program.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Program.cs index 0dd0ce42..ce8581eb 100644 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/Program.cs +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Program.cs @@ -5,6 +5,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using Lab.ModelValidation.API; +using Lab.ModelValidation.API.Filters; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -12,17 +13,23 @@ // Add services to the container. -builder.Services.AddControllers().AddJsonOptions(options => -{ - options.JsonSerializerOptions.MaxDepth = 10; - options.JsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; - options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; - options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; - options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; - options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); - options.AllowInputFormatterExceptionMessages = true; -}); +builder.Services + .AddControllers(p => + { + // p.Filters.Add(); + }) + .AddJsonOptions(options => + { + options.JsonSerializerOptions.MaxDepth = 10; + options.JsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; + options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + options.AllowInputFormatterExceptionMessages = true; + }) + ; builder.Services.Configure(options => { //停用 Model Validation @@ -66,8 +73,7 @@ IActionResult ValidationErrorHandler(ApiBehaviorOptions apiBehaviorOptions, Acti var jsonPathKeys = actionContext.ModelState.Keys.Where(e => e.StartsWith("$.")).ToList(); if (jsonPathKeys.Count > 0) { - var errorData = new List(); - + var errorData = new Dictionary(); foreach (var key in jsonPathKeys) { var normalizedKey = key.Substring(2); @@ -79,31 +85,31 @@ IActionResult ValidationErrorHandler(ApiBehaviorOptions apiBehaviorOptions, Acti } actionContext.ModelState.TryAddModelError(normalizedKey, "The provided value is not valid."); + errorData.Add(normalizedKey, error.ErrorMessage); } actionContext.ModelState.Remove(key); - errorData.Add($"The {normalizedKey} field is not valid."); } - var failure = new Failure + //複寫錯誤內容 + return new BadRequestObjectResult(new Failure { - Code = FailureCode.InputValid, - Message = "input valid", - Data = errorData - }; - - return new BadRequestObjectResult(failure); + Code = FailureCode.InputInvalid, + Message = "enum invalid", + Data = errorData, + TraceId = traceId + }); } var errors = actionContext.ModelState.ToDictionary( p => p.Key, p => p.Value.Errors.Select(e => e.ErrorMessage).ToList()); - + //複寫錯誤內容 return new BadRequestObjectResult(new Failure() { - Code = FailureCode.InputValid, - Message = "input valid", + Code = FailureCode.InputInvalid, + Message = "input invalid", Data = errors, TraceId = traceId }); From 8b591342077549b6a993d717b5f61dc5ff75fc0c Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 30 Aug 2023 13:56:22 +0800 Subject: [PATCH 316/424] =?UTF-8?q?=E5=9C=A8=20member=20service=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=A9=97=E8=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/GenericController.cs | 8 ++- .../Controllers/MembersController.cs | 21 +++++-- .../Lab.ModelValidation.API.csproj | 1 + .../Lab.ModelValidation.API/MemberService.cs | 63 +++++++------------ .../MemberServiceTemp.cs | 53 ++++++++++++++++ .../Models/CreateMemberRequest.cs | 15 ++++- .../Lab.ModelValidation.API/Program.cs | 18 ++++-- 7 files changed, 125 insertions(+), 54 deletions(-) create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/MemberServiceTemp.cs diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/GenericController.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/GenericController.cs index 2f65df86..9faa6a08 100644 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/GenericController.cs +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/GenericController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; namespace Lab.ModelValidation.API.Controllers; @@ -21,6 +22,11 @@ public class GenericController : ControllerBase [NonAction] public FailureObjectResult GenericFailure(Failure failure) { + if (string.IsNullOrWhiteSpace(failure.TraceId)) + { + failure.TraceId = Activity.Current?.Id ?? this.HttpContext.TraceIdentifier; + } + if (FailureLookup.TryGetValue(failure.Code, out int statusCode)) { return new FailureObjectResult(failure, statusCode); diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs index 83017c8d..2b98e0c5 100644 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs @@ -1,4 +1,4 @@ -using Lab.ModelValidation.API.Filters; +using FluentValidation; using Lab.ModelValidation.API.Models; using Microsoft.AspNetCore.Mvc; @@ -9,23 +9,36 @@ namespace Lab.ModelValidation.API.Controllers; public class MembersController : GenericController { private readonly ILogger _logger; + private readonly IValidator _validator; + private readonly MemberService _memberService; - public MembersController(ILogger logger) + public MembersController(ILogger logger, + IValidator validator, + MemberService memberService) { this._logger = logger; + this._validator = validator; + this._memberService = memberService; } // [ModelValidation()] [HttpPost(Name = "CreateData")] - public ActionResult Post(CreateMemberRequest request) + public async Task Post(CreateMemberRequest request, + CancellationToken cancel) { + var createMemberResult = await this._memberService.CreateMemberAsync(request, cancel); + if (createMemberResult.Failure != null) + { + return this.GenericFailure(createMemberResult.Failure); + } + return this.NoContent(); } [HttpGet("{id}", Name = "GetData")] public ActionResult Get(int id) { - var service = new MemberService(); + var service = new MemberServiceTemp(); var (failure, _) = service.GetMember(id); if (failure != null) { diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Lab.ModelValidation.API.csproj b/ModelValidation/net core model validation/Lab.ModelValidation.API/Lab.ModelValidation.API.csproj index 11cb79ab..d86a05e9 100644 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/Lab.ModelValidation.API.csproj +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Lab.ModelValidation.API.csproj @@ -7,6 +7,7 @@ + diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs index 21579dfd..1099d68e 100644 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs @@ -1,55 +1,34 @@ -using Lab.ModelValidation.API.Models; +using FluentValidation; +using Lab.ModelValidation.API.Models; namespace Lab.ModelValidation.API; public class MemberService { - //一個方法有多種可能的 Failure - public (Failure Failure, bool Data) GetMember(int id) - { - if (id == 1) - { - return (new Failure - { - Code = FailureCode.MemberNotFound, - Message = "Member not found.", - }, true); - } - - if (id == 2) - { - return (new Failure - { - Code = FailureCode.MemberAlreadyExist, - Message = "Member already exist.", - }, true); - } + private readonly IValidator _validator; - if (id == 3) - { - return (new Failure - { - Code = FailureCode.DataConcurrency, - Message = "Data concurrency error.", - }, true); - } - - return (null, false); + public MemberService(IValidator validator) + { + this._validator = validator; } - //具有多個 Detail 的 Failure - public (Failure Failure, bool Data) GetMember1() + public async Task<(Failure Failure, bool Data)> CreateMemberAsync(CreateMemberRequest request, + CancellationToken cancel = default) { - var failure = new Failure() + var validateResult = await this._validator.ValidateAsync(request, cancel); + if (validateResult.IsValid == false) { - Code = FailureCode.InputInvalid, - Message = "view detail errors", - Details = new List() + var errors = validateResult.Errors + .ToDictionary(p => p.PropertyName, p => p.ErrorMessage); + var failure = new Failure() { - new(code: FailureCode.MemberNotFound, message: "Member not found."), - new(code: FailureCode.MemberAlreadyExist, message: "Member already exist.") - } - }; - return (failure, false); + Code = FailureCode.InputInvalid, + Message = "input invalid", + Data = errors, + }; + return (failure, false); + } + + return (null, true); } } \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberServiceTemp.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberServiceTemp.cs new file mode 100644 index 00000000..1df2518b --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberServiceTemp.cs @@ -0,0 +1,53 @@ +namespace Lab.ModelValidation.API; + +public class MemberServiceTemp +{ + //一個方法有多種可能的 Failure + public (Failure Failure, bool Data) GetMember(int id) + { + if (id == 1) + { + return (new Failure + { + Code = FailureCode.MemberNotFound, + Message = "Member not found.", + }, true); + } + + if (id == 2) + { + return (new Failure + { + Code = FailureCode.MemberAlreadyExist, + Message = "Member already exist.", + }, true); + } + + if (id == 3) + { + return (new Failure + { + Code = FailureCode.DataConcurrency, + Message = "Data concurrency error.", + }, true); + } + + return (null, false); + } + + //具有多個 Detail 的 Failure + public (Failure Failure, bool Data) GetMember1() + { + var failure = new Failure() + { + Code = FailureCode.InputInvalid, + Message = "view detail errors", + Details = new List() + { + new(code: FailureCode.MemberNotFound, message: "Member not found."), + new(code: FailureCode.MemberAlreadyExist, message: "Member already exist.") + } + }; + return (failure, false); + } +} \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Models/CreateMemberRequest.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Models/CreateMemberRequest.cs index 0e1c5a51..cc8ba166 100644 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/Models/CreateMemberRequest.cs +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Models/CreateMemberRequest.cs @@ -1,8 +1,20 @@ using System.ComponentModel.DataAnnotations; -using Lab.ModelValidation.API.Controllers; +using FluentValidation; namespace Lab.ModelValidation.API.Models; +public class CreateMemberRequestValidator : AbstractValidator +{ + public CreateMemberRequestValidator() + { + this.RuleFor(p => p.Name).NotNull().NotEmpty(); + this.RuleFor(p => p.Age).LessThanOrEqualTo(18).GreaterThan(200); + this.RuleFor(x => x.Type) + .IsInEnum() + .WithMessage("Type is not valid"); + } +} + public enum MemberType { None, @@ -20,5 +32,4 @@ public class CreateMemberRequest [Required] public MemberType Type { get; set; } - public MemberType Type1 { get; set; } } \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Program.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Program.cs index ce8581eb..bcdf5db7 100644 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/Program.cs +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Program.cs @@ -4,20 +4,24 @@ using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; +using FluentValidation; +using FluentValidation.AspNetCore; using Lab.ModelValidation.API; using Lab.ModelValidation.API.Filters; +using Lab.ModelValidation.API.Models; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; var builder = WebApplication.CreateBuilder(args); // Add services to the container. - builder.Services .AddControllers(p => { - // p.Filters.Add(); + // p.ModelValidatorProviders.Clear(); }) + .AddFluentValidation(p => p.RegisterValidatorsFromAssemblyContaining()) .AddJsonOptions(options => { options.JsonSerializerOptions.MaxDepth = 10; @@ -32,12 +36,16 @@ ; builder.Services.Configure(options => { - //停用 Model Validation - options.SuppressModelStateInvalidFilter = false; + //停用 Model State Invalid Filter + options.SuppressModelStateInvalidFilter = true; - options.InvalidModelStateResponseFactory = actionContext => ValidationErrorHandler(options, actionContext); + // options.InvalidModelStateResponseFactory = actionContext => ValidationErrorHandler(options, actionContext); }); +//隨便挑一個 Validator 物件來註冊 FluentValidation +// builder.Services.AddValidatorsFromAssemblyContaining(); +builder.Services.AddScoped(); + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); From d26c14de28d05e3027ad1dcbe45f50ec63e79abf Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 30 Aug 2023 21:28:47 +0800 Subject: [PATCH 317/424] refactor --- .../Controllers/MembersController.cs | 3 --- .../Extensions/ValidationResultExtension.cs | 24 +++++++++++++++++++ .../Lab.ModelValidation.API.csproj | 1 + .../Lab.ModelValidation.API/MemberService.cs | 10 ++------ 4 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 ModelValidation/net core model validation/Lab.ModelValidation.API/Extensions/ValidationResultExtension.cs diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs index 2b98e0c5..2a5d34dd 100644 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Controllers/MembersController.cs @@ -9,15 +9,12 @@ namespace Lab.ModelValidation.API.Controllers; public class MembersController : GenericController { private readonly ILogger _logger; - private readonly IValidator _validator; private readonly MemberService _memberService; public MembersController(ILogger logger, - IValidator validator, MemberService memberService) { this._logger = logger; - this._validator = validator; this._memberService = memberService; } diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Extensions/ValidationResultExtension.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/Extensions/ValidationResultExtension.cs new file mode 100644 index 00000000..938cbe06 --- /dev/null +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Extensions/ValidationResultExtension.cs @@ -0,0 +1,24 @@ +using FluentValidation.Results; + +namespace Lab.ModelValidation.API.Extensions; + +static class ValidationResultExtension +{ + public static Failure ToFailure(this ValidationResult validateResult) + { + if (validateResult.IsValid) + { + return null; + } + + var errors = validateResult.Errors + .ToDictionary(p => p.PropertyName, p => p.ErrorMessage); + var failure = new Failure() + { + Code = FailureCode.InputInvalid, + Message = "input invalid", + Data = errors, + }; + return failure; + } +} \ No newline at end of file diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/Lab.ModelValidation.API.csproj b/ModelValidation/net core model validation/Lab.ModelValidation.API/Lab.ModelValidation.API.csproj index d86a05e9..760b3f98 100644 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/Lab.ModelValidation.API.csproj +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/Lab.ModelValidation.API.csproj @@ -14,4 +14,5 @@ + diff --git a/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs b/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs index 1099d68e..47140ae0 100644 --- a/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs +++ b/ModelValidation/net core model validation/Lab.ModelValidation.API/MemberService.cs @@ -1,4 +1,5 @@ using FluentValidation; +using Lab.ModelValidation.API.Extensions; using Lab.ModelValidation.API.Models; namespace Lab.ModelValidation.API; @@ -18,14 +19,7 @@ public MemberService(IValidator validator) var validateResult = await this._validator.ValidateAsync(request, cancel); if (validateResult.IsValid == false) { - var errors = validateResult.Errors - .ToDictionary(p => p.PropertyName, p => p.ErrorMessage); - var failure = new Failure() - { - Code = FailureCode.InputInvalid, - Message = "input invalid", - Data = errors, - }; + var failure = validateResult.ToFailure(); return (failure, false); } From bb156b33ae3e4f65bb907713e387ea845c001078 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 3 Sep 2023 17:35:55 +0800 Subject: [PATCH 318/424] =?UTF-8?q?=E5=AE=8C=E6=88=90=20Fluent=20Error=20H?= =?UTF-8?q?andler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Without Exception/ErrorHandler.sln | 16 +++ .../Controllers/FailureObjectResult.cs | 15 ++ .../Controllers/GenericController.cs | 37 +++++ .../Controllers/MembersController.cs | 38 +++++ .../Extensions/ValidationResultExtension.cs | 24 ++++ .../Lab.ErrorHandler.API/Failure.cs | 59 ++++++++ .../Lab.ErrorHandler.API/FailureCode.cs | 16 +++ .../Filters/ModelValidationAttribute.cs | 72 ++++++++++ .../Lab.ErrorHandler.API.csproj | 17 +++ .../Lab.ErrorHandler.API/MemberService.cs | 118 ++++++++++++++++ .../Lab.ErrorHandler.API/MemberService1.cs | 116 ++++++++++++++++ .../Lab.ErrorHandler.API/MemberService2.cs | 131 ++++++++++++++++++ .../Lab.ErrorHandler.API/MemberService3.cs | 103 ++++++++++++++ .../Models/BindCellphoneRequest.cs | 8 ++ .../Models/CreateMemberRequest.cs | 8 ++ .../Models/GetMemberResult.cs | 10 ++ .../Lab.ErrorHandler.API/Program.cs | 129 +++++++++++++++++ .../SecondStepExtensions.cs | 65 +++++++++ .../BindCellphoneRequestValidator.cs | 12 ++ .../CreateMemberRequestValidator.cs | 13 ++ .../appsettings.Development.json | 8 ++ .../Lab.ErrorHandler.API/appsettings.json | 9 ++ 22 files changed, 1024 insertions(+) create mode 100644 Error Handler/Without Exception/ErrorHandler.sln create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/FailureObjectResult.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Extensions/ValidationResultExtension.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Failure.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/FailureCode.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Filters/ModelValidationAttribute.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Lab.ErrorHandler.API.csproj create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Models/BindCellphoneRequest.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Models/CreateMemberRequest.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Models/GetMemberResult.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Validators/BindCellphoneRequestValidator.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Validators/CreateMemberRequestValidator.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/appsettings.Development.json create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/appsettings.json diff --git a/Error Handler/Without Exception/ErrorHandler.sln b/Error Handler/Without Exception/ErrorHandler.sln new file mode 100644 index 00000000..1c4d184a --- /dev/null +++ b/Error Handler/Without Exception/ErrorHandler.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ErrorHandler.API", "Lab.ErrorHandler.API\Lab.ErrorHandler.API.csproj", "{E1409B61-F8DB-4533-BEA4-0B83B7F59491}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E1409B61-F8DB-4533-BEA4-0B83B7F59491}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1409B61-F8DB-4533-BEA4-0B83B7F59491}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1409B61-F8DB-4533-BEA4-0B83B7F59491}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1409B61-F8DB-4533-BEA4-0B83B7F59491}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/FailureObjectResult.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/FailureObjectResult.cs new file mode 100644 index 00000000..682c3f93 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/FailureObjectResult.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.ErrorHandler.API.Controllers; + +public class FailureObjectResult : ObjectResult +{ + private ILogger _logger; + + public FailureObjectResult(Failure error, int statusCode = StatusCodes.Status400BadRequest) + : base(error) + { + this.StatusCode = statusCode; + this.Value = error.WithoutException(); + } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs new file mode 100644 index 00000000..dec47767 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs @@ -0,0 +1,37 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; + +namespace Lab.ErrorHandler.API.Controllers; + +public class GenericController : ControllerBase +{ + private static readonly Lazy> s_failureLookupLazy = new(() => new() + { + { FailureCode.InputInvalid, StatusCodes.Status400BadRequest }, + { FailureCode.MemberAlreadyExist, StatusCodes.Status400BadRequest }, + { FailureCode.MemberNotFound, StatusCodes.Status404NotFound }, + { FailureCode.DataNotFound, StatusCodes.Status404NotFound }, + { FailureCode.DataConcurrency, StatusCodes.Status429TooManyRequests }, + { FailureCode.ServerError, StatusCodes.Status500InternalServerError }, + { FailureCode.DbError, StatusCodes.Status500InternalServerError }, + { FailureCode.S3Error, StatusCodes.Status500InternalServerError }, + }); + + private static readonly Dictionary FailureLookup = s_failureLookupLazy.Value; + + [NonAction] + public FailureObjectResult GenericFailure(Failure failure) + { + if (string.IsNullOrWhiteSpace(failure.TraceId)) + { + failure.TraceId = Activity.Current?.Id ?? this.HttpContext.TraceIdentifier; + } + + if (FailureLookup.TryGetValue(failure.Code, out int statusCode)) + { + return new FailureObjectResult(failure, statusCode); + } + + return new FailureObjectResult(failure); + } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs new file mode 100644 index 00000000..81bfefc2 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs @@ -0,0 +1,38 @@ +using Lab.ErrorHandler.API.Models; +using Microsoft.AspNetCore.Mvc; + +namespace Lab.ErrorHandler.API.Controllers; + +[ApiController] +[Route("[controller]")] +public class MembersController : GenericController +{ + private readonly ILogger _logger; + private readonly MemberService3 _memberService; + + public MembersController(ILogger logger, + MemberService3 memberService) + { + this._logger = logger; + this._memberService = memberService; + } + + [Produces("application/json")] + [HttpPost("{memberId}/bind-cellphone", Name = "BindCellphone")] + [ProducesResponseType(typeof(Failure), StatusCodes.Status400BadRequest)] + public async Task Post(int memberId, + BindCellphoneRequest request, + CancellationToken cancel = default) + { + request.MemberId = memberId; + var createMemberResult = + await this._memberService.BindCellphoneAsync(request, cancel); + if (createMemberResult.Failure != null) + { + this._logger.LogInformation(500, "Bind cellphone failure:{@Failure}", createMemberResult.Failure); + return this.GenericFailure(createMemberResult.Failure); + } + + return this.NoContent(); + } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Extensions/ValidationResultExtension.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Extensions/ValidationResultExtension.cs new file mode 100644 index 00000000..ac3e3793 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Extensions/ValidationResultExtension.cs @@ -0,0 +1,24 @@ +using FluentValidation.Results; + +namespace Lab.ErrorHandler.API.Extensions; + +static class ValidationResultExtension +{ + public static Failure ToFailure(this ValidationResult validateResult) + { + if (validateResult.IsValid) + { + return null; + } + + var errors = validateResult.Errors + .ToDictionary(p => p.PropertyName, p => p.ErrorMessage); + var failure = new Failure() + { + Code = FailureCode.InputInvalid, + Message = "input invalid", + Data = errors, + }; + return failure; + } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Failure.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Failure.cs new file mode 100644 index 00000000..95c5ed18 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Failure.cs @@ -0,0 +1,59 @@ +namespace Lab.ErrorHandler.API; + +public class Failure +{ + public Failure() + { + } + + public Failure(FailureCode code, string message) + { + this.Code = code; + this.Message = message; + } + + public FailureCode Code { get; init; } + + public string Message { get; init; } + + public object Data { get; init; } + + public string TraceId { get; set; } + + public Exception Exception { get; set; } + + public List Details { get; init; } = new(); + + public Failure WithoutException() + { + List details = new(); + foreach (var detail in this.Details) + { + details.Add(this.WithoutException(detail)); + } + + return new Failure(this.Code, this.Message) + { + Data = this.Data, + Details = details, + TraceId = this.TraceId, + }; + } + + public Failure WithoutException(Failure error) + { + var result = new Failure(error.Code, error.Message) + { + TraceId = error.TraceId + }; + + foreach (var detailError in this.Details) + { + // 遞迴處理 Details 屬性 + var detailResult = this.WithoutException(detailError); + result.Details.Add(detailResult); + } + + return result; + } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/FailureCode.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/FailureCode.cs new file mode 100644 index 00000000..055b61ab --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/FailureCode.cs @@ -0,0 +1,16 @@ +namespace Lab.ErrorHandler.API; + +public enum FailureCode +{ + UnknownError = 0, + InputInvalid = 1, + MemberNotFound, + MemberAlreadyExist, + ServerError, + DataConflict, + DataConcurrency, + DataNotFound, + DbError, + S3Error, + CellphoneFormatInvalid +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Filters/ModelValidationAttribute.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Filters/ModelValidationAttribute.cs new file mode 100644 index 00000000..5cfe802e --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Filters/ModelValidationAttribute.cs @@ -0,0 +1,72 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Lab.ErrorHandler.API.Filters; + +public class ModelValidationAttribute : ActionFilterAttribute +{ + public override void OnActionExecuting(ActionExecutingContext actionContext) + { + // if (actionContext.Result != null) + // { + // return; + // } + // + // if (actionContext.ModelState.IsValid) + // { + // return; + // } + + var traceId = Activity.Current?.Id ?? actionContext.HttpContext.TraceIdentifier; + + //處理 JSON Path + var jsonPathKeys = actionContext.ModelState.Keys.Where(e => e.StartsWith("$.")).ToList(); + if (jsonPathKeys.Count > 0) + { + var errorData = new Dictionary(); + foreach (var key in jsonPathKeys) + { + var normalizedKey = key.Substring(2); + foreach (var error in actionContext.ModelState[key].Errors) + { + if (error.Exception != null) + { + actionContext.ModelState.TryAddModelException(normalizedKey, error.Exception); + } + + actionContext.ModelState.TryAddModelError(normalizedKey, "The provided value is not valid."); + errorData.Add(normalizedKey, error.ErrorMessage); + } + + actionContext.ModelState.Remove(key); + } + + //複寫錯誤內容 + actionContext.Result = new BadRequestObjectResult(new Failure + { + Code = FailureCode.InputInvalid, + Message = "enum invalid", + Data = errorData, + TraceId = traceId + }); + return; + } + + var errors = actionContext.ModelState + .Where(p => p.Value.ValidationState == ModelValidationState.Invalid) + .ToDictionary( + p => p.Key, + p => p.Value.Errors.Select(e => e.ErrorMessage).ToList()); + + //複寫錯誤內容 + actionContext.Result = new BadRequestObjectResult(new Failure() + { + Code = FailureCode.InputInvalid, + Message = "input invalid", + Data = errors, + TraceId = traceId + }); + } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Lab.ErrorHandler.API.csproj b/Error Handler/Without Exception/Lab.ErrorHandler.API/Lab.ErrorHandler.API.csproj new file mode 100644 index 00000000..6b64bae5 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Lab.ErrorHandler.API.csproj @@ -0,0 +1,17 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService.cs new file mode 100644 index 00000000..7b2f03b7 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService.cs @@ -0,0 +1,118 @@ +using System.Data; +using FluentValidation; +using Lab.ErrorHandler.API.Extensions; +using Lab.ErrorHandler.API.Models; + +namespace Lab.ErrorHandler.API; + +public class MemberService +{ + private readonly IValidator _validator; + + public MemberService(IValidator validator) + { + this._validator = validator; + } + + //一個方法有多種可能的 Failure + public async Task<(Failure Failure, bool Data)> BindCellphoneAsync(BindCellphoneRequest request, + CancellationToken cancel = default) + { + var validationResult = await this._validator.ValidateAsync(request, cancel); + if (validationResult.IsValid == false) + { + return (validationResult.ToFailure(), false); + } + + //找不到會員 + var getMemberResult = await this.GetMemberAsync(request.MemberId, cancel); + if (getMemberResult.Failure != null) + { + return getMemberResult; + } + + //手機格式無效 + var validateCellphoneResult = await this.ValidateCellphoneAsync(request.Cellphone, cancel); + if (validateCellphoneResult.Failure != null) + { + return getMemberResult; + } + + //資料衝突,手機已經被綁定 + var saveChangeResult = await this.SaveChangeAsync(request, cancel); + + return saveChangeResult; + } + + public async Task<(Failure Failure, bool Data)> SaveChangeAsync(BindCellphoneRequest request, + CancellationToken cancel = default) + { + try + { + throw new DBConcurrencyException("insert data row concurrency error."); + } + catch (Exception e) + { + return (new Failure + { + Code = FailureCode.DataConcurrency, + Message = e.Message, + Exception = e, + Data = request + }, false); + } + } + + public async Task<(Failure Failure, bool Data)> ValidateCellphoneAsync(string cellphone, + CancellationToken cancel = default) + { + return (new Failure + { + Code = FailureCode.CellphoneFormatInvalid, + Message = "Cellphone format invalid.", + Data = cellphone + }, false); + } + + public async Task<(Failure Failure, bool Data)> GetMemberAsync(int memberId, + CancellationToken cancel = default) + { + try + { + return (new Failure + { + Code = FailureCode.MemberNotFound, + Message = "Member not found.", + Data = memberId + }, false); + } + catch (Exception e) + { + return (new Failure + { + Code = FailureCode.DbError, + Message = e.Message, + Data = memberId, + Exception = e, + }, false); + } + } + + //具有多個 Detail 的 Failure + public async Task<(Failure Failure, bool Data)> CreateMemberAsync(CreateMemberRequest request, + CancellationToken cancel = default) + { + var failure = new Failure() + { + Code = FailureCode.InputInvalid, + Message = "view detail errors", + Details = new List() + { + new(code: FailureCode.InputInvalid, message: "Input invalid."), + new(code: FailureCode.CellphoneFormatInvalid, message: "Cellphone format invalid."), + new(code: FailureCode.DataConflict, message: "Member already exist."), + } + }; + return (failure, false); + } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs new file mode 100644 index 00000000..03cd4f22 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs @@ -0,0 +1,116 @@ +using System.Data; +using FluentValidation; +using Lab.ErrorHandler.API.Extensions; +using Lab.ErrorHandler.API.Models; + +namespace Lab.ErrorHandler.API; + +public class MemberService1 +{ + private readonly IValidator _validator; + + public MemberService1(IValidator validator) + { + this._validator = validator; + } + + //一個方法有多種可能的 Failure + public async Task<(Failure Failure, bool Data)> BindCellphoneAsync(BindCellphoneRequest request, + CancellationToken cancel = default) + { + var validationResult = await this._validator.ValidateAsync(request, cancel); + if (validationResult.IsValid == false) + { + return (validationResult.ToFailure(), false); + } + + //找不到會員 + var getMemberResult = await this.GetMemberAsync(request.MemberId, cancel); + if (getMemberResult.Failure != null) + { + return (getMemberResult.Failure, false); + } + + //手機格式無效 + var validateCellphoneResult = await this.ValidateCellphoneAsync(request.Cellphone, cancel); + if (validateCellphoneResult.Failure != null) + { + return validateCellphoneResult; + } + + //資料衝突,手機已經被綁定 + var saveChangeResult = await this.SaveChangeAsync(request, cancel); + + return saveChangeResult; + } + + public async Task<(Failure Failure, bool Data)> SaveChangeAsync(BindCellphoneRequest request, + CancellationToken cancel = default) + { + try + { + throw new DBConcurrencyException("insert data row concurrency error."); + } + catch (Exception e) + { + return (new Failure + { + Code = FailureCode.DataConcurrency, + Message = e.Message, + Exception = e, + Data = request + }, false); + } + } + + public async Task<(Failure Failure, bool Data)> ValidateCellphoneAsync(string cellphone, + CancellationToken cancel = default) + { + return (new Failure + { + Code = FailureCode.CellphoneFormatInvalid, + Message = "Cellphone format invalid.", + Data = cellphone + }, false); + } + + public async Task<(Failure Failure, GetMemberResult Data)> GetMemberAsync(int memberId, + CancellationToken cancel = default) + { + try + { + return (new Failure + { + Code = FailureCode.MemberNotFound, + Message = "Member not found.", + Data = memberId + }, null); + } + catch (Exception e) + { + return (new Failure + { + Code = FailureCode.DbError, + Message = e.Message, + Data = memberId, + Exception = e, + }, null); + } + } + + //具有多個 Detail 的 Failure + public (Failure Failure, bool Data) GetMember1() + { + var failure = new Failure() + { + Code = FailureCode.InputInvalid, + Message = "view detail errors", + Details = new List() + { + new(code: FailureCode.MemberNotFound, message: "Member not found."), + new(code: FailureCode.MemberAlreadyExist, message: "Member already exist.") + } + }; + return (failure, false); + } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs new file mode 100644 index 00000000..137a10ad --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs @@ -0,0 +1,131 @@ +using System.Data; +using FluentValidation; +using Lab.ErrorHandler.API.Extensions; +using Lab.ErrorHandler.API.Models; + +namespace Lab.ErrorHandler.API; + +public class MemberService2 +{ + private MemberWorkflow _workflow; + + public MemberService2(MemberWorkflow workflow) + { + this._workflow = workflow; + } + + //一個方法有多種可能的 Failure + public async Task<(Failure Failure, bool Data)> BindCellphoneAsync(BindCellphoneRequest request, + CancellationToken cancel = default) + { + var result = await _workflow.ValidateModelAsync(request, cancel) + .ThenAnyAsync(p => _workflow.GetMemberAsync(request.MemberId, cancel)) + .ThenAnyAsync(p => _workflow.ValidateCellphone(request.Cellphone, cancel)) + .ThenAnyAsync(p => _workflow.SaveChangeAsync(request, cancel)) + ; + if (result.Failure != null) + { + return (result.Failure, false); + } + + return (null, true); + } + + public class MemberWorkflow + { + private readonly IValidator _validator; + + public MemberWorkflow(IValidator validator) + { + this._validator = validator; + } + + public Failure Failure { get; private set; } + + public async Task ValidateModelAsync(BindCellphoneRequest request, + CancellationToken cancel = default) + { + var validationResult = await this._validator.ValidateAsync(request, cancel); + if (validationResult.IsValid == false) + { + this.Failure = validationResult.ToFailure(); + } + + return this; + } + + public async Task SaveChangeAsync(BindCellphoneRequest request, + CancellationToken cancel = default) + { + if (this.Failure != null) + { + return this; + } + + try + { + throw new DBConcurrencyException("insert data row concurrency error."); + } + catch (Exception e) + { + this.Failure = new Failure + { + Code = FailureCode.DataConcurrency, + Message = e.Message, + Exception = e, + Data = request + }; + } + + return this; + } + + public async Task ValidateCellphone(string cellphone, + CancellationToken cancel = default) + { + if (this.Failure != null) + { + return this; + } + + this.Failure = new Failure + { + Code = FailureCode.CellphoneFormatInvalid, + Message = "Cellphone format invalid.", + Data = cellphone + }; + return this; + } + + public async Task GetMemberAsync(int memberId, + CancellationToken cancel = default) + { + if (this.Failure != null) + { + return this; + } + + try + { + this.Failure = new Failure + { + Code = FailureCode.MemberNotFound, + Message = "Member not found.", + Data = memberId + }; + } + catch (Exception e) + { + this.Failure = new Failure + { + Code = FailureCode.DbError, + Message = e.Message, + Data = memberId, + Exception = e, + }; + } + + return this; + } + } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs new file mode 100644 index 00000000..994353c8 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs @@ -0,0 +1,103 @@ +using System.Data; +using FluentValidation; +using Lab.ErrorHandler.API.Extensions; +using Lab.ErrorHandler.API.Models; + +namespace Lab.ErrorHandler.API; + +public class MemberService3 +{ + private readonly IValidator _validator; + + public MemberService3(IValidator validator) + { + this._validator = validator; + } + + //一個方法有多種可能的 Failure + // public async Task<(Failure Failure, bool Data)> BindCellphoneAsync(BindCellphoneRequest request, + // CancellationToken cancel = default) => + // await this.ValidateModelAsync(request, cancel) + // .ThenWithFailureAsync(p => p.Failure != null + // ? Task.FromResult((p.Failure, false)) + // : this.ValidateCellphoneAsync(request.Cellphone, cancel)) + // .ThenWithFailureAsync(p => p.Failure != null + // ? Task.FromResult((p.Failure, false)) + // : this.GetMemberAsync(request.MemberId, cancel)) + // .ThenWithFailureAsync(p => p.Failure != null + // ? Task.FromResult((p.Failure, false)) + // : this.SaveChangeAsync(request, cancel)) + // ; + public async Task<(Failure Failure, bool Data)> BindCellphoneAsync(BindCellphoneRequest request, + CancellationToken cancel = default) => + await this.ValidateModelAsync(request, cancel) + .ThenAsyncIfNoFailure(p => this.GetMemberAsync(request.MemberId, cancel)) + .ThenAsyncIfNoFailure(p => this.ValidateCellphoneAsync(request.Cellphone, cancel)) + .ThenAsyncIfNoFailure(p => this.SaveChangeAsync(request, cancel)); + + public async Task<(Failure Failure, bool Data)> ValidateModelAsync(BindCellphoneRequest request, + CancellationToken cancel = default) + { + var validationResult = await this._validator.ValidateAsync(request, cancel); + if (validationResult.IsValid == false) + { + return (validationResult.ToFailure(), false); + } + + return (null, true); + } + + public async Task<(Failure Failure, bool Data)> SaveChangeAsync(BindCellphoneRequest request, + CancellationToken cancel = default) + { + try + { + throw new DBConcurrencyException("insert data row concurrency error."); + } + catch (Exception e) + { + return (new Failure + { + Code = FailureCode.DataConcurrency, + Message = e.Message, + Exception = e, + Data = request + }, false); + } + } + + public async Task<(Failure Failure, bool Data)> ValidateCellphoneAsync(string cellphone, + CancellationToken cancel = default) + { + return (new Failure + { + Code = FailureCode.CellphoneFormatInvalid, + Message = "Cellphone format invalid.", + Data = cellphone + }, false); + } + + public async Task<(Failure Failure, GetMemberResult Data)> GetMemberAsync(int memberId, + CancellationToken cancel = default) + { + try + { + return (new Failure + { + Code = FailureCode.MemberNotFound, + Message = "Member not found.", + Data = memberId + }, null); + } + catch (Exception e) + { + return (new Failure + { + Code = FailureCode.DbError, + Message = e.Message, + Data = memberId, + Exception = e, + }, null); + } + } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/BindCellphoneRequest.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/BindCellphoneRequest.cs new file mode 100644 index 00000000..b1b58b22 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/BindCellphoneRequest.cs @@ -0,0 +1,8 @@ +namespace Lab.ErrorHandler.API.Models; + +public class BindCellphoneRequest +{ + public int MemberId { get; set; } + + public string Cellphone { get; set; } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/CreateMemberRequest.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/CreateMemberRequest.cs new file mode 100644 index 00000000..e30a3c75 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/CreateMemberRequest.cs @@ -0,0 +1,8 @@ +namespace Lab.ErrorHandler.API.Models; + +public class CreateMemberRequest +{ + public string Name { get; set; } + + public int Age { get; set; } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/GetMemberResult.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/GetMemberResult.cs new file mode 100644 index 00000000..a0ccdcd3 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/GetMemberResult.cs @@ -0,0 +1,10 @@ +namespace Lab.ErrorHandler.API.Models; + +public class GetMemberResult +{ + public int Id { get; set; } + + public string Name { get; set; } + + public int Age { get; set; } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs new file mode 100644 index 00000000..764c7a7c --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs @@ -0,0 +1,129 @@ +using System.Diagnostics; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; +using FluentValidation; +using Lab.ErrorHandler.API; +using Lab.ErrorHandler.API.Filters; +using Lab.ErrorHandler.API.Models; +using Microsoft.AspNetCore.Mvc; +using Serilog; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services + .AddControllers(p => + { + // p.Filters.Add(); + + // p.ModelValidatorProviders.Clear(); + }) + .AddJsonOptions(options => + { + options.JsonSerializerOptions.MaxDepth = 10; + options.JsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; + options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + options.AllowInputFormatterExceptionMessages = true; + }) + ; +builder.Services.Configure(options => +{ + //停用 Model State Invalid Filter + options.SuppressModelStateInvalidFilter = true; + + // options.InvalidModelStateResponseFactory = actionContext => ValidationErrorHandler(options, actionContext); +}); + +builder.Services.AddValidatorsFromAssemblyContaining(); + +// builder.Services.AddScoped(); +// builder.Services.AddScoped(); +builder.Services.AddScoped(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Host.UseSerilog((context, services, config) => + config.ReadFrom.Services(services) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.File("logs/aspnet-.txt", rollingInterval: RollingInterval.Hour) +); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); + +IActionResult ValidationErrorHandler(ApiBehaviorOptions apiBehaviorOptions, ActionContext actionContext) +{ + var originalFactory = apiBehaviorOptions.InvalidModelStateResponseFactory; + if (actionContext.ModelState.IsValid) + { + return originalFactory(actionContext); + } + + var traceId = Activity.Current?.Id ?? actionContext.HttpContext.TraceIdentifier; + + //處理 JSON Path + var jsonPathKeys = actionContext.ModelState.Keys.Where(e => e.StartsWith("$.")).ToList(); + if (jsonPathKeys.Count > 0) + { + var errorData = new Dictionary(); + foreach (var key in jsonPathKeys) + { + var normalizedKey = key.Substring(2); + foreach (var error in actionContext.ModelState[key].Errors) + { + if (error.Exception != null) + { + actionContext.ModelState.TryAddModelException(normalizedKey, error.Exception); + } + + actionContext.ModelState.TryAddModelError(normalizedKey, "The provided value is not valid."); + errorData.Add(normalizedKey, error.ErrorMessage); + } + + actionContext.ModelState.Remove(key); + } + + //複寫錯誤內容 + return new BadRequestObjectResult(new Failure + { + Code = FailureCode.InputInvalid, + Message = "enum invalid", + Data = errorData, + TraceId = traceId + }); + } + + var errors = actionContext.ModelState.ToDictionary( + p => p.Key, + p => p.Value.Errors.Select(e => e.ErrorMessage).ToList()); + + //複寫錯誤內容 + return new BadRequestObjectResult(new Failure() + { + Code = FailureCode.InputInvalid, + Message = "input invalid", + Data = errors, + TraceId = traceId + }); +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs new file mode 100644 index 00000000..3e2c5a99 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs @@ -0,0 +1,65 @@ +namespace Lab.ErrorHandler.API; + +public static class SecondStepExtensions +{ + // public static async Task Then(this T1 first, Func> second) + // { + // return await second(first).ConfigureAwait(false); + // } + // + + /// + /// 接續執行第二個方法 + /// + /// + /// + /// + /// + /// + public static async Task ThenAnyAsync(this Task first, + Func> second) + { + return await second(await first.ConfigureAwait(false)).ConfigureAwait(false); + } + + /// + /// 接續第二個方法,第一個方法有錯誤時,不執行第二個方法 + /// + /// + /// + /// + /// + /// + public static async Task<(Failure Failure, TResult Data)> ThenAsyncIfNoFailure( + this Task<(Failure Failure, TSource Data)> first, + Func> second) + { + var result = await first.ConfigureAwait(false); + if (result.Failure != null) + { + return (result.Failure, default(TResult)); + } + + return await second(result.Data).ConfigureAwait(false); + } + + // public static async Task ThenAsync(this Task first, + // Func second) + // { + // return second(await first.ConfigureAwait(false)); + // } + + // public static Task Then(this T1 first, Func second) + // { + // return Task.FromResult(second(first)); + // } + + // public static TResult Pipe(this TSource target, Func func) => + // func(target); + // + // public static async Task PipeAsync(this Task target, Func func) => + // func(await target); + // + // public static async Task PipeAsync(this Task target, Func> func) => + // await func(await target); +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Validators/BindCellphoneRequestValidator.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Validators/BindCellphoneRequestValidator.cs new file mode 100644 index 00000000..50a0c159 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Validators/BindCellphoneRequestValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; +using Lab.ErrorHandler.API.Models; + +namespace Lab.ErrorHandler.API.Validators; + +public class BindCellphoneRequestValidator : AbstractValidator +{ + public BindCellphoneRequestValidator() + { + this.RuleFor(p => p.Cellphone).NotNull().NotEmpty(); + } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Validators/CreateMemberRequestValidator.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Validators/CreateMemberRequestValidator.cs new file mode 100644 index 00000000..4bf1b89a --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Validators/CreateMemberRequestValidator.cs @@ -0,0 +1,13 @@ +using FluentValidation; +using Lab.ErrorHandler.API.Models; + +namespace Lab.ErrorHandler.API.Validators; + +public class CreateMemberRequestValidator : AbstractValidator +{ + public CreateMemberRequestValidator() + { + this.RuleFor(p => p.Name).NotNull().NotEmpty(); + this.RuleFor(p => p.Age).LessThanOrEqualTo(18).GreaterThanOrEqualTo(200); + } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/appsettings.Development.json b/Error Handler/Without Exception/Lab.ErrorHandler.API/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/appsettings.json b/Error Handler/Without Exception/Lab.ErrorHandler.API/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From 293c0c7cd9cc4a31cd5ff9a2291b8fbdd11ae9db Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 3 Sep 2023 21:45:02 +0800 Subject: [PATCH 319/424] refactor --- .../Controllers/FailureObjectResult.cs | 4 +- .../Controllers/GenericController.cs | 61 ++++++++++++++----- .../Controllers/MembersController.cs | 3 +- .../Lab.ErrorHandler.API/Failure.cs | 18 ++++++ .../Lab.ErrorHandler.API/MemberService1.cs | 2 +- .../Lab.ErrorHandler.API/MemberService3.cs | 9 +-- .../Models/GenericResult.cs | 8 +++ .../Models/GetMemberResult.cs | 2 + 8 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Models/GenericResult.cs diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/FailureObjectResult.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/FailureObjectResult.cs index 682c3f93..41201dff 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/FailureObjectResult.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/FailureObjectResult.cs @@ -4,12 +4,10 @@ namespace Lab.ErrorHandler.API.Controllers; public class FailureObjectResult : ObjectResult { - private ILogger _logger; - public FailureObjectResult(Failure error, int statusCode = StatusCodes.Status400BadRequest) : base(error) { this.StatusCode = statusCode; - this.Value = error.WithoutException(); + this.Value = error; } } \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs index dec47767..c7d6e59d 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs @@ -5,33 +5,66 @@ namespace Lab.ErrorHandler.API.Controllers; public class GenericController : ControllerBase { - private static readonly Lazy> s_failureLookupLazy = new(() => new() + public static readonly Dictionary FailureCodeLookup = s_failureCodeLookupLazy.Value; + private static readonly Lazy> s_failureCodeLookupLazy = new(CreateFailureCodeLookup); + + private static Dictionary CreateFailureCodeMappings() { - { FailureCode.InputInvalid, StatusCodes.Status400BadRequest }, - { FailureCode.MemberAlreadyExist, StatusCodes.Status400BadRequest }, - { FailureCode.MemberNotFound, StatusCodes.Status404NotFound }, - { FailureCode.DataNotFound, StatusCodes.Status404NotFound }, - { FailureCode.DataConcurrency, StatusCodes.Status429TooManyRequests }, - { FailureCode.ServerError, StatusCodes.Status500InternalServerError }, - { FailureCode.DbError, StatusCodes.Status500InternalServerError }, - { FailureCode.S3Error, StatusCodes.Status500InternalServerError }, - }); - - private static readonly Dictionary FailureLookup = s_failureLookupLazy.Value; + //用關鍵字定義錯誤代碼 + return new Dictionary(StringComparer.InvariantCultureIgnoreCase) + { + { "error", StatusCodes.Status500InternalServerError }, + { "invalid", StatusCodes.Status400BadRequest }, + { "notfound", StatusCodes.Status404NotFound }, + { "concurrency", StatusCodes.Status429TooManyRequests }, + { "conflict", StatusCodes.Status404NotFound }, + }; + } [NonAction] - public FailureObjectResult GenericFailure(Failure failure) + public FailureObjectResult FailureContent(Failure failure) { if (string.IsNullOrWhiteSpace(failure.TraceId)) { failure.TraceId = Activity.Current?.Id ?? this.HttpContext.TraceIdentifier; } - if (FailureLookup.TryGetValue(failure.Code, out int statusCode)) + if (FailureCodeLookup.TryGetValue(failure.Code, out int statusCode)) { return new FailureObjectResult(failure, statusCode); } return new FailureObjectResult(failure); } + + private static Dictionary CreateFailureCodeLookup() + { + var result = new Dictionary(); + var type = typeof(FailureCode); + var names = Enum.GetNames(type); + var failureMappings = CreateFailureCodeMappings(); + foreach (var name in names) + { + var failureCode = FailureCode.Parse(name); + var isDefined = false; + foreach (var mapping in failureMappings) + { + var key = mapping.Key; + var statusCode = mapping.Value; + if (name.Contains(key, StringComparison.OrdinalIgnoreCase)) + { + isDefined = true; + result.Add(failureCode, statusCode); + break; + } + } + + if (isDefined == false) + { + result.Add(failureCode, StatusCodes.Status500InternalServerError); + } + } + + return result; + } } \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs index 81bfefc2..1fb8b46b 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs @@ -20,6 +20,7 @@ public MembersController(ILogger logger, [Produces("application/json")] [HttpPost("{memberId}/bind-cellphone", Name = "BindCellphone")] [ProducesResponseType(typeof(Failure), StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task Post(int memberId, BindCellphoneRequest request, CancellationToken cancel = default) @@ -30,7 +31,7 @@ public async Task Post(int memberId, if (createMemberResult.Failure != null) { this._logger.LogInformation(500, "Bind cellphone failure:{@Failure}", createMemberResult.Failure); - return this.GenericFailure(createMemberResult.Failure); + return this.FailureContent(createMemberResult.Failure); } return this.NoContent(); diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Failure.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Failure.cs index 95c5ed18..2b869336 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Failure.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Failure.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace Lab.ErrorHandler.API; public class Failure @@ -12,14 +14,30 @@ public Failure(FailureCode code, string message) this.Message = message; } + /// + /// 錯誤碼 + /// public FailureCode Code { get; init; } + /// + /// 錯誤訊息 + /// public string Message { get; init; } + /// + /// 錯誤發生時的資料 + /// public object Data { get; init; } + /// + /// 追蹤 Id + /// public string TraceId { get; set; } + /// + /// 例外 + /// + [JsonIgnore] public Exception Exception { get; set; } public List Details { get; init; } = new(); diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs index 03cd4f22..96da86c0 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs @@ -32,7 +32,7 @@ public MemberService1(IValidator validator) } //手機格式無效 - var validateCellphoneResult = await this.ValidateCellphoneAsync(request.Cellphone, cancel); + var validateCellphoneResult = await this.ValidateCellphoneAsync(getMemberResult.Data.Cellphone, cancel); if (validateCellphoneResult.Failure != null) { return validateCellphoneResult; diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs index 994353c8..726ae496 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs @@ -32,7 +32,7 @@ public MemberService3(IValidator validator) CancellationToken cancel = default) => await this.ValidateModelAsync(request, cancel) .ThenAsyncIfNoFailure(p => this.GetMemberAsync(request.MemberId, cancel)) - .ThenAsyncIfNoFailure(p => this.ValidateCellphoneAsync(request.Cellphone, cancel)) + .ThenAsyncIfNoFailure(p => this.ValidateCellphoneAsync(p.Cellphone, cancel)) .ThenAsyncIfNoFailure(p => this.SaveChangeAsync(request, cancel)); public async Task<(Failure Failure, bool Data)> ValidateModelAsync(BindCellphoneRequest request, @@ -82,12 +82,7 @@ await this.ValidateModelAsync(request, cancel) { try { - return (new Failure - { - Code = FailureCode.MemberNotFound, - Message = "Member not found.", - Data = memberId - }, null); + throw new Exception("member not found."); } catch (Exception e) { diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/GenericResult.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/GenericResult.cs new file mode 100644 index 00000000..7d220681 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/GenericResult.cs @@ -0,0 +1,8 @@ +namespace Lab.ErrorHandler.API.Models; + +public class GenericResult +{ + public Failure Failure { get; set; } + + public T Data { get; set; } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/GetMemberResult.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/GetMemberResult.cs index a0ccdcd3..841fe50d 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/GetMemberResult.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/GetMemberResult.cs @@ -7,4 +7,6 @@ public class GetMemberResult public string Name { get; set; } public int Age { get; set; } + + public string Cellphone { get; set; } } \ No newline at end of file From bf2d55d04d9ab95e30c5c31b66647032abf9f194 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 3 Sep 2023 22:25:30 +0800 Subject: [PATCH 320/424] re --- .../Controllers/GenericController.cs | 3 +- .../Controllers/MembersController.cs | 4 +- .../Lab.ErrorHandler.API/MemberService.cs | 83 +++++++++---------- .../Lab.ErrorHandler.API/MemberService1.cs | 15 ++-- .../Lab.ErrorHandler.API/MemberService2.cs | 12 +-- .../Lab.ErrorHandler.API/MemberService3.cs | 2 +- .../Lab.ErrorHandler.API/Program.cs | 67 ++------------- 7 files changed, 63 insertions(+), 123 deletions(-) diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs index c7d6e59d..8d0679eb 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs @@ -5,7 +5,8 @@ namespace Lab.ErrorHandler.API.Controllers; public class GenericController : ControllerBase { - public static readonly Dictionary FailureCodeLookup = s_failureCodeLookupLazy.Value; + public Dictionary FailureCodeLookup => s_failureCodeLookupLazy.Value; + private static readonly Lazy> s_failureCodeLookupLazy = new(CreateFailureCodeLookup); private static Dictionary CreateFailureCodeMappings() diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs index 1fb8b46b..3280720f 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs @@ -8,10 +8,10 @@ namespace Lab.ErrorHandler.API.Controllers; public class MembersController : GenericController { private readonly ILogger _logger; - private readonly MemberService3 _memberService; + private readonly MemberService1 _memberService; public MembersController(ILogger logger, - MemberService3 memberService) + MemberService1 memberService) { this._logger = logger; this._memberService = memberService; diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService.cs index 7b2f03b7..0fd84457 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService.cs @@ -15,76 +15,75 @@ public MemberService(IValidator validator) } //一個方法有多種可能的 Failure - public async Task<(Failure Failure, bool Data)> BindCellphoneAsync(BindCellphoneRequest request, + public async Task BindCellphoneAsync(BindCellphoneRequest request, CancellationToken cancel = default) { var validationResult = await this._validator.ValidateAsync(request, cancel); if (validationResult.IsValid == false) { - return (validationResult.ToFailure(), false); + return false; } - //找不到會員 - var getMemberResult = await this.GetMemberAsync(request.MemberId, cancel); - if (getMemberResult.Failure != null) + try { - return getMemberResult; + //找不到會員 + var getMemberResult = await this.GetMemberAsync(request.MemberId, cancel); } - - //手機格式無效 - var validateCellphoneResult = await this.ValidateCellphoneAsync(request.Cellphone, cancel); - if (validateCellphoneResult.Failure != null) + catch (Exception e) { - return getMemberResult; } - //資料衝突,手機已經被綁定 - var saveChangeResult = await this.SaveChangeAsync(request, cancel); - - return saveChangeResult; - } + try + { + //手機格式無效 + var validateCellphoneResult = await this.ValidateCellphoneAsync(request.Cellphone, cancel); + } + catch (Exception e) + { + } - public async Task<(Failure Failure, bool Data)> SaveChangeAsync(BindCellphoneRequest request, - CancellationToken cancel = default) - { try { - throw new DBConcurrencyException("insert data row concurrency error."); + //資料衝突,手機已經被綁定 + var saveChangeResult = await this.SaveChangeAsync(request, cancel); } catch (Exception e) { - return (new Failure - { - Code = FailureCode.DataConcurrency, - Message = e.Message, - Exception = e, - Data = request - }, false); } + + return true; } - public async Task<(Failure Failure, bool Data)> ValidateCellphoneAsync(string cellphone, + public async Task<(Failure Failure, bool Data)> SaveChangeAsync(BindCellphoneRequest request, CancellationToken cancel = default) { - return (new Failure - { - Code = FailureCode.CellphoneFormatInvalid, - Message = "Cellphone format invalid.", - Data = cellphone - }, false); + throw new DBConcurrencyException("insert data row concurrency error."); } - public async Task<(Failure Failure, bool Data)> GetMemberAsync(int memberId, + public async Task ValidateCellphoneAsync(string cellphone, + CancellationToken cancel = default) + { + throw new Exception("Cellphone format invalid."); + } + + // public async Task GetMemberAsync(int memberId, + // CancellationToken cancel = default) + // { + // try + // { + // throw new Exception("Member not found."); + // } + // catch (Exception e) + // { + // throw; + // } + // } + public async Task<(Failure Failure,GetMemberResult Data)> GetMemberAsync(int memberId, CancellationToken cancel = default) { try { - return (new Failure - { - Code = FailureCode.MemberNotFound, - Message = "Member not found.", - Data = memberId - }, false); + throw new Exception("Member not found."); } catch (Exception e) { @@ -94,7 +93,7 @@ public MemberService(IValidator validator) Message = e.Message, Data = memberId, Exception = e, - }, false); + }, null); } } diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs index 96da86c0..eb99be18 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs @@ -79,12 +79,7 @@ public MemberService1(IValidator validator) { try { - return (new Failure - { - Code = FailureCode.MemberNotFound, - Message = "Member not found.", - Data = memberId - }, null); + throw new Exception($"can not connect db."); } catch (Exception e) { @@ -99,7 +94,8 @@ public MemberService1(IValidator validator) } //具有多個 Detail 的 Failure - public (Failure Failure, bool Data) GetMember1() + public async Task<(Failure Failure, bool Data)> CreateMemberAsync(CreateMemberRequest request, + CancellationToken cancel = default) { var failure = new Failure() { @@ -107,8 +103,9 @@ public MemberService1(IValidator validator) Message = "view detail errors", Details = new List() { - new(code: FailureCode.MemberNotFound, message: "Member not found."), - new(code: FailureCode.MemberAlreadyExist, message: "Member already exist.") + new(code: FailureCode.InputInvalid, message: "Input invalid."), + new(code: FailureCode.CellphoneFormatInvalid, message: "Cellphone format invalid."), + new(code: FailureCode.DataConflict, message: "Member already exist."), } }; return (failure, false); diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs index 137a10ad..13e29383 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs @@ -18,6 +18,11 @@ public MemberService2(MemberWorkflow workflow) public async Task<(Failure Failure, bool Data)> BindCellphoneAsync(BindCellphoneRequest request, CancellationToken cancel = default) { + // var result = + // await (await (await (await this._workflow.ValidateModelAsync(request, cancel)) + // .GetMemberAsync(request.MemberId, cancel)) + // .ValidateCellphone(request.Cellphone, cancel)).SaveChangeAsync(request, cancel); + var result = await _workflow.ValidateModelAsync(request, cancel) .ThenAnyAsync(p => _workflow.GetMemberAsync(request.MemberId, cancel)) .ThenAnyAsync(p => _workflow.ValidateCellphone(request.Cellphone, cancel)) @@ -107,12 +112,7 @@ public async Task GetMemberAsync(int memberId, try { - this.Failure = new Failure - { - Code = FailureCode.MemberNotFound, - Message = "Member not found.", - Data = memberId - }; + throw new Exception($"can not connect db."); } catch (Exception e) { diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs index 726ae496..f0bf87e1 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs @@ -82,7 +82,7 @@ await this.ValidateModelAsync(request, cancel) { try { - throw new Exception("member not found."); + throw new Exception($"can not connect db."); } catch (Exception e) { diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs index 764c7a7c..2c3265f4 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs @@ -35,15 +35,14 @@ { //停用 Model State Invalid Filter options.SuppressModelStateInvalidFilter = true; - - // options.InvalidModelStateResponseFactory = actionContext => ValidationErrorHandler(options, actionContext); }); builder.Services.AddValidatorsFromAssemblyContaining(); -// builder.Services.AddScoped(); -// builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +// builder.Services.AddScoped(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); @@ -70,60 +69,4 @@ app.MapControllers(); -app.Run(); - -IActionResult ValidationErrorHandler(ApiBehaviorOptions apiBehaviorOptions, ActionContext actionContext) -{ - var originalFactory = apiBehaviorOptions.InvalidModelStateResponseFactory; - if (actionContext.ModelState.IsValid) - { - return originalFactory(actionContext); - } - - var traceId = Activity.Current?.Id ?? actionContext.HttpContext.TraceIdentifier; - - //處理 JSON Path - var jsonPathKeys = actionContext.ModelState.Keys.Where(e => e.StartsWith("$.")).ToList(); - if (jsonPathKeys.Count > 0) - { - var errorData = new Dictionary(); - foreach (var key in jsonPathKeys) - { - var normalizedKey = key.Substring(2); - foreach (var error in actionContext.ModelState[key].Errors) - { - if (error.Exception != null) - { - actionContext.ModelState.TryAddModelException(normalizedKey, error.Exception); - } - - actionContext.ModelState.TryAddModelError(normalizedKey, "The provided value is not valid."); - errorData.Add(normalizedKey, error.ErrorMessage); - } - - actionContext.ModelState.Remove(key); - } - - //複寫錯誤內容 - return new BadRequestObjectResult(new Failure - { - Code = FailureCode.InputInvalid, - Message = "enum invalid", - Data = errorData, - TraceId = traceId - }); - } - - var errors = actionContext.ModelState.ToDictionary( - p => p.Key, - p => p.Value.Errors.Select(e => e.ErrorMessage).ToList()); - - //複寫錯誤內容 - return new BadRequestObjectResult(new Failure() - { - Code = FailureCode.InputInvalid, - Message = "input invalid", - Data = errors, - TraceId = traceId - }); -} \ No newline at end of file +app.Run(); \ No newline at end of file From e9f4ad712811e81f35e6bb489b333f06b867a138 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 3 Sep 2023 22:42:43 +0800 Subject: [PATCH 321/424] refactor --- .../Controllers/MembersController.cs | 4 +-- .../Lab.ErrorHandler.API/Program.cs | 2 +- .../SecondStepExtensions.cs | 26 ------------------- 3 files changed, 3 insertions(+), 29 deletions(-) diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs index 3280720f..1fb8b46b 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs @@ -8,10 +8,10 @@ namespace Lab.ErrorHandler.API.Controllers; public class MembersController : GenericController { private readonly ILogger _logger; - private readonly MemberService1 _memberService; + private readonly MemberService3 _memberService; public MembersController(ILogger logger, - MemberService1 memberService) + MemberService3 memberService) { this._logger = logger; this._memberService = memberService; diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs index 2c3265f4..fd8000d7 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs @@ -42,7 +42,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -// builder.Services.AddScoped(); +builder.Services.AddScoped(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs index 3e2c5a99..ecb1f25c 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs @@ -2,12 +2,6 @@ namespace Lab.ErrorHandler.API; public static class SecondStepExtensions { - // public static async Task Then(this T1 first, Func> second) - // { - // return await second(first).ConfigureAwait(false); - // } - // - /// /// 接續執行第二個方法 /// @@ -42,24 +36,4 @@ public static async Task ThenAnyAsync(this Task ThenAsync(this Task first, - // Func second) - // { - // return second(await first.ConfigureAwait(false)); - // } - - // public static Task Then(this T1 first, Func second) - // { - // return Task.FromResult(second(first)); - // } - - // public static TResult Pipe(this TSource target, Func func) => - // func(target); - // - // public static async Task PipeAsync(this Task target, Func func) => - // func(await target); - // - // public static async Task PipeAsync(this Task target, Func> func) => - // await func(await target); } \ No newline at end of file From 79cda88596baaf831d8cf4fa94e89fd73218952f Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 4 Sep 2023 11:08:13 +0800 Subject: [PATCH 322/424] refactor --- .../Lab.ErrorHandler.API/Controllers/MembersController.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs index 1fb8b46b..02f113b0 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/MembersController.cs @@ -26,12 +26,12 @@ public async Task Post(int memberId, CancellationToken cancel = default) { request.MemberId = memberId; - var createMemberResult = + var bindCellphoneResult = await this._memberService.BindCellphoneAsync(request, cancel); - if (createMemberResult.Failure != null) + if (bindCellphoneResult.Failure != null) { - this._logger.LogInformation(500, "Bind cellphone failure:{@Failure}", createMemberResult.Failure); - return this.FailureContent(createMemberResult.Failure); + this._logger.LogInformation(500, "Bind cellphone failure:{@Failure}", bindCellphoneResult.Failure); + return this.FailureContent(bindCellphoneResult.Failure); } return this.NoContent(); From 6c0682ad7b1336fa495dbf8b959ac41c886f9cb2 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 4 Sep 2023 11:54:18 +0800 Subject: [PATCH 323/424] refactor --- .../Lab.ErrorHandler.API/MemberService4.cs | 43 ++++++++++ .../MemberWorkflowExtensions.cs | 80 +++++++++++++++++++ .../Lab.ErrorHandler.API/Program.cs | 1 + 3 files changed, 124 insertions(+) create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService4.cs create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/MemberWorkflowExtensions.cs diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService4.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService4.cs new file mode 100644 index 00000000..a7ba94ba --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService4.cs @@ -0,0 +1,43 @@ +using FluentValidation; +using Lab.ErrorHandler.API.Extensions; +using Lab.ErrorHandler.API.Models; + +namespace Lab.ErrorHandler.API; + +public class MemberService4 +{ + private readonly IValidator _validator; + + public MemberService4(IValidator validator) + { + this._validator = validator; + } + + //一個方法有多種可能的 Failure + public async Task<(Failure Failure, bool Data)> BindCellphoneAsync(BindCellphoneRequest request, + CancellationToken cancel = default) + { + var executeResult = await this.ValidateModelAsync(request, cancel) + .GetMemberAsync(request.MemberId, cancel) + .ValidateCellphoneAsync(request.Cellphone, cancel) + .SaveChangeAsync(request, cancel); + if (executeResult.Failure != null) + { + return (executeResult.Failure, false); + } + + return (null, true); + } + + public async Task<(Failure Failure, bool Data)> ValidateModelAsync(BindCellphoneRequest request, + CancellationToken cancel = default) + { + var validationResult = await this._validator.ValidateAsync(request, cancel); + if (validationResult.IsValid == false) + { + return (validationResult.ToFailure(), false); + } + + return (null, true); + } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberWorkflowExtensions.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberWorkflowExtensions.cs new file mode 100644 index 00000000..bb08c1af --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberWorkflowExtensions.cs @@ -0,0 +1,80 @@ +using System.Data; +using Lab.ErrorHandler.API.Models; + +namespace Lab.ErrorHandler.API; + +static class MemberWorkflowExtensions +{ + public static async Task<(Failure Failure, GetMemberResult Data)> GetMemberAsync( + this Task<(Failure Failure, TSource Data)> previousStep, + int memberId, + CancellationToken cancel = default) + { + try + { + var previousStepResult = await previousStep; + if (previousStepResult.Failure != null) + { + return (previousStepResult.Failure, null); + } + + throw new Exception($"can not connect db."); + } + catch (Exception e) + { + return (new Failure + { + Code = FailureCode.DbError, + Message = e.Message, + Data = memberId, + Exception = e, + }, null); + } + } + + public static async Task<(Failure Failure, bool Data)> SaveChangeAsync( + this Task<(Failure Failure, TSource Data)> previousStep, + BindCellphoneRequest request, + CancellationToken cancel = default) + { + try + { + var previousStepResult = await previousStep; + if (previousStepResult.Failure != null) + { + return (previousStepResult.Failure, false); + } + + throw new DBConcurrencyException("insert data row concurrency error."); + } + catch (Exception e) + { + return (new Failure + { + Code = FailureCode.DataConcurrency, + Message = e.Message, + Exception = e, + Data = request + }, false); + } + } + + public static async Task<(Failure Failure, bool Data)> ValidateCellphoneAsync( + this Task<(Failure Failure, TSource Data)> previousStep, + string cellphone, + CancellationToken cancel = default) + { + var previousStepResult = await previousStep; + if (previousStepResult.Failure != null) + { + return (previousStepResult.Failure, false); + } + + return (new Failure + { + Code = FailureCode.CellphoneFormatInvalid, + Message = "Cellphone format invalid.", + Data = cellphone + }, false); + } +} \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs index fd8000d7..9c6497f6 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Program.cs @@ -43,6 +43,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); From 5bb40eed7db8d25f0296651b4677565c2fe1cccf Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 4 Sep 2023 13:07:24 +0800 Subject: [PATCH 324/424] refactor --- .../Lab.ErrorHandler.API/MemberService2.cs | 6 +++--- .../Lab.ErrorHandler.API/MemberService3.cs | 6 +++--- .../Lab.ErrorHandler.API/SecondStepExtensions.cs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs index 13e29383..76655108 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs @@ -24,9 +24,9 @@ public MemberService2(MemberWorkflow workflow) // .ValidateCellphone(request.Cellphone, cancel)).SaveChangeAsync(request, cancel); var result = await _workflow.ValidateModelAsync(request, cancel) - .ThenAnyAsync(p => _workflow.GetMemberAsync(request.MemberId, cancel)) - .ThenAnyAsync(p => _workflow.ValidateCellphone(request.Cellphone, cancel)) - .ThenAnyAsync(p => _workflow.SaveChangeAsync(request, cancel)) + .Then(p => _workflow.GetMemberAsync(request.MemberId, cancel)) + .Then(p => _workflow.ValidateCellphone(request.Cellphone, cancel)) + .Then(p => _workflow.SaveChangeAsync(request, cancel)) ; if (result.Failure != null) { diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs index f0bf87e1..2e353cf3 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs @@ -31,9 +31,9 @@ public MemberService3(IValidator validator) public async Task<(Failure Failure, bool Data)> BindCellphoneAsync(BindCellphoneRequest request, CancellationToken cancel = default) => await this.ValidateModelAsync(request, cancel) - .ThenAsyncIfNoFailure(p => this.GetMemberAsync(request.MemberId, cancel)) - .ThenAsyncIfNoFailure(p => this.ValidateCellphoneAsync(p.Cellphone, cancel)) - .ThenAsyncIfNoFailure(p => this.SaveChangeAsync(request, cancel)); + .WhenSuccess(p => this.GetMemberAsync(request.MemberId, cancel)) + .WhenSuccess(p => this.ValidateCellphoneAsync(p.Cellphone, cancel)) + .WhenSuccess(p => this.SaveChangeAsync(request, cancel)); public async Task<(Failure Failure, bool Data)> ValidateModelAsync(BindCellphoneRequest request, CancellationToken cancel = default) diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs index ecb1f25c..3d418188 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs @@ -10,7 +10,7 @@ public static class SecondStepExtensions /// /// /// - public static async Task ThenAnyAsync(this Task first, + public static async Task Then(this Task first, Func> second) { return await second(await first.ConfigureAwait(false)).ConfigureAwait(false); @@ -24,7 +24,7 @@ public static async Task ThenAnyAsync(this Task /// /// - public static async Task<(Failure Failure, TResult Data)> ThenAsyncIfNoFailure( + public static async Task<(Failure Failure, TResult Data)> WhenSuccess( this Task<(Failure Failure, TSource Data)> first, Func> second) { From d79fc8c2181a4e00591276fc567be1410e3cfa01 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 5 Sep 2023 22:58:56 +0800 Subject: [PATCH 325/424] refactor --- .../Controllers/FailureObjectResult.cs | 9 ++-- .../Controllers/GenericController.cs | 1 + .../Extensions/ValidationResultExtension.cs | 1 + .../Filters/ModelValidationAttribute.cs | 1 + .../Lab.ErrorHandler.API.csproj | 4 ++ .../Lab.ErrorHandler.API/MemberService.cs | 11 ++++- .../Lab.ErrorHandler.API/MemberService1.cs | 10 +++++ .../Lab.ErrorHandler.API/MemberService2.cs | 10 +++++ .../Lab.ErrorHandler.API/MemberService3.cs | 9 ++++ .../{ => Models}/Failure.cs | 7 ++-- .../{ => Models}/FailureCode.cs | 2 +- .../Properties/launchSettings.json | 41 +++++++++++++++++++ .../SecondStepExtensions.cs | 2 + 13 files changed, 100 insertions(+), 8 deletions(-) rename Error Handler/Without Exception/Lab.ErrorHandler.API/{ => Models}/Failure.cs (84%) rename Error Handler/Without Exception/Lab.ErrorHandler.API/{ => Models}/FailureCode.cs (86%) create mode 100644 Error Handler/Without Exception/Lab.ErrorHandler.API/Properties/launchSettings.json diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/FailureObjectResult.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/FailureObjectResult.cs index 41201dff..26c7e2f7 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/FailureObjectResult.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/FailureObjectResult.cs @@ -1,13 +1,16 @@ +using Lab.ErrorHandler.API.Models; using Microsoft.AspNetCore.Mvc; namespace Lab.ErrorHandler.API.Controllers; public class FailureObjectResult : ObjectResult { - public FailureObjectResult(Failure error, int statusCode = StatusCodes.Status400BadRequest) - : base(error) + public FailureObjectResult(Failure failure, int statusCode = StatusCodes.Status400BadRequest) + : base(failure) { this.StatusCode = statusCode; - this.Value = error; + // Failure.Exception 已經使用 [JsonIgnore],不會再回傳給調用端 + // this.Value = failure.WithoutException(); + this.Value = failure; } } \ No newline at end of file diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs index 8d0679eb..40207438 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Controllers/GenericController.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using Lab.ErrorHandler.API.Models; using Microsoft.AspNetCore.Mvc; namespace Lab.ErrorHandler.API.Controllers; diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Extensions/ValidationResultExtension.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Extensions/ValidationResultExtension.cs index ac3e3793..70bc24ca 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Extensions/ValidationResultExtension.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Extensions/ValidationResultExtension.cs @@ -1,4 +1,5 @@ using FluentValidation.Results; +using Lab.ErrorHandler.API.Models; namespace Lab.ErrorHandler.API.Extensions; diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Filters/ModelValidationAttribute.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Filters/ModelValidationAttribute.cs index 5cfe802e..4ed3cc49 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Filters/ModelValidationAttribute.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Filters/ModelValidationAttribute.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using Lab.ErrorHandler.API.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Lab.ErrorHandler.API.csproj b/Error Handler/Without Exception/Lab.ErrorHandler.API/Lab.ErrorHandler.API.csproj index 6b64bae5..c8414612 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Lab.ErrorHandler.API.csproj +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Lab.ErrorHandler.API.csproj @@ -14,4 +14,8 @@ + + + + diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService.cs index 0fd84457..0365637d 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService.cs @@ -78,11 +78,20 @@ public async Task ValidateCellphoneAsync(string cellphone, // throw; // } // } - public async Task<(Failure Failure,GetMemberResult Data)> GetMemberAsync(int memberId, + public async Task<(Failure Failure, GetMemberResult Data)> GetMemberAsync(int memberId, CancellationToken cancel = default) { try { + if (memberId == 1) + { + return (new Failure + { + Code = FailureCode.MemberNotFound, + Message = "member not found.", + }, null); + } + throw new Exception("Member not found."); } catch (Exception e) diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs index eb99be18..9c90df19 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService1.cs @@ -79,6 +79,16 @@ public MemberService1(IValidator validator) { try { + if (memberId == 1) + { + //模擬找不到資料所回傳的失敗 + return (new Failure + { + Code = FailureCode.MemberNotFound, + Message = "member not found.", + }, null); + } + throw new Exception($"can not connect db."); } catch (Exception e) diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs index 76655108..f86758b6 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService2.cs @@ -112,6 +112,16 @@ public async Task GetMemberAsync(int memberId, try { + if (memberId == 1) + { + this.Failure = new Failure + { + Code = FailureCode.MemberNotFound, + Message = "member not found.", + }; + return this; + } + throw new Exception($"can not connect db."); } catch (Exception e) diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs index 2e353cf3..372666d7 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/MemberService3.cs @@ -82,6 +82,15 @@ await this.ValidateModelAsync(request, cancel) { try { + if (memberId == 1) + { + return (new Failure + { + Code = FailureCode.MemberNotFound, + Message = "member not found.", + }, null); + } + throw new Exception($"can not connect db."); } catch (Exception e) diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Failure.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/Failure.cs similarity index 84% rename from Error Handler/Without Exception/Lab.ErrorHandler.API/Failure.cs rename to Error Handler/Without Exception/Lab.ErrorHandler.API/Models/Failure.cs index 2b869336..7e5c71e5 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/Failure.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/Failure.cs @@ -1,6 +1,6 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; -namespace Lab.ErrorHandler.API; +namespace Lab.ErrorHandler.API.Models; public class Failure { @@ -35,13 +35,14 @@ public Failure(FailureCode code, string message) public string TraceId { get; set; } /// - /// 例外 + /// 例外,不回傳給 Web API /// [JsonIgnore] public Exception Exception { get; set; } public List Details { get; init; } = new(); + //用了 [JsonIgnore] 似乎就不需要它了 QQ,寫完了才想到可以 Ignore,不過這仍然可以適用在其他場景,例如 CLI、Console App public Failure WithoutException() { List details = new(); diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/FailureCode.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/FailureCode.cs similarity index 86% rename from Error Handler/Without Exception/Lab.ErrorHandler.API/FailureCode.cs rename to Error Handler/Without Exception/Lab.ErrorHandler.API/Models/FailureCode.cs index 055b61ab..c9edbf84 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/FailureCode.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Models/FailureCode.cs @@ -1,4 +1,4 @@ -namespace Lab.ErrorHandler.API; +namespace Lab.ErrorHandler.API.Models; public enum FailureCode { diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/Properties/launchSettings.json b/Error Handler/Without Exception/Lab.ErrorHandler.API/Properties/launchSettings.json new file mode 100644 index 00000000..e88fda27 --- /dev/null +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:19210", + "sslPort": 44321 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5031", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7164;http://localhost:5031", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs b/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs index 3d418188..44160860 100644 --- a/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs +++ b/Error Handler/Without Exception/Lab.ErrorHandler.API/SecondStepExtensions.cs @@ -1,3 +1,5 @@ +using Lab.ErrorHandler.API.Models; + namespace Lab.ErrorHandler.API; public static class SecondStepExtensions From f1dd1f83612a6647266422120cbdfff231f0e9a9 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 10 Sep 2023 02:21:12 +0800 Subject: [PATCH 326/424] add project --- .../Lab.Test.Container/Lab.Test.Container.sln | 22 +++++++ .../Lab.TestContainers.Test.csproj | 21 +++++++ .../Lab.TestContainers.Test/UnitTest1.cs | 57 +++++++++++++++++++ .../Lab.TestContainers.Test/Usings.cs | 1 + .../Controllers/WeatherForecastController.cs | 32 +++++++++++ .../Lab.TestContainers.WebApi.csproj | 15 +++++ .../Lab.TestContainers.WebApi/Program.cs | 26 +++++++++ .../Properties/launchSettings.json | 41 +++++++++++++ .../WeatherForecast.cs | 12 ++++ .../appsettings.Development.json | 8 +++ .../appsettings.json | 9 +++ 11 files changed, 244 insertions(+) create mode 100644 Test/Lab.Test.Container/Lab.Test.Container.sln create mode 100644 Test/Lab.Test.Container/Lab.TestContainers.Test/Lab.TestContainers.Test.csproj create mode 100644 Test/Lab.Test.Container/Lab.TestContainers.Test/UnitTest1.cs create mode 100644 Test/Lab.Test.Container/Lab.TestContainers.Test/Usings.cs create mode 100644 Test/Lab.Test.Container/Lab.TestContainers.WebApi/Controllers/WeatherForecastController.cs create mode 100644 Test/Lab.Test.Container/Lab.TestContainers.WebApi/Lab.TestContainers.WebApi.csproj create mode 100644 Test/Lab.Test.Container/Lab.TestContainers.WebApi/Program.cs create mode 100644 Test/Lab.Test.Container/Lab.TestContainers.WebApi/Properties/launchSettings.json create mode 100644 Test/Lab.Test.Container/Lab.TestContainers.WebApi/WeatherForecast.cs create mode 100644 Test/Lab.Test.Container/Lab.TestContainers.WebApi/appsettings.Development.json create mode 100644 Test/Lab.Test.Container/Lab.TestContainers.WebApi/appsettings.json diff --git a/Test/Lab.Test.Container/Lab.Test.Container.sln b/Test/Lab.Test.Container/Lab.Test.Container.sln new file mode 100644 index 00000000..040dc3d4 --- /dev/null +++ b/Test/Lab.Test.Container/Lab.Test.Container.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.TestContainers.Test", "Lab.TestContainers.Test\Lab.TestContainers.Test.csproj", "{71FF0299-AB51-4871-A05F-162C9DEC3790}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.TestContainers.WebApi", "Lab.TestContainers.WebApi\Lab.TestContainers.WebApi.csproj", "{DD417E0D-2DCD-4AA8-8E8C-C05E32AD7CF4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {71FF0299-AB51-4871-A05F-162C9DEC3790}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71FF0299-AB51-4871-A05F-162C9DEC3790}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71FF0299-AB51-4871-A05F-162C9DEC3790}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71FF0299-AB51-4871-A05F-162C9DEC3790}.Release|Any CPU.Build.0 = Release|Any CPU + {DD417E0D-2DCD-4AA8-8E8C-C05E32AD7CF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD417E0D-2DCD-4AA8-8E8C-C05E32AD7CF4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD417E0D-2DCD-4AA8-8E8C-C05E32AD7CF4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD417E0D-2DCD-4AA8-8E8C-C05E32AD7CF4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Test/Lab.Test.Container/Lab.TestContainers.Test/Lab.TestContainers.Test.csproj b/Test/Lab.Test.Container/Lab.TestContainers.Test/Lab.TestContainers.Test.csproj new file mode 100644 index 00000000..a2a4c70e --- /dev/null +++ b/Test/Lab.Test.Container/Lab.TestContainers.Test/Lab.TestContainers.Test.csproj @@ -0,0 +1,21 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + diff --git a/Test/Lab.Test.Container/Lab.TestContainers.Test/UnitTest1.cs b/Test/Lab.Test.Container/Lab.TestContainers.Test/UnitTest1.cs new file mode 100644 index 00000000..74f8817f --- /dev/null +++ b/Test/Lab.Test.Container/Lab.TestContainers.Test/UnitTest1.cs @@ -0,0 +1,57 @@ +using System.Data.Common; +using System.Diagnostics; +using DotNet.Testcontainers.Builders; +using Npgsql; +using Testcontainers.PostgreSql; + +namespace Lab.TestContainers.Test; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public async Task GenericContainer() + { + var waitStrategy = Wait.ForUnixContainer().UntilCommandIsCompleted("pg_isready"); + var postgreSqlContainer = new ContainerBuilder() + .WithImage("postgres:12-alpine") + .WithName("postgres.12") + .WithPortBinding(5432) + .WithWaitStrategy(waitStrategy) + .WithEnvironment("POSTGRES_USER", "postgres") + .WithEnvironment("POSTGRES_PASSWORD", "postgres") + .Build(); + await postgreSqlContainer.StartAsync() + .ConfigureAwait(false); + + var connectionString = "Host=localhost;Port=5432;Username=postgres;Password=postgres;Database=postgres"; + await using DbConnection connection = new NpgsqlConnection(connectionString); + await using DbCommand command = new NpgsqlCommand(); + await connection.OpenAsync(); + command.Connection = connection; + command.CommandText = "SELECT 1"; + } + + [TestMethod] + public async Task ModuleContainer() + { + var waitStrategy = Wait.ForUnixContainer().UntilCommandIsCompleted("pg_isready"); + var postgreSqlContainer = new PostgreSqlBuilder() + .WithImage("postgres:12-alpine") + .WithName("postgres.12") + .WithPortBinding(5432, assignRandomHostPort: true) + .WithWaitStrategy(waitStrategy) + .WithUsername("postgres") + .WithPassword("postgres") + .Build(); + await postgreSqlContainer.StartAsync() + .ConfigureAwait(false); + + var connectionString = postgreSqlContainer.GetConnectionString(); + await using DbConnection connection = new NpgsqlConnection(connectionString); + await using DbCommand command = new NpgsqlCommand(); + await connection.OpenAsync(); + command.Connection = connection; + command.CommandText = "SELECT 1"; + } +} \ No newline at end of file diff --git a/Test/Lab.Test.Container/Lab.TestContainers.Test/Usings.cs b/Test/Lab.Test.Container/Lab.TestContainers.Test/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/Test/Lab.Test.Container/Lab.TestContainers.Test/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Controllers/WeatherForecastController.cs b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..5c347520 --- /dev/null +++ b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Controllers/WeatherForecastController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.Test.Container.WebApi.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } +} \ No newline at end of file diff --git a/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Lab.TestContainers.WebApi.csproj b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Lab.TestContainers.WebApi.csproj new file mode 100644 index 00000000..12ae0c4c --- /dev/null +++ b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Lab.TestContainers.WebApi.csproj @@ -0,0 +1,15 @@ + + + + net7.0 + enable + enable + Lab.Test.Container.WebApi + + + + + + + + diff --git a/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Program.cs b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Program.cs new file mode 100644 index 00000000..329fe361 --- /dev/null +++ b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Program.cs @@ -0,0 +1,26 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Properties/launchSettings.json b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Properties/launchSettings.json new file mode 100644 index 00000000..553487f8 --- /dev/null +++ b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:18819", + "sslPort": 44337 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5214", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7023;http://localhost:5214", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Test/Lab.Test.Container/Lab.TestContainers.WebApi/WeatherForecast.cs b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/WeatherForecast.cs new file mode 100644 index 00000000..0c88c8fa --- /dev/null +++ b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace Lab.Test.Container.WebApi; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/Test/Lab.Test.Container/Lab.TestContainers.WebApi/appsettings.Development.json b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Test/Lab.Test.Container/Lab.TestContainers.WebApi/appsettings.json b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From ecd9dc07f17680135db548528eec8bd11bca700d Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 10 Sep 2023 11:51:24 +0800 Subject: [PATCH 327/424] add docker file --- Test/Lab.Test.Container/.dockerignore | 25 ++++++++++++++ .../Lab.TestContainers.Test/UnitTest1.cs | 33 +++++++++++++++++++ .../Controllers/DemoController.cs | 21 ++++++++++++ .../Lab.TestContainers.WebApi/Dockerfile | 20 +++++++++++ .../Lab.TestContainers.WebApi.csproj | 7 ++++ 5 files changed, 106 insertions(+) create mode 100644 Test/Lab.Test.Container/.dockerignore create mode 100644 Test/Lab.Test.Container/Lab.TestContainers.WebApi/Controllers/DemoController.cs create mode 100644 Test/Lab.Test.Container/Lab.TestContainers.WebApi/Dockerfile diff --git a/Test/Lab.Test.Container/.dockerignore b/Test/Lab.Test.Container/.dockerignore new file mode 100644 index 00000000..cd967fc3 --- /dev/null +++ b/Test/Lab.Test.Container/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/Test/Lab.Test.Container/Lab.TestContainers.Test/UnitTest1.cs b/Test/Lab.Test.Container/Lab.TestContainers.Test/UnitTest1.cs index 74f8817f..2655e165 100644 --- a/Test/Lab.Test.Container/Lab.TestContainers.Test/UnitTest1.cs +++ b/Test/Lab.Test.Container/Lab.TestContainers.Test/UnitTest1.cs @@ -54,4 +54,37 @@ await postgreSqlContainer.StartAsync() command.Connection = connection; command.CommandText = "SELECT 1"; } + + [TestMethod] + public async Task CreateImage() + { + var solutionDirectory = CommonDirectoryPath.GetSolutionDirectory(); + var dockerFilePath = "Lab.TestContainers.WebApi/Dockerfile"; + var imageBuilder = new ImageFromDockerfileBuilder() + .WithDockerfileDirectory(solutionDirectory, string.Empty) + .WithDockerfile(dockerFilePath) + .WithName("my.aspnet.core.7") + .Build(); + + await imageBuilder.CreateAsync() + .ConfigureAwait(false); + var container = new ContainerBuilder() + .WithImage("my.aspnet.core.7") + .WithName("my.aspnet.core.7") + .WithPortBinding(80) + .Build() + ; + await container.StartAsync() + .ConfigureAwait(false); + ; + var scheme = "http"; + var host = "localhost"; + var port = container.GetMappedPublicPort(80); + var url = "demo"; + + var httpClient = new HttpClient(); + var requestUri = new UriBuilder(scheme, host, port, url).Uri; + var actual = await httpClient.GetStringAsync(requestUri); + Assert.AreEqual("OK~", actual); + } } \ No newline at end of file diff --git a/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Controllers/DemoController.cs b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Controllers/DemoController.cs new file mode 100644 index 00000000..7d4b2e3f --- /dev/null +++ b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Controllers/DemoController.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.Test.Container.WebApi.Controllers; + +[ApiController] +[Route("[controller]")] +public class DemoController : ControllerBase +{ + private readonly ILogger _logger; + + public DemoController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetDemo")] + public ActionResult Get() + { + return this.Ok("OK~"); + } +} \ No newline at end of file diff --git a/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Dockerfile b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Dockerfile new file mode 100644 index 00000000..00f09879 --- /dev/null +++ b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Dockerfile @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +WORKDIR /src +COPY ["Lab.TestContainers.WebApi/Lab.TestContainers.WebApi.csproj", "Lab.TestContainers.WebApi/"] +RUN dotnet restore "Lab.TestContainers.WebApi/Lab.TestContainers.WebApi.csproj" +COPY . . +WORKDIR "/src/Lab.TestContainers.WebApi" +RUN dotnet build "Lab.TestContainers.WebApi.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Lab.TestContainers.WebApi.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Lab.TestContainers.WebApi.dll"] diff --git a/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Lab.TestContainers.WebApi.csproj b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Lab.TestContainers.WebApi.csproj index 12ae0c4c..a88411cf 100644 --- a/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Lab.TestContainers.WebApi.csproj +++ b/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Lab.TestContainers.WebApi.csproj @@ -5,6 +5,7 @@ enable enable Lab.Test.Container.WebApi + Linux @@ -12,4 +13,10 @@ + + + .dockerignore + + + From 0d4809d6967efd218b14af50d25fe54d1a4b1b91 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 12 Sep 2023 13:39:16 +0800 Subject: [PATCH 328/424] add project --- .../Lab.RefitClient.TestProject.csproj | 31 + .../PetStoreTestServer.cs | 19 + .../Lab.RefitClient.TestProject/UnitTest1.cs | 40 + .../Lab.RefitClient.TestProject/Usings.cs | 1 + .../AutoGenerated/IPetstoreController.cs | 932 +++++++++++++ .../Controllers/PetstoreControllerImpl.cs | 110 ++ .../Lab.RefitClient.WebAPI.csproj | 15 + .../Lab.RefitClient.WebAPI/Program.cs | 32 + .../Properties/launchSettings.json | 41 + .../Lab.RefitClient.WebAPI/WeatherForecast.cs | 12 + .../appsettings.Development.json | 8 + .../Lab.RefitClient.WebAPI/appsettings.json | 9 + .../Lab.RefitClient/Lab.RefitClient.sln | 28 + .../Lab.RefitClient/Lab.RefitClient.csproj | 13 + .../Lab.RefitClient/PetStoreClient.cs | 542 ++++++++ WebAPI/Swagger/Lab.RefitClient/Taskfile.yml | 13 + WebAPI/Swagger/Lab.RefitClient/openapi.json | 1225 +++++++++++++++++ 17 files changed, 3071 insertions(+) create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/Lab.RefitClient.TestProject.csproj create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/PetStoreTestServer.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/Usings.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/AutoGenerated/IPetstoreController.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Lab.RefitClient.WebAPI.csproj create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Program.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Properties/launchSettings.json create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/WeatherForecast.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/appsettings.Development.json create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/appsettings.json create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.sln create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/Lab.RefitClient.csproj create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreClient.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Taskfile.yml create mode 100644 WebAPI/Swagger/Lab.RefitClient/openapi.json diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/Lab.RefitClient.TestProject.csproj b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/Lab.RefitClient.TestProject.csproj new file mode 100644 index 00000000..bb13570c --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/Lab.RefitClient.TestProject.csproj @@ -0,0 +1,31 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/PetStoreTestServer.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/PetStoreTestServer.cs new file mode 100644 index 00000000..0326ad69 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/PetStoreTestServer.cs @@ -0,0 +1,19 @@ +using Lab.RefitClient.WebAPI.Controllers; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Lab.RefitClient.TestProject; + +public class PetStoreTestServer : WebApplicationFactory +{ + private void ConfigureServices(IServiceCollection services) + { + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureServices(this.ConfigureServices); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs new file mode 100644 index 00000000..2357d0b2 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs @@ -0,0 +1,40 @@ +using Lab.RefitClient.GeneratedCode.PetStore; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.FileProviders; +using Refit; + +namespace Lab.RefitClient.TestProject; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public async Task 自動化測試() + { + var server = new PetStoreTestServer(); + var httpClient = server.CreateClient(); + httpClient.BaseAddress = new Uri(httpClient.BaseAddress, "api/v3"); + var client = RestService.For(httpClient); + var username = "yao"; + var response = await client.GetUserByName(username); + var content = response.Content; + Assert.AreEqual(username, content.Username); + } + + [TestMethod] + public async Task 手動建立API() + { + var baseUrl = "https://localhost:7285/api/v3"; + var services = new ServiceCollection(); + services.AddRefitClient() + .ConfigureHttpClient(c => { c.BaseAddress = new Uri(baseUrl); }); + var serviceProvider = services.BuildServiceProvider(); + var client = serviceProvider.GetService(); + var username = "yao"; + var response = await client.GetUserByName(username); + var content = response.Content; + Assert.AreEqual(username, content.Username); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/Usings.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/AutoGenerated/IPetstoreController.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/AutoGenerated/IPetstoreController.cs new file mode 100644 index 00000000..79408615 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/AutoGenerated/IPetstoreController.cs @@ -0,0 +1,932 @@ +//---------------------- +// +// Generated using the NSwag toolchain v13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0)) (http://NSwag.org) +// +//---------------------- + +#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." +#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." +#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' +#pragma warning disable 612 // Disable "CS0612 '...' is obsolete" +#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... +#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." +#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" +#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" +#pragma warning disable 8603 // Disable "CS8603 Possible null reference return" + +namespace Lab.RefitClient.WebAPI.Controllers +{ + using System = global::System; + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0))")] + public interface IPetStoreController + { + + /// + /// Update an existing pet + /// + + /// + /// Update an existing pet by Id + /// + + /// Update an existent pet in the store + + /// Successful operation + + System.Threading.Tasks.Task UpdatePetAsync(Pet body); + + /// + /// Add a new pet to the store + /// + + /// + /// Add a new pet to the store + /// + + /// Create a new pet in the store + + /// Successful operation + + System.Threading.Tasks.Task AddPetAsync(Pet body); + + /// + /// Finds Pets by status + /// + + /// + /// Multiple status values can be provided with comma separated strings + /// + + /// Status values that need to be considered for filter + + /// successful operation + + System.Threading.Tasks.Task> FindPetsByStatusAsync(Status status); + + /// + /// Finds Pets by tags + /// + + /// + /// Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. + /// + + /// Tags to filter by + + /// successful operation + + System.Threading.Tasks.Task> FindPetsByTagsAsync(System.Collections.Generic.IEnumerable tags); + + /// + /// Find pet by ID + /// + + /// + /// Returns a single pet + /// + + /// ID of pet to return + + /// successful operation + + System.Threading.Tasks.Task GetPetByIdAsync(long petId); + + /// + /// Updates a pet in the store with form data + /// + + /// ID of pet that needs to be updated + + /// Name of pet that needs to be updated + + /// Status of pet that needs to be updated + + System.Threading.Tasks.Task UpdatePetWithFormAsync(long petId, string name, string status); + + /// + /// Deletes a pet + /// + + + /// Pet id to delete + + System.Threading.Tasks.Task DeletePetAsync(string api_key, long petId); + + /// + /// uploads an image + /// + + /// ID of pet to update + + /// Additional Metadata + + + /// successful operation + + System.Threading.Tasks.Task UploadFileAsync(long petId, string additionalMetadata, Microsoft.AspNetCore.Http.IFormFile body); + + /// + /// Returns pet inventories by status + /// + + /// + /// Returns a map of status codes to quantities + /// + + /// successful operation + + System.Threading.Tasks.Task> GetInventoryAsync(); + + /// + /// Place an order for a pet + /// + + /// + /// Place a new order in the store + /// + + /// successful operation + + System.Threading.Tasks.Task PlaceOrderAsync(Order body); + + /// + /// Find purchase order by ID + /// + + /// + /// For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions. + /// + + /// ID of order that needs to be fetched + + /// successful operation + + System.Threading.Tasks.Task GetOrderByIdAsync(long orderId); + + /// + /// Delete purchase order by ID + /// + + /// + /// For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors + /// + + /// ID of the order that needs to be deleted + + System.Threading.Tasks.Task DeleteOrderAsync(long orderId); + + /// + /// Create user + /// + + /// + /// This can only be done by the logged in user. + /// + + /// Created user object + + /// successful operation + + System.Threading.Tasks.Task CreateUserAsync(User body); + + /// + /// Creates list of users with given input array + /// + + /// + /// Creates list of users with given input array + /// + + /// Successful operation + + System.Threading.Tasks.Task CreateUsersWithListInputAsync(System.Collections.Generic.IEnumerable body); + + /// + /// Logs user into the system + /// + + /// The user name for login + + /// The password for login in clear text + + /// successful operation + + System.Threading.Tasks.Task LoginUserAsync(string username, string password); + + /// + /// Logs out current logged in user session + /// + + /// successful operation + + System.Threading.Tasks.Task LogoutUserAsync(); + + /// + /// Get user by user name + /// + + /// The name that needs to be fetched. Use user1 for testing. + + /// successful operation + + System.Threading.Tasks.Task GetUserByNameAsync(string username); + + /// + /// Update user + /// + + /// + /// This can only be done by the logged in user. + /// + + /// name that need to be deleted + + /// Update an existent user in the store + + /// successful operation + + System.Threading.Tasks.Task UpdateUserAsync(string username, User body); + + /// + /// Delete user + /// + + /// + /// This can only be done by the logged in user. + /// + + /// The name that needs to be deleted + + System.Threading.Tasks.Task DeleteUserAsync(string username); + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0))")] + [Microsoft.AspNetCore.Mvc.Route("api/v3")] + + public partial class PetStoreController : Microsoft.AspNetCore.Mvc.ControllerBase + { + private IPetStoreController _implementation; + + public PetStoreController(IPetStoreController implementation) + { + _implementation = implementation; + } + + /// + /// Update an existing pet + /// + /// + /// Update an existing pet by Id + /// + /// Update an existent pet in the store + /// Successful operation + [Microsoft.AspNetCore.Mvc.HttpPut, Microsoft.AspNetCore.Mvc.Route("pet")] + public System.Threading.Tasks.Task UpdatePet([Microsoft.AspNetCore.Mvc.FromBody] Pet body) + { + + return _implementation.UpdatePetAsync(body); + } + + /// + /// Add a new pet to the store + /// + /// + /// Add a new pet to the store + /// + /// Create a new pet in the store + /// Successful operation + [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("pet")] + public System.Threading.Tasks.Task AddPet([Microsoft.AspNetCore.Mvc.FromBody] Pet body) + { + + return _implementation.AddPetAsync(body); + } + + /// + /// Finds Pets by status + /// + /// + /// Multiple status values can be provided with comma separated strings + /// + /// Status values that need to be considered for filter + /// successful operation + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pet/findByStatus")] + public System.Threading.Tasks.Task> FindPetsByStatus([Microsoft.AspNetCore.Mvc.FromQuery] Status? status) + { + + return _implementation.FindPetsByStatusAsync(status ?? Lab.RefitClient.WebAPI.Controllers.Status.Available); + } + + /// + /// Finds Pets by tags + /// + /// + /// Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. + /// + /// Tags to filter by + /// successful operation + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pet/findByTags")] + public System.Threading.Tasks.Task> FindPetsByTags([Microsoft.AspNetCore.Mvc.FromQuery] System.Collections.Generic.IEnumerable tags) + { + + return _implementation.FindPetsByTagsAsync(tags); + } + + /// + /// Find pet by ID + /// + /// + /// Returns a single pet + /// + /// ID of pet to return + /// successful operation + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pet/{petId}")] + public System.Threading.Tasks.Task GetPetById(long petId) + { + + return _implementation.GetPetByIdAsync(petId); + } + + /// + /// Updates a pet in the store with form data + /// + /// ID of pet that needs to be updated + /// Name of pet that needs to be updated + /// Status of pet that needs to be updated + [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("pet/{petId}")] + public System.Threading.Tasks.Task UpdatePetWithForm(long petId, [Microsoft.AspNetCore.Mvc.FromQuery] string name, [Microsoft.AspNetCore.Mvc.FromQuery] string status) + { + + return _implementation.UpdatePetWithFormAsync(petId, name, status); + } + + /// + /// Deletes a pet + /// + /// Pet id to delete + [Microsoft.AspNetCore.Mvc.HttpDelete, Microsoft.AspNetCore.Mvc.Route("pet/{petId}")] + public System.Threading.Tasks.Task DeletePet([Microsoft.AspNetCore.Mvc.FromHeader] string api_key, long petId) + { + + return _implementation.DeletePetAsync(api_key, petId); + } + + /// + /// uploads an image + /// + /// ID of pet to update + /// Additional Metadata + /// successful operation + [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("pet/{petId}/uploadImage")] + public System.Threading.Tasks.Task UploadFile(long petId, [Microsoft.AspNetCore.Mvc.FromQuery] string additionalMetadata, Microsoft.AspNetCore.Http.IFormFile body) + { + + return _implementation.UploadFileAsync(petId, additionalMetadata, body); + } + + /// + /// Returns pet inventories by status + /// + /// + /// Returns a map of status codes to quantities + /// + /// successful operation + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("store/inventory")] + public System.Threading.Tasks.Task> GetInventory() + { + + return _implementation.GetInventoryAsync(); + } + + /// + /// Place an order for a pet + /// + /// + /// Place a new order in the store + /// + /// successful operation + [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("store/order")] + public System.Threading.Tasks.Task PlaceOrder([Microsoft.AspNetCore.Mvc.FromBody] Order body) + { + + return _implementation.PlaceOrderAsync(body); + } + + /// + /// Find purchase order by ID + /// + /// + /// For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions. + /// + /// ID of order that needs to be fetched + /// successful operation + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("store/order/{orderId}")] + public System.Threading.Tasks.Task GetOrderById(long orderId) + { + + return _implementation.GetOrderByIdAsync(orderId); + } + + /// + /// Delete purchase order by ID + /// + /// + /// For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors + /// + /// ID of the order that needs to be deleted + [Microsoft.AspNetCore.Mvc.HttpDelete, Microsoft.AspNetCore.Mvc.Route("store/order/{orderId}")] + public System.Threading.Tasks.Task DeleteOrder(long orderId) + { + + return _implementation.DeleteOrderAsync(orderId); + } + + /// + /// Create user + /// + /// + /// This can only be done by the logged in user. + /// + /// Created user object + /// successful operation + [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("user")] + public System.Threading.Tasks.Task CreateUser([Microsoft.AspNetCore.Mvc.FromBody] User body) + { + + return _implementation.CreateUserAsync(body); + } + + /// + /// Creates list of users with given input array + /// + /// + /// Creates list of users with given input array + /// + /// Successful operation + [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("user/createWithList")] + public System.Threading.Tasks.Task CreateUsersWithListInput([Microsoft.AspNetCore.Mvc.FromBody] System.Collections.Generic.IEnumerable body) + { + + return _implementation.CreateUsersWithListInputAsync(body); + } + + /// + /// Logs user into the system + /// + /// The user name for login + /// The password for login in clear text + /// successful operation + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("user/login")] + public System.Threading.Tasks.Task LoginUser([Microsoft.AspNetCore.Mvc.FromQuery] string username, [Microsoft.AspNetCore.Mvc.FromQuery] string password) + { + + return _implementation.LoginUserAsync(username, password); + } + + /// + /// Logs out current logged in user session + /// + /// successful operation + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("user/logout")] + public System.Threading.Tasks.Task LogoutUser() + { + + return _implementation.LogoutUserAsync(); + } + + /// + /// Get user by user name + /// + /// The name that needs to be fetched. Use user1 for testing. + /// successful operation + [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("user/{username}")] + public System.Threading.Tasks.Task GetUserByName(string username) + { + + return _implementation.GetUserByNameAsync(username); + } + + /// + /// Update user + /// + /// + /// This can only be done by the logged in user. + /// + /// name that need to be deleted + /// Update an existent user in the store + /// successful operation + [Microsoft.AspNetCore.Mvc.HttpPut, Microsoft.AspNetCore.Mvc.Route("user/{username}")] + public System.Threading.Tasks.Task UpdateUser(string username, [Microsoft.AspNetCore.Mvc.FromBody] User body) + { + + return _implementation.UpdateUserAsync(username, body); + } + + /// + /// Delete user + /// + /// + /// This can only be done by the logged in user. + /// + /// The name that needs to be deleted + [Microsoft.AspNetCore.Mvc.HttpDelete, Microsoft.AspNetCore.Mvc.Route("user/{username}")] + public System.Threading.Tasks.Task DeleteUser(string username) + { + + return _implementation.DeleteUserAsync(username); + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class Order + { + + [System.Text.Json.Serialization.JsonPropertyName("id")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public long Id { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("petId")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public long PetId { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("quantity")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public int Quantity { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("shipDate")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.DateTimeOffset ShipDate { get; set; } + + /// + /// Order Status + /// + + [System.Text.Json.Serialization.JsonPropertyName("status")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] + public OrderStatus Status { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("complete")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public bool Complete { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class Customer + { + + [System.Text.Json.Serialization.JsonPropertyName("id")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public long Id { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("username")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Username { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("address")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.List
Address { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class Address + { + + [System.Text.Json.Serialization.JsonPropertyName("street")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Street { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("city")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string City { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("state")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string State { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("zip")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Zip { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class Category + { + + [System.Text.Json.Serialization.JsonPropertyName("id")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public long Id { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("name")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Name { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class User + { + + [System.Text.Json.Serialization.JsonPropertyName("id")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public long Id { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("username")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Username { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("firstName")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string FirstName { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("lastName")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string LastName { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("email")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Email { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("password")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Password { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("phone")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Phone { get; set; } + + /// + /// User Status + /// + + [System.Text.Json.Serialization.JsonPropertyName("userStatus")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public int UserStatus { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class Tag + { + + [System.Text.Json.Serialization.JsonPropertyName("id")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public long Id { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("name")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Name { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class Pet + { + + [System.Text.Json.Serialization.JsonPropertyName("id")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public long Id { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("name")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("category")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public Category Category { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("photoUrls")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.List PhotoUrls { get; set; } = new System.Collections.Generic.List(); + + [System.Text.Json.Serialization.JsonPropertyName("tags")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public System.Collections.Generic.List Tags { get; set; } + + /// + /// pet status in the store + /// + + [System.Text.Json.Serialization.JsonPropertyName("status")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] + public PetStatus Status { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class ApiResponse + { + + [System.Text.Json.Serialization.JsonPropertyName("code")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public int Code { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("type")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Type { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("message")] + + [System.Text.Json.Serialization.JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)] + public string Message { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [System.Text.Json.Serialization.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0))")] + public enum Status + { + + [System.Runtime.Serialization.EnumMember(Value = @"available")] + Available = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"pending")] + Pending = 1, + + [System.Runtime.Serialization.EnumMember(Value = @"sold")] + Sold = 2, + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0))")] + public enum OrderStatus + { + + [System.Runtime.Serialization.EnumMember(Value = @"placed")] + Placed = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"approved")] + Approved = 1, + + [System.Runtime.Serialization.EnumMember(Value = @"delivered")] + Delivered = 2, + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0))")] + public enum PetStatus + { + + [System.Runtime.Serialization.EnumMember(Value = @"available")] + Available = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"pending")] + Pending = 1, + + [System.Runtime.Serialization.EnumMember(Value = @"sold")] + Sold = 2, + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class FileParameter + { + public FileParameter(System.IO.Stream data) + : this (data, null, null) + { + } + + public FileParameter(System.IO.Stream data, string fileName) + : this (data, fileName, null) + { + } + + public FileParameter(System.IO.Stream data, string fileName, string contentType) + { + Data = data; + FileName = fileName; + ContentType = contentType; + } + + public System.IO.Stream Data { get; private set; } + + public string FileName { get; private set; } + + public string ContentType { get; private set; } + } + + +} + +#pragma warning restore 108 +#pragma warning restore 114 +#pragma warning restore 472 +#pragma warning restore 612 +#pragma warning restore 1573 +#pragma warning restore 1591 +#pragma warning restore 8073 +#pragma warning restore 3016 +#pragma warning restore 8603 \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs new file mode 100644 index 00000000..447dacf5 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs @@ -0,0 +1,110 @@ +namespace Lab.RefitClient.WebAPI.Controllers; + +public class PetStoreControllerImpl : IPetStoreController +{ + public Task UpdatePetAsync(Pet body) + { + throw new NotImplementedException(); + } + + public Task AddPetAsync(Pet body) + { + throw new NotImplementedException(); + } + + public Task> FindPetsByStatusAsync(Status status) + { + throw new NotImplementedException(); + } + + public Task> FindPetsByTagsAsync(IEnumerable tags) + { + throw new NotImplementedException(); + } + + public Task GetPetByIdAsync(long petId) + { + throw new NotImplementedException(); + } + + public Task UpdatePetWithFormAsync(long petId, string name, string status) + { + throw new NotImplementedException(); + } + + public Task DeletePetAsync(string api_key, long petId) + { + throw new NotImplementedException(); + } + + public Task UploadFileAsync(long petId, string additionalMetadata, IFormFile body) + { + throw new NotImplementedException(); + } + + public Task> GetInventoryAsync() + { + throw new NotImplementedException(); + } + + public Task PlaceOrderAsync(Order body) + { + throw new NotImplementedException(); + } + + public Task GetOrderByIdAsync(long orderId) + { + throw new NotImplementedException(); + } + + public Task DeleteOrderAsync(long orderId) + { + throw new NotImplementedException(); + } + + public Task CreateUserAsync(User body) + { + throw new NotImplementedException(); + } + + public Task CreateUsersWithListInputAsync(IEnumerable body) + { + throw new NotImplementedException(); + } + + public Task LoginUserAsync(string username, string password) + { + throw new NotImplementedException(); + } + + public Task LogoutUserAsync() + { + throw new NotImplementedException(); + } + + public async Task GetUserByNameAsync(string username) + { + return new User + { + Id = 0, + Username = username, + FirstName = null, + LastName = null, + Email = "yao@aa.bb", + Password = null, + Phone = null, + UserStatus = 0, + AdditionalProperties = null + }; + } + + public Task UpdateUserAsync(string username, User body) + { + throw new NotImplementedException(); + } + + public Task DeleteUserAsync(string username) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Lab.RefitClient.WebAPI.csproj b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Lab.RefitClient.WebAPI.csproj new file mode 100644 index 00000000..0894f53c --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Lab.RefitClient.WebAPI.csproj @@ -0,0 +1,15 @@ + + + + net7.0 + enable + enable + + + + + + + + + diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Program.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Program.cs new file mode 100644 index 00000000..77a81721 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Program.cs @@ -0,0 +1,32 @@ +using Lab.RefitClient.WebAPI.Controllers; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddScoped(); +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); + +public partial class Program +{ +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Properties/launchSettings.json b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Properties/launchSettings.json new file mode 100644 index 00000000..9919f9b0 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:55975", + "sslPort": 44397 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5232", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7285;http://localhost:5232", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/WeatherForecast.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/WeatherForecast.cs new file mode 100644 index 00000000..c747db14 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace Lab.RefitClient.WebAPI; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/appsettings.Development.json b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/appsettings.json b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.sln b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.sln new file mode 100644 index 00000000..078ebb02 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.RefitClient.WebAPI", "Lab.RefitClient.WebAPI\Lab.RefitClient.WebAPI.csproj", "{A327EA7D-1FA9-4CA0-A879-612D5DD6476D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.RefitClient.TestProject", "Lab.RefitClient.TestProject\Lab.RefitClient.TestProject.csproj", "{41937C8E-4831-4DA2-AA12-419C5727A2D8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.RefitClient", "Lab.RefitClient\Lab.RefitClient.csproj", "{B0351748-0968-4F05-92F5-EF1C58D7CC3D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A327EA7D-1FA9-4CA0-A879-612D5DD6476D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A327EA7D-1FA9-4CA0-A879-612D5DD6476D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A327EA7D-1FA9-4CA0-A879-612D5DD6476D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A327EA7D-1FA9-4CA0-A879-612D5DD6476D}.Release|Any CPU.Build.0 = Release|Any CPU + {41937C8E-4831-4DA2-AA12-419C5727A2D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41937C8E-4831-4DA2-AA12-419C5727A2D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41937C8E-4831-4DA2-AA12-419C5727A2D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41937C8E-4831-4DA2-AA12-419C5727A2D8}.Release|Any CPU.Build.0 = Release|Any CPU + {B0351748-0968-4F05-92F5-EF1C58D7CC3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0351748-0968-4F05-92F5-EF1C58D7CC3D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0351748-0968-4F05-92F5-EF1C58D7CC3D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0351748-0968-4F05-92F5-EF1C58D7CC3D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/Lab.RefitClient.csproj b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/Lab.RefitClient.csproj new file mode 100644 index 00000000..b079d69a --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/Lab.RefitClient.csproj @@ -0,0 +1,13 @@ + + + + net7.0 + enable + enable + + + + + + + diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreClient.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreClient.cs new file mode 100644 index 00000000..d5208302 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreClient.cs @@ -0,0 +1,542 @@ +// +// This code was generated by Refitter. +// + +using Refit; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Lab.RefitClient.GeneratedCode.PetStore +{ + [System.CodeDom.Compiler.GeneratedCode("Refitter", "0.7.3.0")] + public interface ISwaggerPetstoreOpenAPI30 + { + /// + /// Update an existing pet by Id + /// + [Headers("Accept: application/xml, application/json")] + [Put("/pet")] + Task> UpdatePet([Body] Pet body); + + /// + /// Add a new pet to the store + /// + [Headers("Accept: application/xml, application/json")] + [Post("/pet")] + Task> AddPet([Body] Pet body); + + /// + /// Multiple status values can be provided with comma separated strings + /// + [Headers("Accept: application/xml, application/json")] + [Get("/pet/findByStatus")] + Task>> FindPetsByStatus([Query] Status? status); + + /// + /// Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. + /// + [Headers("Accept: application/xml, application/json")] + [Get("/pet/findByTags")] + Task>> FindPetsByTags([Query(CollectionFormat.Multi)] IEnumerable tags); + + /// + /// Returns a single pet + /// + [Headers("Accept: application/xml, application/json")] + [Get("/pet/{petId}")] + Task> GetPetById(long petId); + + [Post("/pet/{petId}")] + Task UpdatePetWithForm(long petId, [Query] string name, [Query] string status); + + [Delete("/pet/{petId}")] + Task DeletePet(long petId, [Header("api_key")] string api_key); + + [Headers("Accept: application/json")] + [Post("/pet/{petId}/uploadImage")] + Task> UploadFile(long petId, [Query] string additionalMetadata, StreamPart body); + + /// + /// Returns a map of status codes to quantities + /// + [Headers("Accept: application/json")] + [Get("/store/inventory")] + Task>> GetInventory(); + + /// + /// Place a new order in the store + /// + [Headers("Accept: application/json")] + [Post("/store/order")] + Task> PlaceOrder([Body] Order body); + + /// + /// For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions. + /// + [Headers("Accept: application/xml, application/json")] + [Get("/store/order/{orderId}")] + Task> GetOrderById(long orderId); + + /// + /// For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors + /// + [Delete("/store/order/{orderId}")] + Task DeleteOrder(long orderId); + + /// + /// This can only be done by the logged in user. + /// + [Headers("Accept: application/json, application/xml")] + [Post("/user")] + Task CreateUser([Body] User body); + + /// + /// Creates list of users with given input array + /// + [Headers("Accept: application/xml, application/json")] + [Post("/user/createWithList")] + Task> CreateUsersWithListInput([Body] IEnumerable body); + + [Headers("Accept: application/xml, application/json")] + [Get("/user/login")] + Task> LoginUser([Query] string username, [Query] string password); + + [Get("/user/logout")] + Task LogoutUser(); + + [Headers("Accept: application/xml, application/json")] + [Get("/user/{username}")] + Task> GetUserByName(string username); + + /// + /// This can only be done by the logged in user. + /// + [Put("/user/{username}")] + Task UpdateUser(string username, [Body] User body); + + /// + /// This can only be done by the logged in user. + /// + [Delete("/user/{username}")] + Task DeleteUser(string username); + + + } +} + + +//---------------------- +// +// Generated using the NSwag toolchain v13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0)) (http://NSwag.org) +// +//---------------------- + +#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." +#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." +#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' +#pragma warning disable 612 // Disable "CS0612 '...' is obsolete" +#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... +#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." +#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" +#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" +#pragma warning disable 8603 // Disable "CS8603 Possible null reference return" +#pragma warning disable 8604 // Disable "CS8604 Possible null reference argument for parameter" + +namespace Lab.RefitClient.GeneratedCode.PetStore +{ + using System = global::System; + + + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")] + public partial class Order + { + + [JsonPropertyName("id")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public long Id { get; set; } + + [JsonPropertyName("petId")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public long PetId { get; set; } + + [JsonPropertyName("quantity")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int Quantity { get; set; } + + [JsonPropertyName("shipDate")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public System.DateTimeOffset ShipDate { get; set; } + + /// + /// Order Status + /// + + [JsonPropertyName("status")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + [JsonConverter(typeof(JsonStringEnumConverter))] + public OrderStatus Status { get; set; } + + [JsonPropertyName("complete")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public bool Complete { get; set; } + + private IDictionary _additionalProperties; + + [JsonExtensionData] + public IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")] + public partial class Customer + { + + [JsonPropertyName("id")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public long Id { get; set; } + + [JsonPropertyName("username")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Username { get; set; } + + [JsonPropertyName("address")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public ICollection
Address { get; set; } + + private IDictionary _additionalProperties; + + [JsonExtensionData] + public IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")] + public partial class Address + { + + [JsonPropertyName("street")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Street { get; set; } + + [JsonPropertyName("city")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string City { get; set; } + + [JsonPropertyName("state")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string State { get; set; } + + [JsonPropertyName("zip")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Zip { get; set; } + + private IDictionary _additionalProperties; + + [JsonExtensionData] + public IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")] + public partial class Category + { + + [JsonPropertyName("id")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public long Id { get; set; } + + [JsonPropertyName("name")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Name { get; set; } + + private IDictionary _additionalProperties; + + [JsonExtensionData] + public IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")] + public partial class User + { + + [JsonPropertyName("id")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public long Id { get; set; } + + [JsonPropertyName("username")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Username { get; set; } + + [JsonPropertyName("firstName")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string FirstName { get; set; } + + [JsonPropertyName("lastName")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string LastName { get; set; } + + [JsonPropertyName("email")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Email { get; set; } + + [JsonPropertyName("password")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Password { get; set; } + + [JsonPropertyName("phone")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Phone { get; set; } + + /// + /// User Status + /// + + [JsonPropertyName("userStatus")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int UserStatus { get; set; } + + private IDictionary _additionalProperties; + + [JsonExtensionData] + public IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")] + public partial class Tag + { + + [JsonPropertyName("id")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public long Id { get; set; } + + [JsonPropertyName("name")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Name { get; set; } + + private IDictionary _additionalProperties; + + [JsonExtensionData] + public IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")] + public partial class Pet + { + + [JsonPropertyName("id")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public long Id { get; set; } + + [JsonPropertyName("name")] + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } + + [JsonPropertyName("category")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public Category Category { get; set; } + + [JsonPropertyName("photoUrls")] + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [System.ComponentModel.DataAnnotations.Required] + public ICollection PhotoUrls { get; set; } = new System.Collections.ObjectModel.Collection(); + + [JsonPropertyName("tags")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public ICollection Tags { get; set; } + + /// + /// pet status in the store + /// + + [JsonPropertyName("status")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + [JsonConverter(typeof(JsonStringEnumConverter))] + public PetStatus Status { get; set; } + + private IDictionary _additionalProperties; + + [JsonExtensionData] + public IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")] + public partial class ApiResponse + { + + [JsonPropertyName("code")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int Code { get; set; } + + [JsonPropertyName("type")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Type { get; set; } + + [JsonPropertyName("message")] + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Message { get; set; } + + private IDictionary _additionalProperties; + + [JsonExtensionData] + public IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")] + public enum Status + { + + [System.Runtime.Serialization.EnumMember(Value = @"available")] + Available = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"pending")] + Pending = 1, + + [System.Runtime.Serialization.EnumMember(Value = @"sold")] + Sold = 2, + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")] + public enum OrderStatus + { + + [System.Runtime.Serialization.EnumMember(Value = @"placed")] + Placed = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"approved")] + Approved = 1, + + [System.Runtime.Serialization.EnumMember(Value = @"delivered")] + Delivered = 2, + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")] + public enum PetStatus + { + + [System.Runtime.Serialization.EnumMember(Value = @"available")] + Available = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"pending")] + Pending = 1, + + [System.Runtime.Serialization.EnumMember(Value = @"sold")] + Sold = 2, + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.20.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v10.0.0.0))")] + public partial class FileParameter + { + public FileParameter(System.IO.Stream data) + : this (data, null, null) + { + } + + public FileParameter(System.IO.Stream data, string fileName) + : this (data, fileName, null) + { + } + + public FileParameter(System.IO.Stream data, string fileName, string contentType) + { + Data = data; + FileName = fileName; + ContentType = contentType; + } + + public System.IO.Stream Data { get; private set; } + + public string FileName { get; private set; } + + public string ContentType { get; private set; } + } + + +} + +#pragma warning restore 108 +#pragma warning restore 114 +#pragma warning restore 472 +#pragma warning restore 612 +#pragma warning restore 1573 +#pragma warning restore 1591 +#pragma warning restore 8073 +#pragma warning restore 3016 +#pragma warning restore 8603 +#pragma warning restore 8604 diff --git a/WebAPI/Swagger/Lab.RefitClient/Taskfile.yml b/WebAPI/Swagger/Lab.RefitClient/Taskfile.yml new file mode 100644 index 00000000..cafd319f --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Taskfile.yml @@ -0,0 +1,13 @@ +# Taskfile.yml + +version: "3" + +tasks: + codegen-client: + desc: codegen client + cmds: + - refitter ./openapi.json --namespace "Lab.RefitClient.GeneratedCode.PetStore" --output ./Lab.RefitClient/PetStoreClient.cs --use-api-response + codegen-server: + desc: codegen server + cmds: + - nswag openapi2cscontroller /input:./openapi.json /classname:PetStore /namespace:Lab.RefitClient.WebAPI.Controllers /output:Lab.RefitClient.WebAPI/AutoGenerated/IPetStoreController.cs /jsonLibrary:SystemTextJson diff --git a/WebAPI/Swagger/Lab.RefitClient/openapi.json b/WebAPI/Swagger/Lab.RefitClient/openapi.json new file mode 100644 index 00000000..12881192 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/openapi.json @@ -0,0 +1,1225 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "Swagger Petstore - OpenAPI 3.0", + "description": "This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\nSwagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\nSome useful links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.17" + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + }, + "servers": [ + { + "url": "/api/v3" + } + ], + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + }, + { + "name": "user", + "description": "Operations about user" + } + ], + "paths": { + "/pet": { + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "Update an existing pet by Id", + "operationId": "updatePet", + "requestBody": { + "description": "Update an existent pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "Add a new pet to the store", + "operationId": "addPet", + "requestBody": { + "description": "Create a new pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": false, + "explode": true, + "schema": { + "type": "string", + "default": "available", + "enum": [ + "available", + "pending", + "sold" + ] + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags", + "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": false, + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid tag value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "api_key": [] + }, + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "name", + "in": "query", + "description": "Name of pet that needs to be updated", + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "description": "Status of pet that needs to be updated", + "schema": { + "type": "string" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "parameters": [ + { + "name": "api_key", + "in": "header", + "description": "", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "400": { + "description": "Invalid pet value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "additionalMetadata", + "in": "query", + "description": "Additional Metadata", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "description": "Place a new order in the store", + "operationId": "placeOrder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "405": { + "description": "Invalid input" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.", + "operationId": "getOrderById", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of order that needs to be fetched", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors", + "operationId": "deleteOrder", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "requestBody": { + "description": "Created user object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "default": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "Creates list of users with given input array", + "operationId": "createUsersWithListInput", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "default": { + "description": "successful operation" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Rate-Limit": { + "description": "calls per hour allowed by the user", + "schema": { + "type": "integer", + "format": "int32" + } + }, + "X-Expires-After": { + "description": "date in UTC when token expires", + "schema": { + "type": "string", + "format": "date-time" + } + } + }, + "content": { + "application/xml": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Update user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Update an existent user in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "default": { + "description": "successful operation" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + } + } + }, + "components": { + "schemas": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "petId": { + "type": "integer", + "format": "int64", + "example": 198772 + }, + "quantity": { + "type": "integer", + "format": "int32", + "example": 7 + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "example": "approved", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "order" + } + }, + "Customer": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 100000 + }, + "username": { + "type": "string", + "example": "fehguy" + }, + "address": { + "type": "array", + "xml": { + "name": "addresses", + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Address" + } + } + }, + "xml": { + "name": "customer" + } + }, + "Address": { + "type": "object", + "properties": { + "street": { + "type": "string", + "example": "437 Lytton" + }, + "city": { + "type": "string", + "example": "Palo Alto" + }, + "state": { + "type": "string", + "example": "CA" + }, + "zip": { + "type": "string", + "example": "94301" + } + }, + "xml": { + "name": "address" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "type": "string", + "example": "Dogs" + } + }, + "xml": { + "name": "category" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "username": { + "type": "string", + "example": "theUser" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "James" + }, + "email": { + "type": "string", + "example": "john@email.com" + }, + "password": { + "type": "string", + "example": "12345" + }, + "phone": { + "type": "string", + "example": "12345" + }, + "userStatus": { + "type": "integer", + "description": "User Status", + "format": "int32", + "example": 1 + } + }, + "xml": { + "name": "user" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "tag" + } + }, + "Pet": { + "required": [ + "name", + "photoUrls" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "name": { + "type": "string", + "example": "doggie" + }, + "category": { + "$ref": "#/components/schemas/Category" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "xml": { + "name": "##default" + } + } + }, + "requestBodies": { + "Pet": { + "description": "Pet object that needs to be added to the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "UserArray": { + "description": "List of user object", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + }, + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://petstore3.swagger.io/oauth/authorize", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + } +} \ No newline at end of file From 9fd98dbc7d4fae10306e94f3b9523433c985bd66 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 13 Sep 2023 09:04:02 +0800 Subject: [PATCH 329/424] =?UTF-8?q?=E7=A7=BB=E9=99=A4=20header?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Lab.RefitClient.TestProject/UnitTest1.cs | 13 ++ .../AutoGenerated/PetStoreController.cs} | 114 +++++++++--------- .../Controllers/PetstoreControllerImpl.cs | 61 +++++++--- .../Lab.RefitClient.WebAPI.csproj | 5 + .../Lab.RefitClient.WebAPI/Program.cs | 2 + .../Lab.RefitClient/PetStoreClient.cs | 6 +- WebAPI/Swagger/Lab.RefitClient/Taskfile.yml | 9 +- WebAPI/Swagger/Lab.RefitClient/openapi.json | 18 +++ 8 files changed, 145 insertions(+), 83 deletions(-) rename WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/{AutoGenerated/IPetstoreController.cs => Controllers/AutoGenerated/PetStoreController.cs} (86%) diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs index 2357d0b2..49e0b6fb 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs @@ -7,6 +7,17 @@ namespace Lab.RefitClient.TestProject; +public static class HttpClientExtensions +{ + public static void AddHeaders(this HttpRequestMessage requestMessage, Dictionary headers) + { + foreach (var header in headers) + { + requestMessage.Headers.Add(header.Key, header.Value); + } + } +} + [TestClass] public class UnitTest1 { @@ -15,9 +26,11 @@ public async Task 自動化測試() { var server = new PetStoreTestServer(); var httpClient = server.CreateClient(); + httpClient.DefaultRequestHeaders.Add("x-idempotency-key", "1234567890"); httpClient.BaseAddress = new Uri(httpClient.BaseAddress, "api/v3"); var client = RestService.For(httpClient); var username = "yao"; + var response = await client.GetUserByName(username); var content = response.Content; Assert.AreEqual(username, content.Username); diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/AutoGenerated/IPetstoreController.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/AutoGenerated/PetStoreController.cs similarity index 86% rename from WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/AutoGenerated/IPetstoreController.cs rename to WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/AutoGenerated/PetStoreController.cs index 79408615..ed3e732a 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/AutoGenerated/IPetstoreController.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/AutoGenerated/PetStoreController.cs @@ -34,7 +34,7 @@ public interface IPetStoreController /// Successful operation - System.Threading.Tasks.Task UpdatePetAsync(Pet body); + System.Threading.Tasks.Task UpdatePetAsync(Pet body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Add a new pet to the store @@ -48,7 +48,7 @@ public interface IPetStoreController /// Successful operation - System.Threading.Tasks.Task AddPetAsync(Pet body); + System.Threading.Tasks.Task AddPetAsync(Pet body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Finds Pets by status @@ -62,7 +62,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task> FindPetsByStatusAsync(Status status); + System.Threading.Tasks.Task> FindPetsByStatusAsync(Status status, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Finds Pets by tags @@ -76,7 +76,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task> FindPetsByTagsAsync(System.Collections.Generic.IEnumerable tags); + System.Threading.Tasks.Task> FindPetsByTagsAsync(System.Collections.Generic.IEnumerable tags, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Find pet by ID @@ -90,7 +90,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task GetPetByIdAsync(long petId); + System.Threading.Tasks.Task GetPetByIdAsync(long petId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Updates a pet in the store with form data @@ -102,7 +102,7 @@ public interface IPetStoreController /// Status of pet that needs to be updated - System.Threading.Tasks.Task UpdatePetWithFormAsync(long petId, string name, string status); + System.Threading.Tasks.Task UpdatePetWithFormAsync(long petId, string name, string status, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Deletes a pet @@ -111,7 +111,7 @@ public interface IPetStoreController /// Pet id to delete - System.Threading.Tasks.Task DeletePetAsync(string api_key, long petId); + System.Threading.Tasks.Task DeletePetAsync(string api_key, long petId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// uploads an image @@ -124,7 +124,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task UploadFileAsync(long petId, string additionalMetadata, Microsoft.AspNetCore.Http.IFormFile body); + System.Threading.Tasks.Task UploadFileAsync(long petId, string additionalMetadata, Microsoft.AspNetCore.Http.IFormFile body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Returns pet inventories by status @@ -136,7 +136,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task> GetInventoryAsync(); + System.Threading.Tasks.Task> GetInventoryAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Place an order for a pet @@ -148,7 +148,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task PlaceOrderAsync(Order body); + System.Threading.Tasks.Task PlaceOrderAsync(Order body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Find purchase order by ID @@ -162,7 +162,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task GetOrderByIdAsync(long orderId); + System.Threading.Tasks.Task GetOrderByIdAsync(long orderId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Delete purchase order by ID @@ -174,7 +174,7 @@ public interface IPetStoreController /// ID of the order that needs to be deleted - System.Threading.Tasks.Task DeleteOrderAsync(long orderId); + System.Threading.Tasks.Task DeleteOrderAsync(long orderId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Create user @@ -188,7 +188,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task CreateUserAsync(User body); + System.Threading.Tasks.Task CreateUserAsync(User body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Creates list of users with given input array @@ -200,7 +200,7 @@ public interface IPetStoreController /// Successful operation - System.Threading.Tasks.Task CreateUsersWithListInputAsync(System.Collections.Generic.IEnumerable body); + System.Threading.Tasks.Task CreateUsersWithListInputAsync(System.Collections.Generic.IEnumerable body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Logs user into the system @@ -212,7 +212,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task LoginUserAsync(string username, string password); + System.Threading.Tasks.Task LoginUserAsync(string username, string password, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Logs out current logged in user session @@ -220,7 +220,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task LogoutUserAsync(); + System.Threading.Tasks.Task LogoutUserAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Get user by user name @@ -230,7 +230,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task GetUserByNameAsync(string username); + System.Threading.Tasks.Task GetUserByNameAsync(string username, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Update user @@ -246,7 +246,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task UpdateUserAsync(string username, User body); + System.Threading.Tasks.Task UpdateUserAsync(string username, User body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Delete user @@ -258,7 +258,7 @@ public interface IPetStoreController /// The name that needs to be deleted - System.Threading.Tasks.Task DeleteUserAsync(string username); + System.Threading.Tasks.Task DeleteUserAsync(string username, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } @@ -283,10 +283,10 @@ public PetStoreController(IPetStoreController implementation) /// Update an existent pet in the store /// Successful operation [Microsoft.AspNetCore.Mvc.HttpPut, Microsoft.AspNetCore.Mvc.Route("pet")] - public System.Threading.Tasks.Task UpdatePet([Microsoft.AspNetCore.Mvc.FromBody] Pet body) + public System.Threading.Tasks.Task UpdatePet([Microsoft.AspNetCore.Mvc.FromBody] Pet body, System.Threading.CancellationToken cancellationToken) { - return _implementation.UpdatePetAsync(body); + return _implementation.UpdatePetAsync(body, cancellationToken); } /// @@ -298,10 +298,10 @@ public System.Threading.Tasks.Task UpdatePet([Microsoft.AspNetCore.Mvc.From /// Create a new pet in the store /// Successful operation [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("pet")] - public System.Threading.Tasks.Task AddPet([Microsoft.AspNetCore.Mvc.FromBody] Pet body) + public System.Threading.Tasks.Task AddPet([Microsoft.AspNetCore.Mvc.FromBody] Pet body, System.Threading.CancellationToken cancellationToken) { - return _implementation.AddPetAsync(body); + return _implementation.AddPetAsync(body, cancellationToken); } /// @@ -313,10 +313,10 @@ public System.Threading.Tasks.Task AddPet([Microsoft.AspNetCore.Mvc.FromBod /// Status values that need to be considered for filter /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pet/findByStatus")] - public System.Threading.Tasks.Task> FindPetsByStatus([Microsoft.AspNetCore.Mvc.FromQuery] Status? status) + public System.Threading.Tasks.Task> FindPetsByStatus([Microsoft.AspNetCore.Mvc.FromQuery] Status? status, System.Threading.CancellationToken cancellationToken) { - return _implementation.FindPetsByStatusAsync(status ?? Lab.RefitClient.WebAPI.Controllers.Status.Available); + return _implementation.FindPetsByStatusAsync(status ?? Lab.RefitClient.WebAPI.Controllers.Status.Available, cancellationToken); } /// @@ -328,10 +328,10 @@ public System.Threading.Tasks.Task AddPet([Microsoft.AspNetCore.Mvc.FromBod /// Tags to filter by /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pet/findByTags")] - public System.Threading.Tasks.Task> FindPetsByTags([Microsoft.AspNetCore.Mvc.FromQuery] System.Collections.Generic.IEnumerable tags) + public System.Threading.Tasks.Task> FindPetsByTags([Microsoft.AspNetCore.Mvc.FromQuery] System.Collections.Generic.IEnumerable tags, System.Threading.CancellationToken cancellationToken) { - return _implementation.FindPetsByTagsAsync(tags); + return _implementation.FindPetsByTagsAsync(tags, cancellationToken); } /// @@ -343,10 +343,10 @@ public System.Threading.Tasks.Task AddPet([Microsoft.AspNetCore.Mvc.FromBod /// ID of pet to return /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pet/{petId}")] - public System.Threading.Tasks.Task GetPetById(long petId) + public System.Threading.Tasks.Task GetPetById(long petId, System.Threading.CancellationToken cancellationToken) { - return _implementation.GetPetByIdAsync(petId); + return _implementation.GetPetByIdAsync(petId, cancellationToken); } /// @@ -356,10 +356,10 @@ public System.Threading.Tasks.Task GetPetById(long petId) /// Name of pet that needs to be updated /// Status of pet that needs to be updated [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("pet/{petId}")] - public System.Threading.Tasks.Task UpdatePetWithForm(long petId, [Microsoft.AspNetCore.Mvc.FromQuery] string name, [Microsoft.AspNetCore.Mvc.FromQuery] string status) + public System.Threading.Tasks.Task UpdatePetWithForm(long petId, [Microsoft.AspNetCore.Mvc.FromQuery] string name, [Microsoft.AspNetCore.Mvc.FromQuery] string status, System.Threading.CancellationToken cancellationToken) { - return _implementation.UpdatePetWithFormAsync(petId, name, status); + return _implementation.UpdatePetWithFormAsync(petId, name, status, cancellationToken); } /// @@ -367,10 +367,10 @@ public System.Threading.Tasks.Task UpdatePetWithForm(long petId, [Microsoft.AspN /// /// Pet id to delete [Microsoft.AspNetCore.Mvc.HttpDelete, Microsoft.AspNetCore.Mvc.Route("pet/{petId}")] - public System.Threading.Tasks.Task DeletePet([Microsoft.AspNetCore.Mvc.FromHeader] string api_key, long petId) + public System.Threading.Tasks.Task DeletePet([Microsoft.AspNetCore.Mvc.FromHeader] string api_key, long petId, System.Threading.CancellationToken cancellationToken) { - return _implementation.DeletePetAsync(api_key, petId); + return _implementation.DeletePetAsync(api_key, petId, cancellationToken); } /// @@ -380,10 +380,10 @@ public System.Threading.Tasks.Task DeletePet([Microsoft.AspNetCore.Mvc.FromHeade /// Additional Metadata /// successful operation [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("pet/{petId}/uploadImage")] - public System.Threading.Tasks.Task UploadFile(long petId, [Microsoft.AspNetCore.Mvc.FromQuery] string additionalMetadata, Microsoft.AspNetCore.Http.IFormFile body) + public System.Threading.Tasks.Task UploadFile(long petId, [Microsoft.AspNetCore.Mvc.FromQuery] string additionalMetadata, Microsoft.AspNetCore.Http.IFormFile body, System.Threading.CancellationToken cancellationToken) { - return _implementation.UploadFileAsync(petId, additionalMetadata, body); + return _implementation.UploadFileAsync(petId, additionalMetadata, body, cancellationToken); } /// @@ -394,10 +394,10 @@ public System.Threading.Tasks.Task UploadFile(long petId, [Microsof /// /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("store/inventory")] - public System.Threading.Tasks.Task> GetInventory() + public System.Threading.Tasks.Task> GetInventory(System.Threading.CancellationToken cancellationToken) { - return _implementation.GetInventoryAsync(); + return _implementation.GetInventoryAsync(cancellationToken); } /// @@ -408,10 +408,10 @@ public System.Threading.Tasks.Task UploadFile(long petId, [Microsof /// /// successful operation [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("store/order")] - public System.Threading.Tasks.Task PlaceOrder([Microsoft.AspNetCore.Mvc.FromBody] Order body) + public System.Threading.Tasks.Task PlaceOrder([Microsoft.AspNetCore.Mvc.FromBody] Order body, System.Threading.CancellationToken cancellationToken) { - return _implementation.PlaceOrderAsync(body); + return _implementation.PlaceOrderAsync(body, cancellationToken); } /// @@ -423,10 +423,10 @@ public System.Threading.Tasks.Task PlaceOrder([Microsoft.AspNetCore.Mvc.F /// ID of order that needs to be fetched /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("store/order/{orderId}")] - public System.Threading.Tasks.Task GetOrderById(long orderId) + public System.Threading.Tasks.Task GetOrderById(long orderId, System.Threading.CancellationToken cancellationToken) { - return _implementation.GetOrderByIdAsync(orderId); + return _implementation.GetOrderByIdAsync(orderId, cancellationToken); } /// @@ -437,10 +437,10 @@ public System.Threading.Tasks.Task GetOrderById(long orderId) /// /// ID of the order that needs to be deleted [Microsoft.AspNetCore.Mvc.HttpDelete, Microsoft.AspNetCore.Mvc.Route("store/order/{orderId}")] - public System.Threading.Tasks.Task DeleteOrder(long orderId) + public System.Threading.Tasks.Task DeleteOrder(long orderId, System.Threading.CancellationToken cancellationToken) { - return _implementation.DeleteOrderAsync(orderId); + return _implementation.DeleteOrderAsync(orderId, cancellationToken); } /// @@ -452,10 +452,10 @@ public System.Threading.Tasks.Task DeleteOrder(long orderId) /// Created user object /// successful operation [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("user")] - public System.Threading.Tasks.Task CreateUser([Microsoft.AspNetCore.Mvc.FromBody] User body) + public System.Threading.Tasks.Task CreateUser([Microsoft.AspNetCore.Mvc.FromBody] User body, System.Threading.CancellationToken cancellationToken) { - return _implementation.CreateUserAsync(body); + return _implementation.CreateUserAsync(body, cancellationToken); } /// @@ -466,10 +466,10 @@ public System.Threading.Tasks.Task CreateUser([Microsoft.AspNetCore.Mvc.Fr /// /// Successful operation [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("user/createWithList")] - public System.Threading.Tasks.Task CreateUsersWithListInput([Microsoft.AspNetCore.Mvc.FromBody] System.Collections.Generic.IEnumerable body) + public System.Threading.Tasks.Task CreateUsersWithListInput([Microsoft.AspNetCore.Mvc.FromBody] System.Collections.Generic.IEnumerable body, System.Threading.CancellationToken cancellationToken) { - return _implementation.CreateUsersWithListInputAsync(body); + return _implementation.CreateUsersWithListInputAsync(body, cancellationToken); } /// @@ -479,10 +479,10 @@ public System.Threading.Tasks.Task CreateUsersWithListInput([Microsoft.Asp /// The password for login in clear text /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("user/login")] - public System.Threading.Tasks.Task LoginUser([Microsoft.AspNetCore.Mvc.FromQuery] string username, [Microsoft.AspNetCore.Mvc.FromQuery] string password) + public System.Threading.Tasks.Task LoginUser([Microsoft.AspNetCore.Mvc.FromQuery] string username, [Microsoft.AspNetCore.Mvc.FromQuery] string password, System.Threading.CancellationToken cancellationToken) { - return _implementation.LoginUserAsync(username, password); + return _implementation.LoginUserAsync(username, password, cancellationToken); } /// @@ -490,10 +490,10 @@ public System.Threading.Tasks.Task LoginUser([Microsoft.AspNetCore.Mvc.F /// /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("user/logout")] - public System.Threading.Tasks.Task LogoutUser() + public System.Threading.Tasks.Task LogoutUser(System.Threading.CancellationToken cancellationToken) { - return _implementation.LogoutUserAsync(); + return _implementation.LogoutUserAsync(cancellationToken); } /// @@ -502,10 +502,10 @@ public System.Threading.Tasks.Task LogoutUser() /// The name that needs to be fetched. Use user1 for testing. /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("user/{username}")] - public System.Threading.Tasks.Task GetUserByName(string username) + public System.Threading.Tasks.Task GetUserByName(string username, System.Threading.CancellationToken cancellationToken) { - return _implementation.GetUserByNameAsync(username); + return _implementation.GetUserByNameAsync(username, cancellationToken); } /// @@ -518,10 +518,10 @@ public System.Threading.Tasks.Task GetUserByName(string username) /// Update an existent user in the store /// successful operation [Microsoft.AspNetCore.Mvc.HttpPut, Microsoft.AspNetCore.Mvc.Route("user/{username}")] - public System.Threading.Tasks.Task UpdateUser(string username, [Microsoft.AspNetCore.Mvc.FromBody] User body) + public System.Threading.Tasks.Task UpdateUser(string username, [Microsoft.AspNetCore.Mvc.FromBody] User body, System.Threading.CancellationToken cancellationToken) { - return _implementation.UpdateUserAsync(username, body); + return _implementation.UpdateUserAsync(username, body, cancellationToken); } /// @@ -532,10 +532,10 @@ public System.Threading.Tasks.Task UpdateUser(string username, [Microsoft.AspNet /// /// The name that needs to be deleted [Microsoft.AspNetCore.Mvc.HttpDelete, Microsoft.AspNetCore.Mvc.Route("user/{username}")] - public System.Threading.Tasks.Task DeleteUser(string username) + public System.Threading.Tasks.Task DeleteUser(string username, System.Threading.CancellationToken cancellationToken) { - return _implementation.DeleteUserAsync(username); + return _implementation.DeleteUserAsync(username, cancellationToken); } } diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs index 447dacf5..b0b2c731 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs @@ -2,88 +2,111 @@ public class PetStoreControllerImpl : IPetStoreController { - public Task UpdatePetAsync(Pet body) + private IHttpContextAccessor _httpContextAccessor; + + private HttpContext _httpContent; + + public PetStoreControllerImpl(IHttpContextAccessor httpContextAccessor) + { + this._httpContextAccessor = httpContextAccessor; + } + + // public PetStoreControllerImpl(HttpContextAccessor httpContextAccessor) + // { + // this._httpContextAccessor = httpContextAccessor; + // } + + public Task UpdatePetAsync(Pet body, CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotImplementedException(); + } + + public Task AddPetAsync(Pet body, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task AddPetAsync(Pet body) + public Task> FindPetsByStatusAsync(Status status, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task> FindPetsByStatusAsync(Status status) + public Task> FindPetsByTagsAsync(IEnumerable tags, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task> FindPetsByTagsAsync(IEnumerable tags) + public Task GetPetByIdAsync(long petId, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task GetPetByIdAsync(long petId) + public Task UpdatePetWithFormAsync(long petId, string name, string status, + CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task UpdatePetWithFormAsync(long petId, string name, string status) + public Task DeletePetAsync(string api_key, long petId, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task DeletePetAsync(string api_key, long petId) + public Task UploadFileAsync(long petId, string additionalMetadata, IFormFile body, + CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task UploadFileAsync(long petId, string additionalMetadata, IFormFile body) + public Task> GetInventoryAsync(CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task> GetInventoryAsync() + public Task PlaceOrderAsync(Order body, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task PlaceOrderAsync(Order body) + public Task GetOrderByIdAsync(long orderId, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task GetOrderByIdAsync(long orderId) + public Task DeleteOrderAsync(long orderId, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task DeleteOrderAsync(long orderId) + public Task CreateUserAsync(User body, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task CreateUserAsync(User body) + public Task CreateUsersWithListInputAsync(IEnumerable body, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task CreateUsersWithListInputAsync(IEnumerable body) + public Task LoginUserAsync(string username, string password, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task LoginUserAsync(string username, string password) + public Task LogoutUserAsync(CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task LogoutUserAsync() + public Task GetUserByNameAsync(string username, string x_api_key, + CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public async Task GetUserByNameAsync(string username) + public async Task GetUserByNameAsync(string username, CancellationToken cancellationToken = default(CancellationToken)) { + var headers = this._httpContextAccessor.HttpContext.Request.Headers; return new User { Id = 0, @@ -98,12 +121,12 @@ public async Task GetUserByNameAsync(string username) }; } - public Task UpdateUserAsync(string username, User body) + public Task UpdateUserAsync(string username, User body, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task DeleteUserAsync(string username) + public Task DeleteUserAsync(string username, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Lab.RefitClient.WebAPI.csproj b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Lab.RefitClient.WebAPI.csproj index 0894f53c..f1706541 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Lab.RefitClient.WebAPI.csproj +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Lab.RefitClient.WebAPI.csproj @@ -12,4 +12,9 @@ + + + + + diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Program.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Program.cs index 77a81721..34386fe6 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Program.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Program.cs @@ -9,6 +9,8 @@ // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +builder.Services.AddHttpContextAccessor(); + builder.Services.AddScoped(); var app = builder.Build(); diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreClient.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreClient.cs index d5208302..2aebcdad 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreClient.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreClient.cs @@ -1,7 +1,3 @@ -// -// This code was generated by Refitter. -// - using Refit; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -51,7 +47,7 @@ public interface ISwaggerPetstoreOpenAPI30 Task UpdatePetWithForm(long petId, [Query] string name, [Query] string status); [Delete("/pet/{petId}")] - Task DeletePet(long petId, [Header("api_key")] string api_key); + Task DeletePet(long petId); [Headers("Accept: application/json")] [Post("/pet/{petId}/uploadImage")] diff --git a/WebAPI/Swagger/Lab.RefitClient/Taskfile.yml b/WebAPI/Swagger/Lab.RefitClient/Taskfile.yml index cafd319f..7833cfe6 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Taskfile.yml +++ b/WebAPI/Swagger/Lab.RefitClient/Taskfile.yml @@ -3,11 +3,16 @@ version: "3" tasks: + codegen: + desc: codegen client and server + cmds: + - task: codegen-client + - task: codegen-server codegen-client: desc: codegen client cmds: - - refitter ./openapi.json --namespace "Lab.RefitClient.GeneratedCode.PetStore" --output ./Lab.RefitClient/PetStoreClient.cs --use-api-response + - refitter ./openapi.json --namespace "Lab.RefitClient.GeneratedCode.PetStore" --output ./Lab.RefitClient/PetStoreClient.cs --use-api-response --no-operation-headers --no-auto-generated-header codegen-server: desc: codegen server cmds: - - nswag openapi2cscontroller /input:./openapi.json /classname:PetStore /namespace:Lab.RefitClient.WebAPI.Controllers /output:Lab.RefitClient.WebAPI/AutoGenerated/IPetStoreController.cs /jsonLibrary:SystemTextJson + - nswag openapi2cscontroller /input:./openapi.json /classname:PetStore /namespace:Lab.RefitClient.WebAPI.Controllers /output:Lab.RefitClient.WebAPI/Controllers/AutoGenerated/PetStoreController.cs /jsonLibrary:SystemTextJson /useCancellationToken:true /excludedParameterNames:x-idempotency-key,x-api-key diff --git a/WebAPI/Swagger/Lab.RefitClient/openapi.json b/WebAPI/Swagger/Lab.RefitClient/openapi.json index 12881192..80ffe3e2 100644 --- a/WebAPI/Swagger/Lab.RefitClient/openapi.json +++ b/WebAPI/Swagger/Lab.RefitClient/openapi.json @@ -832,6 +832,24 @@ "schema": { "type": "string" } + }, + { + "name": "x-idempotency-key", + "in": "header", + "description": "idempotency key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "x-api-key", + "in": "header", + "description": "api key", + "required": true, + "schema": { + "type": "string" + } } ], "responses": { From 76c06c05fb8212afab263f1e1e9fc28b260e26ea Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 14 Sep 2023 08:28:58 +0800 Subject: [PATCH 330/424] refactor --- .../Lab.RefitClient.TestProject/UnitTest1.cs | 12 ++- .../AutoGenerated/PetStoreController.cs | 76 +++++++++---------- .../Controllers/PetstoreControllerImpl.cs | 63 +++++++-------- .../Lab.RefitClient.WebAPI.csproj | 4 + .../Lab.RefitClient/PetStoreClient.cs | 4 + .../Lab.RefitClient/PetStoreHeaderNames.cs | 7 ++ WebAPI/Swagger/Lab.RefitClient/Taskfile.yml | 5 +- 7 files changed, 95 insertions(+), 76 deletions(-) create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreHeaderNames.cs diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs index 49e0b6fb..f0ebfe04 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs @@ -1,3 +1,5 @@ +using System.Text.Encodings.Web; +using System.Text.Json; using Lab.RefitClient.GeneratedCode.PetStore; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; @@ -26,7 +28,8 @@ public async Task 自動化測試() { var server = new PetStoreTestServer(); var httpClient = server.CreateClient(); - httpClient.DefaultRequestHeaders.Add("x-idempotency-key", "1234567890"); + httpClient.DefaultRequestHeaders.Add(PetStoreHeaderNames.IdempotencyKey, "1234567890"); + httpClient.DefaultRequestHeaders.Add(PetStoreHeaderNames.ApiKey, "1234567890"); httpClient.BaseAddress = new Uri(httpClient.BaseAddress, "api/v3"); var client = RestService.For(httpClient); var username = "yao"; @@ -42,7 +45,12 @@ public async Task 手動建立API() var baseUrl = "https://localhost:7285/api/v3"; var services = new ServiceCollection(); services.AddRefitClient() - .ConfigureHttpClient(c => { c.BaseAddress = new Uri(baseUrl); }); + .ConfigureHttpClient(p => + { + p.DefaultRequestHeaders.Add(PetStoreHeaderNames.IdempotencyKey, "1234567890"); + p.DefaultRequestHeaders.Add(PetStoreHeaderNames.ApiKey, "1234567890"); + p.BaseAddress = new Uri(baseUrl); + }); var serviceProvider = services.BuildServiceProvider(); var client = serviceProvider.GetService(); var username = "yao"; diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/AutoGenerated/PetStoreController.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/AutoGenerated/PetStoreController.cs index ed3e732a..918daafc 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/AutoGenerated/PetStoreController.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/AutoGenerated/PetStoreController.cs @@ -34,7 +34,7 @@ public interface IPetStoreController /// Successful operation - System.Threading.Tasks.Task UpdatePetAsync(Pet body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task> UpdatePetAsync(Pet body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Add a new pet to the store @@ -48,7 +48,7 @@ public interface IPetStoreController /// Successful operation - System.Threading.Tasks.Task AddPetAsync(Pet body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task> AddPetAsync(Pet body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Finds Pets by status @@ -62,7 +62,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task> FindPetsByStatusAsync(Status status, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task>> FindPetsByStatusAsync(Status status, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Finds Pets by tags @@ -76,7 +76,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task> FindPetsByTagsAsync(System.Collections.Generic.IEnumerable tags, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task>> FindPetsByTagsAsync(System.Collections.Generic.IEnumerable tags, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Find pet by ID @@ -90,7 +90,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task GetPetByIdAsync(long petId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task> GetPetByIdAsync(long petId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Updates a pet in the store with form data @@ -102,7 +102,7 @@ public interface IPetStoreController /// Status of pet that needs to be updated - System.Threading.Tasks.Task UpdatePetWithFormAsync(long petId, string name, string status, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task UpdatePetWithFormAsync(long petId, string name, string status, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Deletes a pet @@ -111,7 +111,7 @@ public interface IPetStoreController /// Pet id to delete - System.Threading.Tasks.Task DeletePetAsync(string api_key, long petId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task DeletePetAsync(string api_key, long petId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// uploads an image @@ -124,7 +124,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task UploadFileAsync(long petId, string additionalMetadata, Microsoft.AspNetCore.Http.IFormFile body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task> UploadFileAsync(long petId, string additionalMetadata, Microsoft.AspNetCore.Http.IFormFile body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Returns pet inventories by status @@ -136,7 +136,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task> GetInventoryAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task>> GetInventoryAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Place an order for a pet @@ -148,7 +148,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task PlaceOrderAsync(Order body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task> PlaceOrderAsync(Order body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Find purchase order by ID @@ -162,7 +162,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task GetOrderByIdAsync(long orderId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task> GetOrderByIdAsync(long orderId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Delete purchase order by ID @@ -174,7 +174,7 @@ public interface IPetStoreController /// ID of the order that needs to be deleted - System.Threading.Tasks.Task DeleteOrderAsync(long orderId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task DeleteOrderAsync(long orderId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Create user @@ -188,7 +188,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task CreateUserAsync(User body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task> CreateUserAsync(User body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Creates list of users with given input array @@ -200,7 +200,7 @@ public interface IPetStoreController /// Successful operation - System.Threading.Tasks.Task CreateUsersWithListInputAsync(System.Collections.Generic.IEnumerable body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task> CreateUsersWithListInputAsync(System.Collections.Generic.IEnumerable body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Logs user into the system @@ -212,7 +212,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task LoginUserAsync(string username, string password, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task> LoginUserAsync(string username, string password, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Logs out current logged in user session @@ -220,7 +220,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task LogoutUserAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task LogoutUserAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Get user by user name @@ -230,7 +230,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task GetUserByNameAsync(string username, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task> GetUserByNameAsync(string username, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Update user @@ -246,7 +246,7 @@ public interface IPetStoreController /// successful operation - System.Threading.Tasks.Task UpdateUserAsync(string username, User body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task UpdateUserAsync(string username, User body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); /// /// Delete user @@ -258,7 +258,7 @@ public interface IPetStoreController /// The name that needs to be deleted - System.Threading.Tasks.Task DeleteUserAsync(string username, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task DeleteUserAsync(string username, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); } @@ -283,7 +283,7 @@ public PetStoreController(IPetStoreController implementation) /// Update an existent pet in the store /// Successful operation [Microsoft.AspNetCore.Mvc.HttpPut, Microsoft.AspNetCore.Mvc.Route("pet")] - public System.Threading.Tasks.Task UpdatePet([Microsoft.AspNetCore.Mvc.FromBody] Pet body, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task> UpdatePet([Microsoft.AspNetCore.Mvc.FromBody] Pet body, System.Threading.CancellationToken cancellationToken) { return _implementation.UpdatePetAsync(body, cancellationToken); @@ -298,7 +298,7 @@ public System.Threading.Tasks.Task UpdatePet([Microsoft.AspNetCore.Mvc.From /// Create a new pet in the store /// Successful operation [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("pet")] - public System.Threading.Tasks.Task AddPet([Microsoft.AspNetCore.Mvc.FromBody] Pet body, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task> AddPet([Microsoft.AspNetCore.Mvc.FromBody] Pet body, System.Threading.CancellationToken cancellationToken) { return _implementation.AddPetAsync(body, cancellationToken); @@ -313,7 +313,7 @@ public System.Threading.Tasks.Task AddPet([Microsoft.AspNetCore.Mvc.FromBod /// Status values that need to be considered for filter /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pet/findByStatus")] - public System.Threading.Tasks.Task> FindPetsByStatus([Microsoft.AspNetCore.Mvc.FromQuery] Status? status, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task>> FindPetsByStatus([Microsoft.AspNetCore.Mvc.FromQuery] Status? status, System.Threading.CancellationToken cancellationToken) { return _implementation.FindPetsByStatusAsync(status ?? Lab.RefitClient.WebAPI.Controllers.Status.Available, cancellationToken); @@ -328,7 +328,7 @@ public System.Threading.Tasks.Task AddPet([Microsoft.AspNetCore.Mvc.FromBod /// Tags to filter by /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pet/findByTags")] - public System.Threading.Tasks.Task> FindPetsByTags([Microsoft.AspNetCore.Mvc.FromQuery] System.Collections.Generic.IEnumerable tags, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task>> FindPetsByTags([Microsoft.AspNetCore.Mvc.FromQuery] System.Collections.Generic.IEnumerable tags, System.Threading.CancellationToken cancellationToken) { return _implementation.FindPetsByTagsAsync(tags, cancellationToken); @@ -343,7 +343,7 @@ public System.Threading.Tasks.Task AddPet([Microsoft.AspNetCore.Mvc.FromBod /// ID of pet to return /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("pet/{petId}")] - public System.Threading.Tasks.Task GetPetById(long petId, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task> GetPetById(long petId, System.Threading.CancellationToken cancellationToken) { return _implementation.GetPetByIdAsync(petId, cancellationToken); @@ -356,7 +356,7 @@ public System.Threading.Tasks.Task GetPetById(long petId, System.Threading. /// Name of pet that needs to be updated /// Status of pet that needs to be updated [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("pet/{petId}")] - public System.Threading.Tasks.Task UpdatePetWithForm(long petId, [Microsoft.AspNetCore.Mvc.FromQuery] string name, [Microsoft.AspNetCore.Mvc.FromQuery] string status, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task UpdatePetWithForm(long petId, [Microsoft.AspNetCore.Mvc.FromQuery] string name, [Microsoft.AspNetCore.Mvc.FromQuery] string status, System.Threading.CancellationToken cancellationToken) { return _implementation.UpdatePetWithFormAsync(petId, name, status, cancellationToken); @@ -367,7 +367,7 @@ public System.Threading.Tasks.Task UpdatePetWithForm(long petId, [Microsoft.AspN /// /// Pet id to delete [Microsoft.AspNetCore.Mvc.HttpDelete, Microsoft.AspNetCore.Mvc.Route("pet/{petId}")] - public System.Threading.Tasks.Task DeletePet([Microsoft.AspNetCore.Mvc.FromHeader] string api_key, long petId, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task DeletePet([Microsoft.AspNetCore.Mvc.FromHeader] string api_key, long petId, System.Threading.CancellationToken cancellationToken) { return _implementation.DeletePetAsync(api_key, petId, cancellationToken); @@ -380,7 +380,7 @@ public System.Threading.Tasks.Task DeletePet([Microsoft.AspNetCore.Mvc.FromHeade /// Additional Metadata /// successful operation [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("pet/{petId}/uploadImage")] - public System.Threading.Tasks.Task UploadFile(long petId, [Microsoft.AspNetCore.Mvc.FromQuery] string additionalMetadata, Microsoft.AspNetCore.Http.IFormFile body, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task> UploadFile(long petId, [Microsoft.AspNetCore.Mvc.FromQuery] string additionalMetadata, Microsoft.AspNetCore.Http.IFormFile body, System.Threading.CancellationToken cancellationToken) { return _implementation.UploadFileAsync(petId, additionalMetadata, body, cancellationToken); @@ -394,7 +394,7 @@ public System.Threading.Tasks.Task UploadFile(long petId, [Microsof /// /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("store/inventory")] - public System.Threading.Tasks.Task> GetInventory(System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task>> GetInventory(System.Threading.CancellationToken cancellationToken) { return _implementation.GetInventoryAsync(cancellationToken); @@ -408,7 +408,7 @@ public System.Threading.Tasks.Task UploadFile(long petId, [Microsof /// /// successful operation [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("store/order")] - public System.Threading.Tasks.Task PlaceOrder([Microsoft.AspNetCore.Mvc.FromBody] Order body, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task> PlaceOrder([Microsoft.AspNetCore.Mvc.FromBody] Order body, System.Threading.CancellationToken cancellationToken) { return _implementation.PlaceOrderAsync(body, cancellationToken); @@ -423,7 +423,7 @@ public System.Threading.Tasks.Task PlaceOrder([Microsoft.AspNetCore.Mvc.F /// ID of order that needs to be fetched /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("store/order/{orderId}")] - public System.Threading.Tasks.Task GetOrderById(long orderId, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task> GetOrderById(long orderId, System.Threading.CancellationToken cancellationToken) { return _implementation.GetOrderByIdAsync(orderId, cancellationToken); @@ -437,7 +437,7 @@ public System.Threading.Tasks.Task GetOrderById(long orderId, System.Thre /// /// ID of the order that needs to be deleted [Microsoft.AspNetCore.Mvc.HttpDelete, Microsoft.AspNetCore.Mvc.Route("store/order/{orderId}")] - public System.Threading.Tasks.Task DeleteOrder(long orderId, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task DeleteOrder(long orderId, System.Threading.CancellationToken cancellationToken) { return _implementation.DeleteOrderAsync(orderId, cancellationToken); @@ -452,7 +452,7 @@ public System.Threading.Tasks.Task DeleteOrder(long orderId, System.Threading.Ca /// Created user object /// successful operation [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("user")] - public System.Threading.Tasks.Task CreateUser([Microsoft.AspNetCore.Mvc.FromBody] User body, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task> CreateUser([Microsoft.AspNetCore.Mvc.FromBody] User body, System.Threading.CancellationToken cancellationToken) { return _implementation.CreateUserAsync(body, cancellationToken); @@ -466,7 +466,7 @@ public System.Threading.Tasks.Task CreateUser([Microsoft.AspNetCore.Mvc.Fr /// /// Successful operation [Microsoft.AspNetCore.Mvc.HttpPost, Microsoft.AspNetCore.Mvc.Route("user/createWithList")] - public System.Threading.Tasks.Task CreateUsersWithListInput([Microsoft.AspNetCore.Mvc.FromBody] System.Collections.Generic.IEnumerable body, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task> CreateUsersWithListInput([Microsoft.AspNetCore.Mvc.FromBody] System.Collections.Generic.IEnumerable body, System.Threading.CancellationToken cancellationToken) { return _implementation.CreateUsersWithListInputAsync(body, cancellationToken); @@ -479,7 +479,7 @@ public System.Threading.Tasks.Task CreateUsersWithListInput([Microsoft.Asp /// The password for login in clear text /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("user/login")] - public System.Threading.Tasks.Task LoginUser([Microsoft.AspNetCore.Mvc.FromQuery] string username, [Microsoft.AspNetCore.Mvc.FromQuery] string password, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task> LoginUser([Microsoft.AspNetCore.Mvc.FromQuery] string username, [Microsoft.AspNetCore.Mvc.FromQuery] string password, System.Threading.CancellationToken cancellationToken) { return _implementation.LoginUserAsync(username, password, cancellationToken); @@ -490,7 +490,7 @@ public System.Threading.Tasks.Task LoginUser([Microsoft.AspNetCore.Mvc.F /// /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("user/logout")] - public System.Threading.Tasks.Task LogoutUser(System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task LogoutUser(System.Threading.CancellationToken cancellationToken) { return _implementation.LogoutUserAsync(cancellationToken); @@ -502,7 +502,7 @@ public System.Threading.Tasks.Task LogoutUser(System.Threading.CancellationToken /// The name that needs to be fetched. Use user1 for testing. /// successful operation [Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("user/{username}")] - public System.Threading.Tasks.Task GetUserByName(string username, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task> GetUserByName(string username, System.Threading.CancellationToken cancellationToken) { return _implementation.GetUserByNameAsync(username, cancellationToken); @@ -518,7 +518,7 @@ public System.Threading.Tasks.Task GetUserByName(string username, System.T /// Update an existent user in the store /// successful operation [Microsoft.AspNetCore.Mvc.HttpPut, Microsoft.AspNetCore.Mvc.Route("user/{username}")] - public System.Threading.Tasks.Task UpdateUser(string username, [Microsoft.AspNetCore.Mvc.FromBody] User body, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task UpdateUser(string username, [Microsoft.AspNetCore.Mvc.FromBody] User body, System.Threading.CancellationToken cancellationToken) { return _implementation.UpdateUserAsync(username, body, cancellationToken); @@ -532,7 +532,7 @@ public System.Threading.Tasks.Task UpdateUser(string username, [Microsoft.AspNet /// /// The name that needs to be deleted [Microsoft.AspNetCore.Mvc.HttpDelete, Microsoft.AspNetCore.Mvc.Route("user/{username}")] - public System.Threading.Tasks.Task DeleteUser(string username, System.Threading.CancellationToken cancellationToken) + public System.Threading.Tasks.Task DeleteUser(string username, System.Threading.CancellationToken cancellationToken) { return _implementation.DeleteUserAsync(username, cancellationToken); diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs index b0b2c731..a3131085 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs @@ -1,112 +1,107 @@ -namespace Lab.RefitClient.WebAPI.Controllers; +using Microsoft.AspNetCore.Mvc; + +namespace Lab.RefitClient.WebAPI.Controllers; public class PetStoreControllerImpl : IPetStoreController { - private IHttpContextAccessor _httpContextAccessor; - - private HttpContext _httpContent; + private readonly IHttpContextAccessor _httpContextAccessor; public PetStoreControllerImpl(IHttpContextAccessor httpContextAccessor) { this._httpContextAccessor = httpContextAccessor; } - // public PetStoreControllerImpl(HttpContextAccessor httpContextAccessor) - // { - // this._httpContextAccessor = httpContextAccessor; - // } - - public Task UpdatePetAsync(Pet body, CancellationToken cancellationToken = default(CancellationToken)) + public Task> UpdatePetAsync(Pet body, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task AddPetAsync(Pet body, CancellationToken cancellationToken = default(CancellationToken)) + public Task> AddPetAsync(Pet body, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task> FindPetsByStatusAsync(Status status, CancellationToken cancellationToken = default(CancellationToken)) + public Task>> FindPetsByStatusAsync(Status status, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task> FindPetsByTagsAsync(IEnumerable tags, CancellationToken cancellationToken = default(CancellationToken)) + public Task>> FindPetsByTagsAsync(IEnumerable tags, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task GetPetByIdAsync(long petId, CancellationToken cancellationToken = default(CancellationToken)) + public Task> GetPetByIdAsync(long petId, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task UpdatePetWithFormAsync(long petId, string name, string status, + public Task UpdatePetWithFormAsync(long petId, string name, string status, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task DeletePetAsync(string api_key, long petId, CancellationToken cancellationToken = default(CancellationToken)) + public Task DeletePetAsync(string api_key, long petId, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task UploadFileAsync(long petId, string additionalMetadata, IFormFile body, + public Task> UploadFileAsync(long petId, string additionalMetadata, IFormFile body, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task> GetInventoryAsync(CancellationToken cancellationToken = default(CancellationToken)) - { - throw new NotImplementedException(); - } - - public Task PlaceOrderAsync(Order body, CancellationToken cancellationToken = default(CancellationToken)) + public Task>> GetInventoryAsync(CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task GetOrderByIdAsync(long orderId, CancellationToken cancellationToken = default(CancellationToken)) + public Task> PlaceOrderAsync(Order body, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task DeleteOrderAsync(long orderId, CancellationToken cancellationToken = default(CancellationToken)) + public Task> GetOrderByIdAsync(long orderId, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task CreateUserAsync(User body, CancellationToken cancellationToken = default(CancellationToken)) + public Task DeleteOrderAsync(long orderId, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task CreateUsersWithListInputAsync(IEnumerable body, CancellationToken cancellationToken = default(CancellationToken)) + public Task> CreateUserAsync(User body, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task LoginUserAsync(string username, string password, CancellationToken cancellationToken = default(CancellationToken)) + public Task> CreateUsersWithListInputAsync(IEnumerable body, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task LogoutUserAsync(CancellationToken cancellationToken = default(CancellationToken)) + public Task> LoginUserAsync(string username, string password, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task GetUserByNameAsync(string username, string x_api_key, - CancellationToken cancellationToken = default(CancellationToken)) + public Task LogoutUserAsync(CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public async Task GetUserByNameAsync(string username, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetUserByNameAsync(string username, CancellationToken cancellationToken = default(CancellationToken)) { var headers = this._httpContextAccessor.HttpContext.Request.Headers; + var idempotencyKey = headers[PetStoreHeaderNames.IdempotencyKey]; + var apiKey = headers[PetStoreHeaderNames.ApiKey]; + + var response = this._httpContextAccessor.HttpContext.Response; + response.Headers.Add(PetStoreHeaderNames.IdempotencyKey,idempotencyKey); + response.Headers.Add(PetStoreHeaderNames.ApiKey,apiKey); return new User { Id = 0, @@ -121,12 +116,12 @@ public Task GetUserByNameAsync(string username, string x_api_key, }; } - public Task UpdateUserAsync(string username, User body, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateUserAsync(string username, User body, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } - public Task DeleteUserAsync(string username, CancellationToken cancellationToken = default(CancellationToken)) + public Task DeleteUserAsync(string username, CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Lab.RefitClient.WebAPI.csproj b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Lab.RefitClient.WebAPI.csproj index f1706541..0209724e 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Lab.RefitClient.WebAPI.csproj +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Lab.RefitClient.WebAPI.csproj @@ -17,4 +17,8 @@ + + + + diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreClient.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreClient.cs index 2aebcdad..1efb7c1f 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreClient.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreClient.cs @@ -1,3 +1,7 @@ +// +// This code was generated by Refitter. +// + using Refit; using System.Collections.Generic; using System.Text.Json.Serialization; diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreHeaderNames.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreHeaderNames.cs new file mode 100644 index 00000000..4f44edba --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/PetStoreHeaderNames.cs @@ -0,0 +1,7 @@ +namespace Lab.RefitClient; + +public class PetStoreHeaderNames +{ + public const string IdempotencyKey = "x-idempotency-key"; + public const string ApiKey = "x-api-key"; +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Taskfile.yml b/WebAPI/Swagger/Lab.RefitClient/Taskfile.yml index 7833cfe6..b4927c07 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Taskfile.yml +++ b/WebAPI/Swagger/Lab.RefitClient/Taskfile.yml @@ -11,8 +11,9 @@ tasks: codegen-client: desc: codegen client cmds: - - refitter ./openapi.json --namespace "Lab.RefitClient.GeneratedCode.PetStore" --output ./Lab.RefitClient/PetStoreClient.cs --use-api-response --no-operation-headers --no-auto-generated-header +# - refitter ./openapi.json --namespace "Lab.RefitClient.GeneratedCode.PetStore" --output ./Lab.RefitClient/PetStoreClient.cs --use-api-response --no-operation-headers + - refitter ./openapi.json --namespace "Lab.RefitClient.GeneratedCode.PetStore" --output ./Lab.RefitClient/PetStoreClient.cs --use-api-response --no-operation-headers --no-auto-generated-header codegen-server: desc: codegen server cmds: - - nswag openapi2cscontroller /input:./openapi.json /classname:PetStore /namespace:Lab.RefitClient.WebAPI.Controllers /output:Lab.RefitClient.WebAPI/Controllers/AutoGenerated/PetStoreController.cs /jsonLibrary:SystemTextJson /useCancellationToken:true /excludedParameterNames:x-idempotency-key,x-api-key + - nswag openapi2cscontroller /input:./openapi.json /classname:PetStore /namespace:Lab.RefitClient.WebAPI.Controllers /output:Lab.RefitClient.WebAPI/Controllers/AutoGenerated/PetStoreController.cs /jsonLibrary:SystemTextJson /useCancellationToken:true /useActionResultType:true /excludedParameterNames:x-idempotency-key,x-api-key From f42d50e12921a9677da6b00e6fe0d674de031592 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 14 Sep 2023 17:41:06 +0800 Subject: [PATCH 331/424] refactor --- .../PetStoreTestServer.cs | 3 +- .../Lab.RefitClient.TestProject/UnitTest1.cs | 23 ++---- .../Lab.RefitClient.TestProject/UnitTest2.cs | 74 +++++++++++++++++++ .../Controllers/PetstoreControllerImpl.cs | 16 ++-- .../Lab.RefitClient.WebAPI/Program.cs | 17 ++++- .../ResolverHeaderContextFilterAttribute.cs | 20 +++++ .../Lab.RefitClient/ContextAccessor.cs | 22 ++++++ .../Lab.RefitClient/DefaultHeaderHandler.cs | 21 ++++++ .../Lab.RefitClient/HeaderContext.cs | 8 ++ .../Lab.RefitClient/IContextGetter.cs | 7 ++ .../Lab.RefitClient/IContextSetter.cs | 7 ++ .../Lab.RefitClient/Workflow.cs | 19 +++++ 12 files changed, 209 insertions(+), 28 deletions(-) create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest2.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/ResolverHeaderContextFilterAttribute.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/ContextAccessor.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/DefaultHeaderHandler.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/HeaderContext.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/IContextGetter.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/IContextSetter.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/Workflow.cs diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/PetStoreTestServer.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/PetStoreTestServer.cs index 0326ad69..4fa7f315 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/PetStoreTestServer.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/PetStoreTestServer.cs @@ -1,4 +1,5 @@ -using Lab.RefitClient.WebAPI.Controllers; +using Lab.RefitClient.WebAPI; +using Lab.RefitClient.WebAPI.Controllers; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs index f0ebfe04..e73fa23b 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest1.cs @@ -1,30 +1,14 @@ -using System.Text.Encodings.Web; -using System.Text.Json; using Lab.RefitClient.GeneratedCode.PetStore; -using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.FileProviders; using Refit; namespace Lab.RefitClient.TestProject; -public static class HttpClientExtensions -{ - public static void AddHeaders(this HttpRequestMessage requestMessage, Dictionary headers) - { - foreach (var header in headers) - { - requestMessage.Headers.Add(header.Key, header.Value); - } - } -} - [TestClass] public class UnitTest1 { [TestMethod] - public async Task 自動化測試() + public async Task RestServiceFor() { var server = new PetStoreTestServer(); var httpClient = server.CreateClient(); @@ -39,8 +23,11 @@ public async Task 自動化測試() Assert.AreEqual(username, content.Username); } + /// + /// F5執行WebApi專案_再呼叫WebApi + /// [TestMethod] - public async Task 手動建立API() + public async Task AddRefitClient() { var baseUrl = "https://localhost:7285/api/v3"; var services = new ServiceCollection(); diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest2.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest2.cs new file mode 100644 index 00000000..4fda8e7f --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest2.cs @@ -0,0 +1,74 @@ +using System.Text.Encodings.Web; +using System.Text.Json; +using Lab.RefitClient.GeneratedCode.PetStore; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.FileProviders; +using Microsoft.Net.Http.Headers; +using Refit; + +namespace Lab.RefitClient.TestProject; + +[TestClass] +public class UnitTest2 +{ + [TestMethod] + public async Task RestServiceFor() + { + var server = new PetStoreTestServer(); + var httpClient = server.CreateClient(); + httpClient.BaseAddress = new Uri(httpClient.BaseAddress, "api/v3"); + + var settings = new RefitSettings + { + HttpMessageHandlerFactory = () => + { + var contextAccessor = new ContextAccessor(); + contextAccessor.Set(new HeaderContext + { + IdempotencyKey = "1234567890", + ApiKey = "1234567890" + }); + return new DefaultHeaderHandler(contextAccessor); + }, + }; + var client = RestService.For(httpClient, settings); + + var username = "yao"; + var response = await client.GetUserByName(username); + var content = response.Content; + Assert.AreEqual(username, content.Username); + } + + [TestMethod] + public async Task AddRefitClient() + { + var baseUrl = "https://localhost:7285/api/v3"; + + var services = new ServiceCollection(); + services.AddSingleton>(); + services.AddSingleton>(p => p.GetService>()); + services.AddSingleton>(p => p.GetService>()); + + services.AddRefitClient() + .ConfigureHttpClient(p => { p.BaseAddress = new Uri(baseUrl); }) + .AddHttpMessageHandler(p => + { + var contextSetter = p.GetService>(); + contextSetter.Set(new HeaderContext + { + IdempotencyKey = "1234567890", + ApiKey = "1234567890" + }); + return new DefaultHeaderHandler(p.GetService>()); + }); + + var serviceProvider = services.BuildServiceProvider(); + var client = serviceProvider.GetService(); + var username = "yao"; + var response = await client.GetUserByName(username); + var content = response.Content; + Assert.AreEqual(username, content.Username); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs index a3131085..32777006 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Controllers/PetstoreControllerImpl.cs @@ -4,10 +4,13 @@ namespace Lab.RefitClient.WebAPI.Controllers; public class PetStoreControllerImpl : IPetStoreController { + private readonly IContextGetter _contextGetter; private readonly IHttpContextAccessor _httpContextAccessor; - public PetStoreControllerImpl(IHttpContextAccessor httpContextAccessor) + public PetStoreControllerImpl(IContextGetter contextGetter, + IHttpContextAccessor httpContextAccessor) { + this._contextGetter = contextGetter; this._httpContextAccessor = httpContextAccessor; } @@ -95,13 +98,12 @@ public Task> UploadFileAsync(long petId, string additi public async Task> GetUserByNameAsync(string username, CancellationToken cancellationToken = default(CancellationToken)) { - var headers = this._httpContextAccessor.HttpContext.Request.Headers; - var idempotencyKey = headers[PetStoreHeaderNames.IdempotencyKey]; - var apiKey = headers[PetStoreHeaderNames.ApiKey]; - + // 透過一個 Filter 處理 Header 並轉呈 HeaderContext 物件 + var headerContext = this._contextGetter.Get(); + var response = this._httpContextAccessor.HttpContext.Response; - response.Headers.Add(PetStoreHeaderNames.IdempotencyKey,idempotencyKey); - response.Headers.Add(PetStoreHeaderNames.ApiKey,apiKey); + response.Headers.Add(PetStoreHeaderNames.IdempotencyKey,headerContext.IdempotencyKey); + response.Headers.Add(PetStoreHeaderNames.ApiKey,headerContext.ApiKey); return new User { Id = 0, diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Program.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Program.cs index 34386fe6..e9d88138 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Program.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/Program.cs @@ -1,17 +1,27 @@ +using Lab.RefitClient; +using Lab.RefitClient.WebAPI; using Lab.RefitClient.WebAPI.Controllers; var builder = WebApplication.CreateBuilder(args); // Add services to the container. -builder.Services.AddControllers(); +builder.Services.AddControllers(p => +{ + p.Filters.Add(new ResolverHeaderContextFilterAttribute()); +}); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddHttpContextAccessor(); +builder.Services.AddSingleton>(); +builder.Services.AddSingleton>(p => p.GetService>()); +builder.Services.AddSingleton>(p => p.GetService>()); + builder.Services.AddScoped(); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -29,6 +39,9 @@ app.Run(); -public partial class Program +namespace Lab.RefitClient.WebAPI { + public partial class Program + { + } } \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/ResolverHeaderContextFilterAttribute.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/ResolverHeaderContextFilterAttribute.cs new file mode 100644 index 00000000..b28cb8bf --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/ResolverHeaderContextFilterAttribute.cs @@ -0,0 +1,20 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Lab.RefitClient.WebAPI +{ + public class ResolverHeaderContextFilterAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(ActionExecutingContext actionContext) + { + var idempotencyKey = actionContext.HttpContext.Request.Headers[PetStoreHeaderNames.IdempotencyKey]; + var apiKey = actionContext.HttpContext.Request.Headers[PetStoreHeaderNames.ApiKey]; + var headerContext = actionContext.HttpContext.RequestServices.GetService>(); + headerContext.Set(new HeaderContext + { + IdempotencyKey = idempotencyKey, + ApiKey = apiKey + }); + } + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/ContextAccessor.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/ContextAccessor.cs new file mode 100644 index 00000000..fff185dc --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/ContextAccessor.cs @@ -0,0 +1,22 @@ +namespace Lab.RefitClient +{ + public class ContextAccessor : IContextSetter, IContextGetter where T : class + { + private static readonly AsyncLocal s_current = new AsyncLocal(); + + public T Get() + { + return s_current?.Value; + } + + public void Set(T value) + { + if (s_current == null) + { + return; + } + + s_current.Value = value; + } + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/DefaultHeaderHandler.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/DefaultHeaderHandler.cs new file mode 100644 index 00000000..99f5f6b3 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/DefaultHeaderHandler.cs @@ -0,0 +1,21 @@ +namespace Lab.RefitClient; + +public class DefaultHeaderHandler : DelegatingHandler +{ + private IContextGetter _contextGetter; + + public DefaultHeaderHandler(IContextGetter contextGetter) + { + this._contextGetter = contextGetter; + } + + protected override async Task SendAsync(HttpRequestMessage request, + CancellationToken cancellationToken) + { + var headerContext = this._contextGetter.Get(); + request.Headers.Add(PetStoreHeaderNames.IdempotencyKey, headerContext.IdempotencyKey); + request.Headers.Add(PetStoreHeaderNames.ApiKey, headerContext.ApiKey); + + return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/HeaderContext.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/HeaderContext.cs new file mode 100644 index 00000000..62129d7d --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/HeaderContext.cs @@ -0,0 +1,8 @@ +namespace Lab.RefitClient; + +public class HeaderContext +{ + public string IdempotencyKey { get; set; } + + public string ApiKey { get; set; } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/IContextGetter.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/IContextGetter.cs new file mode 100644 index 00000000..9f5d5fc1 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/IContextGetter.cs @@ -0,0 +1,7 @@ +namespace Lab.RefitClient +{ + public interface IContextGetter where T : class + { + T Get(); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/IContextSetter.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/IContextSetter.cs new file mode 100644 index 00000000..a6356ce4 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/IContextSetter.cs @@ -0,0 +1,7 @@ +namespace Lab.RefitClient +{ + public interface IContextSetter where T : class + { + void Set(T trackContext); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/Workflow.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/Workflow.cs new file mode 100644 index 00000000..1dc0f8cf --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/Workflow.cs @@ -0,0 +1,19 @@ +using Lab.RefitClient.GeneratedCode.PetStore; + +namespace Lab.RefitClient; + +public class Workflow +{ + readonly ISwaggerPetstoreOpenAPI30 _petStore; + + public Workflow(ISwaggerPetstoreOpenAPI30 petStore) + { + this._petStore = petStore; + } + + public async Task GetUser(string name) + { + var getUserByNameResult = await this._petStore.GetUserByName(name); + return getUserByNameResult.Content.Username; + } +} \ No newline at end of file From d421516a66e588a0d6fa47649c6fd8430d66bb7a Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 15 Sep 2023 02:07:32 +0800 Subject: [PATCH 332/424] refactor --- .../Lab.RefitClient.TestProject/UnitTest2.cs | 34 +++++----- .../Controllers/DemoController.cs | 31 ++++++++++ .../GenHeaderContextFilterAttribute.cs | 20 ++++++ .../Lab.RefitClient.WebAPI.Caller.csproj | 19 ++++++ .../Lab.RefitClient.WebAPI.Caller/Program.cs | 62 +++++++++++++++++++ .../Properties/launchSettings.json | 41 ++++++++++++ .../appsettings.Development.json | 8 +++ .../appsettings.json | 9 +++ .../Lab.RefitClient/Lab.RefitClient.sln | 6 ++ .../Lab.RefitClient/DefaultHeaderHandler.cs | 2 +- 10 files changed, 213 insertions(+), 19 deletions(-) create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Controllers/DemoController.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/GenHeaderContextFilterAttribute.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Lab.RefitClient.WebAPI.Caller.csproj create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Program.cs create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Properties/launchSettings.json create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/appsettings.Development.json create mode 100644 WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/appsettings.json diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest2.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest2.cs index 4fda8e7f..a2b30d14 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest2.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest2.cs @@ -1,11 +1,5 @@ -using System.Text.Encodings.Web; -using System.Text.Json; using Lab.RefitClient.GeneratedCode.PetStore; -using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.FileProviders; -using Microsoft.Net.Http.Headers; using Refit; namespace Lab.RefitClient.TestProject; @@ -30,7 +24,10 @@ public async Task RestServiceFor() IdempotencyKey = "1234567890", ApiKey = "1234567890" }); - return new DefaultHeaderHandler(contextAccessor); + return new DefaultHeaderHandler(contextAccessor) + { + InnerHandler = new SocketsHttpHandler() + }; }, }; var client = RestService.For(httpClient, settings); @@ -47,22 +44,23 @@ public async Task AddRefitClient() var baseUrl = "https://localhost:7285/api/v3"; var services = new ServiceCollection(); + services.AddSingleton>(); services.AddSingleton>(p => p.GetService>()); - services.AddSingleton>(p => p.GetService>()); + services.AddSingleton>(p => + { + var context = p.GetService>(); + context.Set(new HeaderContext + { + IdempotencyKey = "1234567890", + ApiKey = "1234567890" + }); + return context; + }); services.AddRefitClient() .ConfigureHttpClient(p => { p.BaseAddress = new Uri(baseUrl); }) - .AddHttpMessageHandler(p => - { - var contextSetter = p.GetService>(); - contextSetter.Set(new HeaderContext - { - IdempotencyKey = "1234567890", - ApiKey = "1234567890" - }); - return new DefaultHeaderHandler(p.GetService>()); - }); + .AddHttpMessageHandler(p => new DefaultHeaderHandler(p.GetService>())); var serviceProvider = services.BuildServiceProvider(); var client = serviceProvider.GetService(); diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Controllers/DemoController.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Controllers/DemoController.cs new file mode 100644 index 00000000..8b016a0f --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Controllers/DemoController.cs @@ -0,0 +1,31 @@ +using Lab.RefitClient.GeneratedCode.PetStore; +using Microsoft.AspNetCore.Mvc; + +namespace Lab.RefitClient.WebAPI2.Controllers; + +[ApiController] +[Route("[controller]")] +public class DemoController : ControllerBase +{ + private readonly ISwaggerPetstoreOpenAPI30 _petStoreService; + private readonly ILogger _logger; + + public DemoController(ILogger logger, + ISwaggerPetstoreOpenAPI30 petStoreService) + { + this._logger = logger; + this._petStoreService = petStoreService; + } + + [HttpGet("{name}", Name = "GetUserName")] + public async Task Get(string name) + { + var response = await this._petStoreService.GetUserByName(name); + var user = response.Content; + var idempotencyKey = response.Headers.GetValues(PetStoreHeaderNames.IdempotencyKey).FirstOrDefault(); + var apiKey = response.Headers.GetValues(PetStoreHeaderNames.ApiKey).FirstOrDefault(); + this.Response.Headers[PetStoreHeaderNames.IdempotencyKey]= idempotencyKey; + this.Response.Headers[PetStoreHeaderNames.ApiKey]= apiKey; + return this.Ok(user); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/GenHeaderContextFilterAttribute.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/GenHeaderContextFilterAttribute.cs new file mode 100644 index 00000000..20a01720 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/GenHeaderContextFilterAttribute.cs @@ -0,0 +1,20 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Lab.RefitClient.WebAPI +{ + public class GenHeaderContextFilterAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(ActionExecutingContext actionContext) + { + var key = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"); + + var headerContext = actionContext.HttpContext.RequestServices.GetService>(); + headerContext.Set(new HeaderContext + { + IdempotencyKey = key, + ApiKey = key + }); + } + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Lab.RefitClient.WebAPI.Caller.csproj b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Lab.RefitClient.WebAPI.Caller.csproj new file mode 100644 index 00000000..18f67ad6 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Lab.RefitClient.WebAPI.Caller.csproj @@ -0,0 +1,19 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Program.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Program.cs new file mode 100644 index 00000000..e3254c21 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Program.cs @@ -0,0 +1,62 @@ +using Lab.RefitClient; +using Lab.RefitClient.GeneratedCode.PetStore; +using Lab.RefitClient.WebAPI; +using Refit; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(p => p.Filters.Add()); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddSingleton>(); +builder.Services.AddSingleton>(p => p.GetService>()); +builder.Services.AddSingleton>(p => p.GetService>()); + +var baseUrl = "https://localhost:7285/api/v3"; + +builder.Services.AddSingleton(p => +{ + var settings = new RefitSettings + { + HttpMessageHandlerFactory = () => + { + var contextGetter = p.GetService>(); + return new DefaultHeaderHandler(contextGetter) + { + InnerHandler = new SocketsHttpHandler() + }; + }, + }; + return settings; +}); + +builder.Services + .AddRefitClient(p => p.GetRequiredService()) + .ConfigureHttpClient(p => { p.BaseAddress = new Uri(baseUrl); }) + ; + +// builder.Services +// .AddRefitClient() +// .ConfigureHttpClient(p => { p.BaseAddress = new Uri(baseUrl); }) +// .AddHttpMessageHandler(p => new DefaultHeaderHandler(p.GetService>())) +// ; +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Properties/launchSettings.json b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Properties/launchSettings.json new file mode 100644 index 00000000..d4f149c1 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:33803", + "sslPort": 44384 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5217", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7237;http://localhost:5217", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/appsettings.Development.json b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/appsettings.json b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.sln b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.sln index 078ebb02..dcfa3ce8 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.sln +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.sln @@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.RefitClient.TestProject EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.RefitClient", "Lab.RefitClient\Lab.RefitClient.csproj", "{B0351748-0968-4F05-92F5-EF1C58D7CC3D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.RefitClient.WebAPI.Caller", "Lab.RefitClient.WebAPI.Caller\Lab.RefitClient.WebAPI.Caller.csproj", "{D6628998-3E70-4747-8A02-F4FA53D000B9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,5 +26,9 @@ Global {B0351748-0968-4F05-92F5-EF1C58D7CC3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {B0351748-0968-4F05-92F5-EF1C58D7CC3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {B0351748-0968-4F05-92F5-EF1C58D7CC3D}.Release|Any CPU.Build.0 = Release|Any CPU + {D6628998-3E70-4747-8A02-F4FA53D000B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6628998-3E70-4747-8A02-F4FA53D000B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6628998-3E70-4747-8A02-F4FA53D000B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6628998-3E70-4747-8A02-F4FA53D000B9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/DefaultHeaderHandler.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/DefaultHeaderHandler.cs index 99f5f6b3..294ff059 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/DefaultHeaderHandler.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient/DefaultHeaderHandler.cs @@ -2,7 +2,7 @@ public class DefaultHeaderHandler : DelegatingHandler { - private IContextGetter _contextGetter; + private readonly IContextGetter _contextGetter; public DefaultHeaderHandler(IContextGetter contextGetter) { From f5ec51761e7b1001b1505c38f528e1217412348f Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 15 Sep 2023 09:09:09 +0800 Subject: [PATCH 333/424] refactor --- .../Lab.RefitClient.WebAPI.Caller/Program.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Program.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Program.cs index e3254c21..b329fb5e 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Program.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI.Caller/Program.cs @@ -22,13 +22,9 @@ { var settings = new RefitSettings { - HttpMessageHandlerFactory = () => + HttpMessageHandlerFactory = () => new DefaultHeaderHandler(p.GetService>()) { - var contextGetter = p.GetService>(); - return new DefaultHeaderHandler(contextGetter) - { - InnerHandler = new SocketsHttpHandler() - }; + InnerHandler = new SocketsHttpHandler() }, }; return settings; From 3121de1bfad80859b3b7c096ee5eae2648f04378 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 17 Sep 2023 13:35:47 +0800 Subject: [PATCH 334/424] refactor --- .../Lab.RefitClient.TestProject/UnitTest2.cs | 79 ++++++++++++------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest2.cs b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest2.cs index a2b30d14..0ba4e539 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest2.cs +++ b/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.TestProject/UnitTest2.cs @@ -1,3 +1,4 @@ +using System.Net; using Lab.RefitClient.GeneratedCode.PetStore; using Microsoft.Extensions.DependencyInjection; using Refit; @@ -10,63 +11,85 @@ public class UnitTest2 [TestMethod] public async Task RestServiceFor() { + var contextAccessor = new ContextAccessor(); var server = new PetStoreTestServer(); - var httpClient = server.CreateClient(); + var httpClient = server.CreateDefaultClient(new DefaultHeaderHandler(contextAccessor) + { + InnerHandler = new SocketsHttpHandler() + }); httpClient.BaseAddress = new Uri(httpClient.BaseAddress, "api/v3"); - var settings = new RefitSettings - { - HttpMessageHandlerFactory = () => - { - var contextAccessor = new ContextAccessor(); - contextAccessor.Set(new HeaderContext - { - IdempotencyKey = "1234567890", - ApiKey = "1234567890" - }); - return new DefaultHeaderHandler(contextAccessor) - { - InnerHandler = new SocketsHttpHandler() - }; - }, - }; - var client = RestService.For(httpClient, settings); + var client = RestService.For(httpClient); var username = "yao"; + this.SetHeaderContext(contextAccessor); var response = await client.GetUserByName(username); var content = response.Content; + Console.WriteLine("get first headers: {0}", response.Headers); Assert.AreEqual(username, content.Username); + Thread.Sleep(1000); + + this.SetHeaderContext(contextAccessor); + var response1 = await client.GetUserByName(username); + var content1 = response1.Content; + Console.WriteLine("get second headers: {0}", response1.Headers); + Assert.AreEqual(username, content1.Username); } + void SetHeaderContext(IContextSetter setter) + { + var key = DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss.fff"); + var headerContext = new HeaderContext + { + IdempotencyKey = key, + ApiKey = key + }; + setter.Set(headerContext); + } + [TestMethod] public async Task AddRefitClient() { var baseUrl = "https://localhost:7285/api/v3"; var services = new ServiceCollection(); - + services.AddSingleton>(); services.AddSingleton>(p => p.GetService>()); - services.AddSingleton>(p => + services.AddSingleton>(p => p.GetService>()); + services.AddSingleton(p => { - var context = p.GetService>(); - context.Set(new HeaderContext + var settings = new RefitSettings { - IdempotencyKey = "1234567890", - ApiKey = "1234567890" - }); - return context; + HttpMessageHandlerFactory = () => + new DefaultHeaderHandler(p.GetService>()) + { + InnerHandler = new SocketsHttpHandler() + }, + }; + return settings; }); - services.AddRefitClient() + services.AddRefitClient(p => p.GetRequiredService()) .ConfigureHttpClient(p => { p.BaseAddress = new Uri(baseUrl); }) - .AddHttpMessageHandler(p => new DefaultHeaderHandler(p.GetService>())); + ; var serviceProvider = services.BuildServiceProvider(); + var contextSetter = serviceProvider.GetService>(); var client = serviceProvider.GetService(); var username = "yao"; + + this.SetHeaderContext(contextSetter); var response = await client.GetUserByName(username); var content = response.Content; + Console.WriteLine("get first headers: {0}", response.Headers); Assert.AreEqual(username, content.Username); + Thread.Sleep(1000); + + this.SetHeaderContext(contextSetter); + var response1 = await client.GetUserByName(username); + var content1 = response1.Content; + Console.WriteLine("get second headers: {0}", response1.Headers); + Assert.AreEqual(username, content1.Username); } } \ No newline at end of file From 645a13d9940a583c3ecce9c4cb0aefb25461ff52 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 24 Sep 2023 13:37:45 +0800 Subject: [PATCH 335/424] add lab mongodb project --- .../Lab.MongoDB.CRUD.TestProject.csproj | 24 +++ .../Lab.MongoDB.CRUD.TestProject/UnitTest1.cs | 191 ++++++++++++++++++ .../Lab.MongoDB.CRUD.TestProject/Usings.cs | 1 + MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.sln | 21 ++ MongoDB/Lab.MongoDB.CRUD/docker-compose.yml | 21 ++ 5 files changed, 258 insertions(+) create mode 100644 MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/Lab.MongoDB.CRUD.TestProject.csproj create mode 100644 MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/UnitTest1.cs create mode 100644 MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/Usings.cs create mode 100644 MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.sln create mode 100644 MongoDB/Lab.MongoDB.CRUD/docker-compose.yml diff --git a/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/Lab.MongoDB.CRUD.TestProject.csproj b/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/Lab.MongoDB.CRUD.TestProject.csproj new file mode 100644 index 00000000..55c74004 --- /dev/null +++ b/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/Lab.MongoDB.CRUD.TestProject.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + + false + + Linux + + + + + + + + + + + + + + diff --git a/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/UnitTest1.cs b/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/UnitTest1.cs new file mode 100644 index 00000000..a424341e --- /dev/null +++ b/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/UnitTest1.cs @@ -0,0 +1,191 @@ +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using Testcontainers.MongoDb; + +namespace Lab.MongoDB.CRUD.TestProject; + +[TestClass] +public class UnitTest1 +{ + private static MongoDbContainer MongoDbContainer; + private static MongoClient MongoClient; + private string TestData = "出發吧,讓我們航向偉大的航道"; + + [ClassInitialize] + public static async Task ClassInitialize(TestContext context) + { + // MongoDbContainer = new MongoDbBuilder() + // .WithPortBinding(27017, true) + // .Build(); + // await MongoDbContainer.StartAsync().ConfigureAwait(false); + // var mongoClientSettings = MongoClientSettings.FromConnectionString(MongoDbContainer.GetConnectionString()); + var mongoClientSettings = new MongoClientSettings() + { + Server = new MongoServerAddress("localhost", 27017), + }; + + MongoClient = new MongoClient(mongoClientSettings); + } + + [ClassCleanup] + public static async Task ClassCleanup() + { + // await MongoDbContainer.DisposeAsync().ConfigureAwait(false); + } + + [TestCleanup] + public void TestCleanup() + { + //復原資料 + var mongoCollection = MongoClient.GetDatabase("example").GetCollection("product"); + var filter = Builders.Filter + .Eq(r => r.Remark, this.TestData); + var data = mongoCollection.DeleteMany(filter); + } + + [TestMethod] + public async Task 新增一筆資料() + { + var mongoCollection = MongoClient.GetDatabase("example").GetCollection("product"); + var expected = new Product + { + Id = "1", + Name = "TV", + Price = 33.11m, + Remark = this.TestData + }; + + //新增一筆資料 + await mongoCollection.InsertOneAsync(expected); + + //驗證 + var actual = await mongoCollection.AsQueryable().FirstOrDefaultAsync(p => p.Id == "1"); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public async Task 更新一筆資料() + { + var mongoCollection = MongoClient.GetDatabase("example").GetCollection("product"); + var expected = new Product + { + Id = "1", + Name = "TV", + Price = 33.11m, + Remark = this.TestData + }; + + //產生資料 + var products = this.GenerateProducts(); + await mongoCollection.InsertManyAsync(products); + + var filter = Builders.Filter + .Eq(restaurant => restaurant.Id, "1"); + + var update = Builders.Update + .Set(restaurant => restaurant.Name, "TV"); + + //更新資料 + await mongoCollection.UpdateOneAsync(filter, update); + + //驗證 + var actual = await mongoCollection.AsQueryable().FirstOrDefaultAsync(p => p.Id == "1"); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public async Task 刪除資料() + { + var mongoCollection = MongoClient.GetDatabase("example").GetCollection("product"); + + //產生資料 + var products = this.GenerateProducts(); + await mongoCollection.InsertManyAsync(products); + + var filter = Builders.Filter + .Eq(restaurant => restaurant.Id, "1"); + + //更新資料 + await mongoCollection.DeleteOneAsync(filter); + + //驗證 + var actual = await mongoCollection.AsQueryable().FirstOrDefaultAsync(p => p.Id == "1"); + + Assert.AreEqual(null, actual); + } + + + [TestMethod] + public async Task 查詢() + { + var mongoCollection = MongoClient.GetDatabase("example").GetCollection("product"); + var expected = new Product + { + Id = "1", + Name = "Air jordan 11", + Price = 33.11m, + Remark = this.TestData + }; + + //產生資料 + var products = this.GenerateProducts(); + await mongoCollection.InsertManyAsync(products); + + //查詢1 + var filter = Builders.Filter.Eq(x => x.Id, "1"); + var find = await mongoCollection.FindAsync(filter); + var data1 = await find.FirstOrDefaultAsync(); + + //驗證 + Assert.AreEqual(expected, data1); + + //查詢2 + var data2 = await mongoCollection.AsQueryable().FirstOrDefaultAsync(p => p.Id == "1"); + + //驗證 + Assert.AreEqual(expected, data2); + } + + private List GenerateProducts() + { + var products = new List() + { + new() + { + Id = "1", + Name = "Air jordan 11", + Price = 33.11m, + Remark = this.TestData + }, + new() + { + Id = "2", + Name = "Air jordan 12", + Price = 33.12m, + Remark = this.TestData + }, + new() + { + Id = "3", + Name = "Air jordan 13", + Price = 33.13m, + Remark = this.TestData + } + }; + return products; + } + + + public record Product + { + public string Id { get; init; } + + public string Name { get; init; } + + public decimal Price { get; init; } + + public string Remark { get; init; } + } +} \ No newline at end of file diff --git a/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/Usings.cs b/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.sln b/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.sln new file mode 100644 index 00000000..90146883 --- /dev/null +++ b/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.sln @@ -0,0 +1,21 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.MongoDB.CRUD.TestProject", "Lab.MongoDB.CRUD.TestProject\Lab.MongoDB.CRUD.TestProject.csproj", "{58427830-E529-4C0F-A0E3-0D3E14BB81DC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C09F0E2A-1C9B-440C-BAF0-1E9C365E6362}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {58427830-E529-4C0F-A0E3-0D3E14BB81DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58427830-E529-4C0F-A0E3-0D3E14BB81DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58427830-E529-4C0F-A0E3-0D3E14BB81DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58427830-E529-4C0F-A0E3-0D3E14BB81DC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/MongoDB/Lab.MongoDB.CRUD/docker-compose.yml b/MongoDB/Lab.MongoDB.CRUD/docker-compose.yml new file mode 100644 index 00000000..b7ca4a4a --- /dev/null +++ b/MongoDB/Lab.MongoDB.CRUD/docker-compose.yml @@ -0,0 +1,21 @@ +version: "3.8" + +services: + mongo: + image: mongo + container_name: mongo_test + ports: + - 27017:27017 +# environment: +# MONGO_INITDB_ROOT_USERNAME: root +# MONGO_INITDB_ROOT_PASSWORD: example + + mongo-express: + image: mongo-express + container_name: mongo_express_test + ports: + - 8081:8081 +# environment: +# ME_CONFIG_MONGODB_ADMINUSERNAME: root +# ME_CONFIG_MONGODB_ADMINPASSWORD: example + \ No newline at end of file From 5139383721b56e4cb24def1577315175b00e99ef Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 25 Sep 2023 10:58:03 +0800 Subject: [PATCH 336/424] refactor --- .../Lab.MongoDB.CRUD.TestProject/UnitTest1.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/UnitTest1.cs b/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/UnitTest1.cs index a424341e..ef975efd 100644 --- a/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/UnitTest1.cs +++ b/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/UnitTest1.cs @@ -10,20 +10,20 @@ public class UnitTest1 { private static MongoDbContainer MongoDbContainer; private static MongoClient MongoClient; - private string TestData = "出發吧,讓我們航向偉大的航道"; + private readonly string TestData = "出發吧,讓我們航向偉大的航道"; [ClassInitialize] public static async Task ClassInitialize(TestContext context) { - // MongoDbContainer = new MongoDbBuilder() - // .WithPortBinding(27017, true) - // .Build(); - // await MongoDbContainer.StartAsync().ConfigureAwait(false); - // var mongoClientSettings = MongoClientSettings.FromConnectionString(MongoDbContainer.GetConnectionString()); - var mongoClientSettings = new MongoClientSettings() - { - Server = new MongoServerAddress("localhost", 27017), - }; + MongoDbContainer = new MongoDbBuilder() + .WithPortBinding(27017, true) + .Build(); + await MongoDbContainer.StartAsync(); + var mongoClientSettings = MongoClientSettings.FromConnectionString(MongoDbContainer.GetConnectionString()); + // var mongoClientSettings = new MongoClientSettings() + // { + // Server = new MongoServerAddress("localhost", 27017), + // }; MongoClient = new MongoClient(mongoClientSettings); } @@ -31,7 +31,7 @@ public static async Task ClassInitialize(TestContext context) [ClassCleanup] public static async Task ClassCleanup() { - // await MongoDbContainer.DisposeAsync().ConfigureAwait(false); + // await MongoDbContainer.DisposeAsync(); } [TestCleanup] From 67272bbf6711efb59c791d42640e01c59c3e1304 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 1 Oct 2023 13:30:52 +0800 Subject: [PATCH 337/424] add lazy di project --- DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy.sln | 16 ++++++ .../Controllers/DemoController.cs | 22 +++++++ .../Lab.MsDI.Lazy/Lab.MsDI.Lazy.csproj | 16 ++++++ .../Lab.MsDI.Lazy/Lab.MsDI.Lazy/Program.cs | 57 +++++++++++++++++++ .../Properties/launchSettings.json | 41 +++++++++++++ .../Lab.MsDI.Lazy/Lab.MsDI.Lazy/Service.cs | 54 ++++++++++++++++++ .../Lab.MsDI.Lazy/ServiceLazy.cs | 25 ++++++++ .../appsettings.Development.json | 8 +++ .../Lab.MsDI.Lazy/appsettings.json | 9 +++ 9 files changed, 248 insertions(+) create mode 100644 DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy.sln create mode 100644 DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Controllers/DemoController.cs create mode 100644 DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Lab.MsDI.Lazy.csproj create mode 100644 DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Program.cs create mode 100644 DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Properties/launchSettings.json create mode 100644 DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Service.cs create mode 100644 DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/ServiceLazy.cs create mode 100644 DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/appsettings.Development.json create mode 100644 DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/appsettings.json diff --git a/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy.sln b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy.sln new file mode 100644 index 00000000..77587338 --- /dev/null +++ b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.MsDI.Lazy", "Lab.MsDI.Lazy\Lab.MsDI.Lazy.csproj", "{33CA20D4-BF77-46F7-9BEB-6C75E0717EC3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {33CA20D4-BF77-46F7-9BEB-6C75E0717EC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33CA20D4-BF77-46F7-9BEB-6C75E0717EC3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33CA20D4-BF77-46F7-9BEB-6C75E0717EC3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33CA20D4-BF77-46F7-9BEB-6C75E0717EC3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Controllers/DemoController.cs b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Controllers/DemoController.cs new file mode 100644 index 00000000..031c244b --- /dev/null +++ b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Controllers/DemoController.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Lab.MsDI.Lazy.Controllers; + +[ApiController] +[Route("[controller]")] +public class DemoController : ControllerBase +{ + private readonly ILogger _logger; + private readonly IService _service; + public DemoController(ILogger logger, IService service) + { + this._logger = logger; + this._service = service; + } + + [HttpGet(Name = "GetDemo")] + public ActionResult Get() + { + return this.Ok(this._service.Get()); + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Lab.MsDI.Lazy.csproj b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Lab.MsDI.Lazy.csproj new file mode 100644 index 00000000..aad5fe67 --- /dev/null +++ b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Lab.MsDI.Lazy.csproj @@ -0,0 +1,16 @@ + + + + net7.0 + enable + enable + + + + + + + + + + diff --git a/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Program.cs b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Program.cs new file mode 100644 index 00000000..c41ac9a6 --- /dev/null +++ b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Program.cs @@ -0,0 +1,57 @@ +using Lab.MsDI.Lazy; +using LazyProxy; +using LazyProxy.ServiceProvider; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// builder.Services.AddScoped(); +// builder.Services.AddScoped(); + +// builder.Services.AddScoped(); +// +// builder.Services.AddScoped(p => +// { +// var serviceA = new Lazy(() => p.GetService()); +// var serviceB = new Lazy(() => p.GetService()); +// return new ServiceLazy(serviceA, serviceB); +// }); + +// builder.Services.AddScoped(p => +// { +// var lazyProxy = LazyProxyBuilder.CreateInstance(() => +// { +// var serviceA = p.GetService(); +// var serviceB = p.GetService(); +// return new Service(serviceA, serviceB); +// }); +// return lazyProxy; +// }); + +builder.Services.AddLazyScoped(); +builder.Services.AddLazyScoped(); +builder.Services.AddLazyScoped(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Properties/launchSettings.json b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Properties/launchSettings.json new file mode 100644 index 00000000..82121723 --- /dev/null +++ b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:36643", + "sslPort": 44334 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5102", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7029;http://localhost:5102", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Service.cs b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Service.cs new file mode 100644 index 00000000..c62a8126 --- /dev/null +++ b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/Service.cs @@ -0,0 +1,54 @@ +namespace Lab.MsDI.Lazy; + +public interface IService +{ + string Get(); +} + +public class Service : IService +{ + private readonly IServiceA _serviceA; + private readonly IServiceB _serviceB; + + public Service(IServiceA serviceA, + IServiceB serviceB) + { + this._serviceA = serviceA; + this._serviceB = serviceB; + } + + public string Get() + { + var random = new Random().Next(1, 10); + if (random % 2 == 0) + { + return this._serviceB.Get(); + } + + return this._serviceA.Get(); + } +} + +public interface IServiceA +{ + string Get(); +} + +public class ServiceA : IServiceA +{ + public ServiceA() => Console.WriteLine("Create instance for ServiceA"); + + public string Get() => "ServiceA"; +} + +public interface IServiceB +{ + string Get(); +} + +public class ServiceB : IServiceB +{ + public ServiceB() => Console.WriteLine("Create instance for ServiceB"); + + public string Get() => "ServiceB"; +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/ServiceLazy.cs b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/ServiceLazy.cs new file mode 100644 index 00000000..0b0f3c68 --- /dev/null +++ b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/ServiceLazy.cs @@ -0,0 +1,25 @@ +namespace Lab.MsDI.Lazy; + +public class ServiceLazy : IService +{ + private readonly Lazy _serviceA; + private readonly Lazy _serviceB; + + public ServiceLazy(Lazy serviceA, + Lazy serviceB) + { + this._serviceA = serviceA; + this._serviceB = serviceB; + } + + public string Get() + { + var random = new Random().Next(1, 10); + if (random % 2 == 0) + { + return this._serviceB.Value.Get(); + } + + return this._serviceA.Value.Get(); + } +} \ No newline at end of file diff --git a/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/appsettings.Development.json b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/appsettings.json b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/DI/Lab.MsDI/Lab.MsDI.Lazy/Lab.MsDI.Lazy/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From 9161906f30644396300541ce87748bb13ffc470e Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 2 Oct 2023 12:10:52 +0800 Subject: [PATCH 338/424] add trace context --- .../Controllers/DemoController.cs | 33 ++++++++ .../Controllers/FailureObjectResult.cs | 16 ++++ .../Controllers/GenericController.cs | 72 ++++++++++++++++++ .../Lab.Context.Trace.WebAPI.csproj | 24 ++++++ .../Models/Failure.cs | 44 +++++++++++ .../Models/FailureCode.cs | 17 +++++ .../Lab.Context.Trace.WebAPI/Models/Member.cs | 30 ++++++++ .../Lab.Context.Trace.WebAPI/Program.cs | 63 +++++++++++++++ .../Properties/launchSettings.json | 41 ++++++++++ .../SysHeaderNames.cs | 6 ++ .../TraceContextMiddleware.cs | 76 +++++++++++++++++++ .../appsettings.Development.json | 8 ++ .../Lab.Context.Trace.WebAPI/appsettings.json | 9 +++ Trace/Lab.Context.Trace/Lab.Context.Trace.sln | 27 +++++++ .../Lab.Context.Trace/IObjectContextGetter.cs | 6 ++ .../Lab.Context.Trace/IObjectContextSetter.cs | 6 ++ .../Lab.Context.Trace.csproj | 16 ++++ .../ObjectContextAccessor.cs | 22 ++++++ .../Lab.Context.Trace/TraceContext.cs | 8 ++ Trace/Lab.Context.Trace/docker-compose.yml | 7 ++ 20 files changed, 531 insertions(+) create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/DemoController.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/FailureObjectResult.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/GenericController.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Lab.Context.Trace.WebAPI.csproj create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/Failure.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/FailureCode.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/Member.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Properties/launchSettings.json create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/SysHeaderNames.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/TraceContextMiddleware.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/appsettings.Development.json create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/appsettings.json create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.sln create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextGetter.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextSetter.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace/Lab.Context.Trace.csproj create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace/ObjectContextAccessor.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace/TraceContext.cs create mode 100644 Trace/Lab.Context.Trace/docker-compose.yml diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/DemoController.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/DemoController.cs new file mode 100644 index 00000000..dd2a63ea --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/DemoController.cs @@ -0,0 +1,33 @@ +using Lab.Context.Trace.WebAPI.Models; +using Microsoft.AspNetCore.Mvc; + +namespace Lab.Context.Trace.WebAPI.Controllers; + +[ApiController] +[Route("[controller]")] +public class DemoController : ControllerBase +{ + private readonly ILogger _logger; + private readonly IObjectContextGetter _contextGetter; + + public DemoController(ILogger logger, + IObjectContextGetter contextGetter) + { + _logger = logger; + this._contextGetter = contextGetter; + } + + [HttpGet(Name = "GetDemo")] + public ActionResult Get() + { + var traceContext = this._contextGetter.Get(); + var userId = traceContext.UserId; + + // 由 Context 取得 UserId + var member = Member.GetFakeMembers().FirstOrDefault(p => p.UserId == userId); + + this._logger.LogInformation(2000, "found {@Data}", member); + + return this.Ok(member); + } +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/FailureObjectResult.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/FailureObjectResult.cs new file mode 100644 index 00000000..312f4a30 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/FailureObjectResult.cs @@ -0,0 +1,16 @@ +using Lab.Context.Trace.WebAPI.Models; +using Microsoft.AspNetCore.Mvc; + +namespace Lab.Context.Trace.WebAPI.Controllers; + +public class FailureObjectResult : ObjectResult +{ + public FailureObjectResult(Failure failure, int statusCode = StatusCodes.Status400BadRequest) + : base(failure) + { + this.StatusCode = statusCode; + // Failure.Exception 已經使用 [JsonIgnore],不會再回傳給調用端 + // this.Value = failure.WithoutException(); + this.Value = failure; + } +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/GenericController.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/GenericController.cs new file mode 100644 index 00000000..a1190a21 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/GenericController.cs @@ -0,0 +1,72 @@ +using System.Diagnostics; +using Lab.Context.Trace.WebAPI.Models; +using Microsoft.AspNetCore.Mvc; + +namespace Lab.Context.Trace.WebAPI.Controllers; + +public class GenericController : ControllerBase +{ + public Dictionary FailureCodeLookup => s_failureCodeLookupLazy.Value; + + private static readonly Lazy> s_failureCodeLookupLazy = new(CreateFailureCodeLookup); + + private static Dictionary CreateFailureCodeMappings() + { + //用關鍵字定義錯誤代碼 + return new Dictionary(StringComparer.InvariantCultureIgnoreCase) + { + { "error", StatusCodes.Status500InternalServerError }, + { "invalid", StatusCodes.Status400BadRequest }, + { "notfound", StatusCodes.Status404NotFound }, + { "concurrency", StatusCodes.Status429TooManyRequests }, + { "conflict", StatusCodes.Status404NotFound }, + }; + } + + [NonAction] + public FailureObjectResult FailureContent(Failure failure) + { + if (string.IsNullOrWhiteSpace(failure.TraceId)) + { + failure.TraceId = Activity.Current?.Id ?? this.HttpContext.TraceIdentifier; + } + + if (this.FailureCodeLookup.TryGetValue(failure.Code, out int statusCode)) + { + return new FailureObjectResult(failure, statusCode); + } + + return new FailureObjectResult(failure); + } + + private static Dictionary CreateFailureCodeLookup() + { + var result = new Dictionary(); + var type = typeof(FailureCode); + var names = Enum.GetNames(type); + var failureMappings = CreateFailureCodeMappings(); + foreach (var name in names) + { + var failureCode = FailureCode.Parse(name); + var isDefined = false; + foreach (var mapping in failureMappings) + { + var key = mapping.Key; + var statusCode = mapping.Value; + if (name.Contains(key, StringComparison.OrdinalIgnoreCase)) + { + isDefined = true; + result.Add(failureCode, statusCode); + break; + } + } + + if (isDefined == false) + { + result.Add(failureCode, StatusCodes.Status500InternalServerError); + } + } + + return result; + } +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Lab.Context.Trace.WebAPI.csproj b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Lab.Context.Trace.WebAPI.csproj new file mode 100644 index 00000000..7aba19cd --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Lab.Context.Trace.WebAPI.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + + + + + + diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/Failure.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/Failure.cs new file mode 100644 index 00000000..17452f1e --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/Failure.cs @@ -0,0 +1,44 @@ +using System.Text.Json.Serialization; + +namespace Lab.Context.Trace.WebAPI.Models; + +public class Failure +{ + public Failure() + { + } + + public Failure(FailureCode code, string message) + { + this.Code = code; + this.Message = message; + } + + /// + /// 錯誤碼 + /// + public FailureCode Code { get; init; } + + /// + /// 錯誤訊息 + /// + public string Message { get; init; } + + /// + /// 錯誤發生時的資料 + /// + public object Data { get; init; } + + /// + /// 追蹤 Id + /// + public string TraceId { get; set; } + + /// + /// 例外,不回傳給 Web API + /// + [JsonIgnore] + public Exception Exception { get; set; } + + public List Details { get; init; } = new(); +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/FailureCode.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/FailureCode.cs new file mode 100644 index 00000000..a57ade75 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/FailureCode.cs @@ -0,0 +1,17 @@ +namespace Lab.Context.Trace.WebAPI.Models; + +public enum FailureCode +{ + UnknownError = 0, + InputInvalid = 1, + MemberNotFound, + MemberAlreadyExist, + ServerError, + DataConflict, + DataConcurrency, + DataNotFound, + DbError, + S3Error, + CellphoneFormatInvalid, + Unauthorized +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/Member.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/Member.cs new file mode 100644 index 00000000..2c2f7896 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/Member.cs @@ -0,0 +1,30 @@ +namespace Lab.Context.Trace.WebAPI.Models; + +internal class Member +{ + public string UserId { get; set; } + + public int Age { get; set; } + + public string Name { get; set; } + + public static IEnumerable GetFakeMembers() + { + return new List() + { + new() + { + UserId = "yao", + Age = 19, + Name = "小章" + }, + new() + { + UserId = "yao1", + Age = 21, + Name = "小章1" + }, + + }; + } +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs new file mode 100644 index 00000000..220841bd --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs @@ -0,0 +1,63 @@ +using Lab.Context.Trace; +using Lab.Context.Trace.WebAPI; +using Serilog; +using Serilog.Events; + +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.File("logs/host-.txt", rollingInterval: RollingInterval.Hour) + .CreateLogger(); +Log.Information("Starting web host"); + +try +{ + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + builder.Services.AddControllers(); + + builder.Host.UseSerilog((context, services, config) => + config.ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq("http://localhost:5341") + .WriteTo.File("logs/aspnet-.txt", rollingInterval: RollingInterval.Minute) + ); + + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + builder.Services.AddSingleton>(); + builder.Services.AddSingleton>(p => p.GetService>()); + builder.Services.AddSingleton>(p => p.GetService>()); + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + + app.UseHttpsRedirection(); + + app.UseAuthorization(); + app.UseMiddleware(); + + app.MapControllers(); + app.Run(); + return 0; +} +catch (Exception ex) +{ + Log.Fatal(ex, "Host terminated unexpectedly"); + return 1; +} +finally +{ + Log.CloseAndFlush(); +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Properties/launchSettings.json b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Properties/launchSettings.json new file mode 100644 index 00000000..6807ef37 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:40069", + "sslPort": 44377 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5294", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7004;http://localhost:5294", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/SysHeaderNames.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/SysHeaderNames.cs new file mode 100644 index 00000000..f63f83a2 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/SysHeaderNames.cs @@ -0,0 +1,6 @@ +namespace Lab.Context.Trace.WebAPI; + +public abstract class SysHeaderNames +{ + public const string TraceId = "x-trace-id"; +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/TraceContextMiddleware.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/TraceContextMiddleware.cs new file mode 100644 index 00000000..323cbdec --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/TraceContextMiddleware.cs @@ -0,0 +1,76 @@ +using System.Security.Claims; +using Lab.Context.Trace.WebAPI.Models; + +namespace Lab.Context.Trace.WebAPI; + +public class TraceContextMiddleware +{ + private readonly RequestDelegate _next; + + public TraceContextMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext httpContext, ILogger logger) + { + var traceId = httpContext.Request.Headers[SysHeaderNames.TraceId].FirstOrDefault(); + + //// 若調用端沒有傳入 traceId,則產生一個新的 traceId + if (string.IsNullOrWhiteSpace(traceId)) + { + traceId = httpContext.TraceIdentifier; + } + + // 模擬登入 + Signin(httpContext); + + if (httpContext.User.Identity.IsAuthenticated == false) + { + httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized; + await httpContext.Response.WriteAsJsonAsync(new Failure + { + Code = FailureCode.Unauthorized, + Message = "not login", + }); + return; + } + + var userId = httpContext.User.Identity.Name; + + // 寫入 trace context 到 object context setter + var contextSetter = httpContext.RequestServices.GetService>(); + contextSetter.Set(new TraceContext + { + TraceId = traceId, + UserId = userId + }); + + // 附加 traceId 與 userId 到 log 中 + using var _ = logger.BeginScope("{Location},{TraceId},{UserId}", + "TW", traceId, userId); + + // 附加 traceId 到 response header 中 + var contextGetter = httpContext.RequestServices.GetService>(); + var traceContext = contextGetter.Get(); + httpContext.Response.Headers.TryAdd(SysHeaderNames.TraceId, traceContext.TraceId); + + await this._next.Invoke(httpContext); + } + + /// + /// 假的登入 + /// + /// + private static void Signin(HttpContext context) + { + var claims = new[] + { + new Claim(ClaimTypes.NameIdentifier, "yao"), + new Claim(ClaimTypes.Name, "yao"), + }; + var identity = new ClaimsIdentity(claims, "Bearer"); + var principal = new ClaimsPrincipal(identity); + context.User = principal; + } +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/appsettings.Development.json b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/appsettings.json b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.sln b/Trace/Lab.Context.Trace/Lab.Context.Trace.sln new file mode 100644 index 00000000..d35ea5dc --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Context.Trace", "Lab.Context.Trace\Lab.Context.Trace.csproj", "{85EF9A0D-F431-4C29-9FDD-E39C38A3D027}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Context.Trace.WebAPI", "Lab.Context.Trace.WebAPI\Lab.Context.Trace.WebAPI.csproj", "{04987A3C-032F-49CB-B59A-80D188BF1372}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{47606710-3952-41C4-8B80-57FB42EF000C}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {85EF9A0D-F431-4C29-9FDD-E39C38A3D027}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85EF9A0D-F431-4C29-9FDD-E39C38A3D027}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85EF9A0D-F431-4C29-9FDD-E39C38A3D027}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85EF9A0D-F431-4C29-9FDD-E39C38A3D027}.Release|Any CPU.Build.0 = Release|Any CPU + {04987A3C-032F-49CB-B59A-80D188BF1372}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04987A3C-032F-49CB-B59A-80D188BF1372}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04987A3C-032F-49CB-B59A-80D188BF1372}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04987A3C-032F-49CB-B59A-80D188BF1372}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextGetter.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextGetter.cs new file mode 100644 index 00000000..dc7be908 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextGetter.cs @@ -0,0 +1,6 @@ +namespace Lab.Context.Trace; + +public interface IObjectContextGetter +{ + T Get(); +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextSetter.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextSetter.cs new file mode 100644 index 00000000..8a406392 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextSetter.cs @@ -0,0 +1,6 @@ +namespace Lab.Context.Trace; + +public interface IObjectContextSetter +{ + void Set(T value); +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace/Lab.Context.Trace.csproj b/Trace/Lab.Context.Trace/Lab.Context.Trace/Lab.Context.Trace.csproj new file mode 100644 index 00000000..0c1dcdeb --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace/Lab.Context.Trace.csproj @@ -0,0 +1,16 @@ + + + + net7.0 + enable + enable + Linux + + + + + + + + + diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace/ObjectContextAccessor.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace/ObjectContextAccessor.cs new file mode 100644 index 00000000..633a0ce7 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace/ObjectContextAccessor.cs @@ -0,0 +1,22 @@ +namespace Lab.Context.Trace; + +public class ObjectContextAccessor : IObjectContextSetter, IObjectContextGetter + where T : class +{ + private static readonly AsyncLocal s_current = new AsyncLocal(); + + public T Get() + { + return s_current?.Value; + } + + public void Set(T value) + { + if (s_current == null) + { + return; + } + + s_current.Value = value; + } +} diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace/TraceContext.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace/TraceContext.cs new file mode 100644 index 00000000..fda0b199 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace/TraceContext.cs @@ -0,0 +1,8 @@ +namespace Lab.Context.Trace; + +public record TraceContext +{ + public string TraceId { get; init; } + + public string UserId { get; init; } +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/docker-compose.yml b/Trace/Lab.Context.Trace/docker-compose.yml new file mode 100644 index 00000000..a9338570 --- /dev/null +++ b/Trace/Lab.Context.Trace/docker-compose.yml @@ -0,0 +1,7 @@ +services: + seq: + image: datalust/seq:latest + ports: + - "5341:80" + environment: + - ACCEPT_EULA=Y \ No newline at end of file From 0d3447b700f9e138f52af2bf615e220a5c02944a Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 4 Oct 2023 09:28:42 +0800 Subject: [PATCH 339/424] refactor --- .../Controllers/DemoController.cs | 4 ++-- .../Lab.Context.Trace.WebAPI/Program.cs | 6 ++--- .../TraceContextMiddleware.cs | 4 ++-- .../Lab.Context.Trace/ContextAccessor.cs | 23 +++++++++++++++++++ .../Lab.Context.Trace/ContextHolder.cs | 6 +++++ .../Lab.Context.Trace/IContextGetter.cs | 6 +++++ ...jectContextSetter.cs => IContextSetter.cs} | 2 +- .../Lab.Context.Trace/IObjectContextGetter.cs | 6 ----- .../ObjectContextAccessor.cs | 22 ------------------ 9 files changed, 43 insertions(+), 36 deletions(-) create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace/ContextAccessor.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace/ContextHolder.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace/IContextGetter.cs rename Trace/Lab.Context.Trace/Lab.Context.Trace/{IObjectContextSetter.cs => IContextSetter.cs} (57%) delete mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextGetter.cs delete mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace/ObjectContextAccessor.cs diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/DemoController.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/DemoController.cs index dd2a63ea..6b7972e8 100644 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/DemoController.cs +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/DemoController.cs @@ -8,10 +8,10 @@ namespace Lab.Context.Trace.WebAPI.Controllers; public class DemoController : ControllerBase { private readonly ILogger _logger; - private readonly IObjectContextGetter _contextGetter; + private readonly IContextGetter _contextGetter; public DemoController(ILogger logger, - IObjectContextGetter contextGetter) + IContextGetter contextGetter) { _logger = logger; this._contextGetter = contextGetter; diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs index 220841bd..81846c27 100644 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs @@ -31,9 +31,9 @@ // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); - builder.Services.AddSingleton>(); - builder.Services.AddSingleton>(p => p.GetService>()); - builder.Services.AddSingleton>(p => p.GetService>()); + builder.Services.AddSingleton>(); + builder.Services.AddSingleton>(p => p.GetService>()); + builder.Services.AddSingleton>(p => p.GetService>()); var app = builder.Build(); // Configure the HTTP request pipeline. diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/TraceContextMiddleware.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/TraceContextMiddleware.cs index 323cbdec..3b779bd1 100644 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/TraceContextMiddleware.cs +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/TraceContextMiddleware.cs @@ -39,7 +39,7 @@ await httpContext.Response.WriteAsJsonAsync(new Failure var userId = httpContext.User.Identity.Name; // 寫入 trace context 到 object context setter - var contextSetter = httpContext.RequestServices.GetService>(); + var contextSetter = httpContext.RequestServices.GetService>(); contextSetter.Set(new TraceContext { TraceId = traceId, @@ -51,7 +51,7 @@ await httpContext.Response.WriteAsJsonAsync(new Failure "TW", traceId, userId); // 附加 traceId 到 response header 中 - var contextGetter = httpContext.RequestServices.GetService>(); + IContextGetter? contextGetter = httpContext.RequestServices.GetService>(); var traceContext = contextGetter.Get(); httpContext.Response.Headers.TryAdd(SysHeaderNames.TraceId, traceContext.TraceId); diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace/ContextAccessor.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace/ContextAccessor.cs new file mode 100644 index 00000000..677e4e3b --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace/ContextAccessor.cs @@ -0,0 +1,23 @@ +namespace Lab.Context.Trace; + +public class ContextAccessor : IContextSetter, IContextGetter + where T : class +{ + private static readonly AsyncLocal> s_current = new(); + + public T? Get() + { + var contextHolder = s_current.Value; + return contextHolder?.Value; + } + + public void Set(T value) + { + if (s_current.Value == null) + { + s_current.Value = new ContextHolder(); + } + + s_current.Value.Value = value; + } +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace/ContextHolder.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace/ContextHolder.cs new file mode 100644 index 00000000..3a0238d8 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace/ContextHolder.cs @@ -0,0 +1,6 @@ +namespace Lab.Context.Trace; + +public class ContextHolder +{ + public T Value { get; set; } +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace/IContextGetter.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace/IContextGetter.cs new file mode 100644 index 00000000..315ffb41 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace/IContextGetter.cs @@ -0,0 +1,6 @@ +namespace Lab.Context.Trace; + +public interface IContextGetter +{ + T? Get(); +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextSetter.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace/IContextSetter.cs similarity index 57% rename from Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextSetter.cs rename to Trace/Lab.Context.Trace/Lab.Context.Trace/IContextSetter.cs index 8a406392..a9b95902 100644 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextSetter.cs +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace/IContextSetter.cs @@ -1,6 +1,6 @@ namespace Lab.Context.Trace; -public interface IObjectContextSetter +public interface IContextSetter { void Set(T value); } \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextGetter.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextGetter.cs deleted file mode 100644 index dc7be908..00000000 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace/IObjectContextGetter.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Lab.Context.Trace; - -public interface IObjectContextGetter -{ - T Get(); -} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace/ObjectContextAccessor.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace/ObjectContextAccessor.cs deleted file mode 100644 index 633a0ce7..00000000 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace/ObjectContextAccessor.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Lab.Context.Trace; - -public class ObjectContextAccessor : IObjectContextSetter, IObjectContextGetter - where T : class -{ - private static readonly AsyncLocal s_current = new AsyncLocal(); - - public T Get() - { - return s_current?.Value; - } - - public void Set(T value) - { - if (s_current == null) - { - return; - } - - s_current.Value = value; - } -} From 5ec9cd755986ce066ac41473ea5a7ddd132f5199 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 8 Oct 2023 12:38:38 +0800 Subject: [PATCH 340/424] refactor --- .../Controllers/DemoController.cs | 13 +++++------ .../Lab.Context.Trace.WebAPI/Program.cs | 12 +++++++--- .../TraceContextMiddleware.cs | 10 ++++---- .../{TraceContext.cs => AuthContext.cs} | 2 +- .../Lab.Context.Trace/ContextAccessor.cs | 16 ++++--------- .../Lab.Context.Trace/ContextAccessor2.cs | 23 +++++++++++++++++++ 6 files changed, 49 insertions(+), 27 deletions(-) rename Trace/Lab.Context.Trace/Lab.Context.Trace/{TraceContext.cs => AuthContext.cs} (81%) create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace/ContextAccessor2.cs diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/DemoController.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/DemoController.cs index 6b7972e8..0beaf5d4 100644 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/DemoController.cs +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/DemoController.cs @@ -8,21 +8,20 @@ namespace Lab.Context.Trace.WebAPI.Controllers; public class DemoController : ControllerBase { private readonly ILogger _logger; - private readonly IContextGetter _contextGetter; + private readonly IContextGetter _authContextGetter; public DemoController(ILogger logger, - IContextGetter contextGetter) + IContextGetter authContextGetter) { - _logger = logger; - this._contextGetter = contextGetter; + this._logger = logger; + this._authContextGetter = authContextGetter; } [HttpGet(Name = "GetDemo")] public ActionResult Get() { - var traceContext = this._contextGetter.Get(); - var userId = traceContext.UserId; - + var authContext = this._authContextGetter.Get(); + var userId = authContext.UserId; // 由 Context 取得 UserId var member = Member.GetFakeMembers().FirstOrDefault(p => p.UserId == userId); diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs index 81846c27..bfbc74b3 100644 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs @@ -31,9 +31,15 @@ // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); - builder.Services.AddSingleton>(); - builder.Services.AddSingleton>(p => p.GetService>()); - builder.Services.AddSingleton>(p => p.GetService>()); + + // builder.Services.AddScoped>(); + // builder.Services.AddScoped>(p => p.GetService>()); + // builder.Services.AddScoped>(p => p.GetService>()); + + builder.Services.AddSingleton>(); + builder.Services.AddSingleton>(p => p.GetService>()); + builder.Services.AddSingleton>(p => p.GetService>()); + var app = builder.Build(); // Configure the HTTP request pipeline. diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/TraceContextMiddleware.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/TraceContextMiddleware.cs index 3b779bd1..144f22a1 100644 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/TraceContextMiddleware.cs +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/TraceContextMiddleware.cs @@ -9,12 +9,12 @@ public class TraceContextMiddleware public TraceContextMiddleware(RequestDelegate next) { - _next = next; + this._next = next; } public async Task Invoke(HttpContext httpContext, ILogger logger) { - var traceId = httpContext.Request.Headers[SysHeaderNames.TraceId].FirstOrDefault(); + var traceId = httpContext.Request.Headers[SysHeaderNames.TraceId].FirstOrDefault(); //// 若調用端沒有傳入 traceId,則產生一個新的 traceId if (string.IsNullOrWhiteSpace(traceId)) @@ -39,8 +39,8 @@ await httpContext.Response.WriteAsJsonAsync(new Failure var userId = httpContext.User.Identity.Name; // 寫入 trace context 到 object context setter - var contextSetter = httpContext.RequestServices.GetService>(); - contextSetter.Set(new TraceContext + var authContextSetter = httpContext.RequestServices.GetService>(); + authContextSetter.Set(new AuthContext { TraceId = traceId, UserId = userId @@ -51,7 +51,7 @@ await httpContext.Response.WriteAsJsonAsync(new Failure "TW", traceId, userId); // 附加 traceId 到 response header 中 - IContextGetter? contextGetter = httpContext.RequestServices.GetService>(); + IContextGetter? contextGetter = httpContext.RequestServices.GetService>(); var traceContext = contextGetter.Get(); httpContext.Response.Headers.TryAdd(SysHeaderNames.TraceId, traceContext.TraceId); diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace/TraceContext.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace/AuthContext.cs similarity index 81% rename from Trace/Lab.Context.Trace/Lab.Context.Trace/TraceContext.cs rename to Trace/Lab.Context.Trace/Lab.Context.Trace/AuthContext.cs index fda0b199..844135ae 100644 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace/TraceContext.cs +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace/AuthContext.cs @@ -1,6 +1,6 @@ namespace Lab.Context.Trace; -public record TraceContext +public record AuthContext { public string TraceId { get; init; } diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace/ContextAccessor.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace/ContextAccessor.cs index 677e4e3b..60465637 100644 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace/ContextAccessor.cs +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace/ContextAccessor.cs @@ -3,21 +3,15 @@ public class ContextAccessor : IContextSetter, IContextGetter where T : class { - private static readonly AsyncLocal> s_current = new(); + private T _value; - public T? Get() + public void Set(T value) { - var contextHolder = s_current.Value; - return contextHolder?.Value; + this._value = value; } - public void Set(T value) + public T? Get() { - if (s_current.Value == null) - { - s_current.Value = new ContextHolder(); - } - - s_current.Value.Value = value; + return this._value; } } \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace/ContextAccessor2.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace/ContextAccessor2.cs new file mode 100644 index 00000000..a706f605 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace/ContextAccessor2.cs @@ -0,0 +1,23 @@ +namespace Lab.Context.Trace; + +public class ContextAccessor2 : IContextSetter, IContextGetter + where T : class +{ + private static readonly AsyncLocal> s_current = new(); + + public T? Get() + { + var contextHolder = s_current.Value; + return contextHolder?.Value; + } + + public void Set(T value) + { + if (s_current.Value == null) + { + s_current.Value = new ContextHolder(); + } + + s_current.Value.Value = value; + } +} \ No newline at end of file From 3a8e160fba1046d956b0afeab151589bc5b50635 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 8 Oct 2023 12:40:31 +0800 Subject: [PATCH 341/424] refactor --- .../Controllers/FailureObjectResult.cs | 16 ----- .../Controllers/GenericController.cs | 72 ------------------- .../Models/FailureCode.cs | 11 --- 3 files changed, 99 deletions(-) delete mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/FailureObjectResult.cs delete mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/GenericController.cs diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/FailureObjectResult.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/FailureObjectResult.cs deleted file mode 100644 index 312f4a30..00000000 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/FailureObjectResult.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Lab.Context.Trace.WebAPI.Models; -using Microsoft.AspNetCore.Mvc; - -namespace Lab.Context.Trace.WebAPI.Controllers; - -public class FailureObjectResult : ObjectResult -{ - public FailureObjectResult(Failure failure, int statusCode = StatusCodes.Status400BadRequest) - : base(failure) - { - this.StatusCode = statusCode; - // Failure.Exception 已經使用 [JsonIgnore],不會再回傳給調用端 - // this.Value = failure.WithoutException(); - this.Value = failure; - } -} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/GenericController.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/GenericController.cs deleted file mode 100644 index a1190a21..00000000 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Controllers/GenericController.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Diagnostics; -using Lab.Context.Trace.WebAPI.Models; -using Microsoft.AspNetCore.Mvc; - -namespace Lab.Context.Trace.WebAPI.Controllers; - -public class GenericController : ControllerBase -{ - public Dictionary FailureCodeLookup => s_failureCodeLookupLazy.Value; - - private static readonly Lazy> s_failureCodeLookupLazy = new(CreateFailureCodeLookup); - - private static Dictionary CreateFailureCodeMappings() - { - //用關鍵字定義錯誤代碼 - return new Dictionary(StringComparer.InvariantCultureIgnoreCase) - { - { "error", StatusCodes.Status500InternalServerError }, - { "invalid", StatusCodes.Status400BadRequest }, - { "notfound", StatusCodes.Status404NotFound }, - { "concurrency", StatusCodes.Status429TooManyRequests }, - { "conflict", StatusCodes.Status404NotFound }, - }; - } - - [NonAction] - public FailureObjectResult FailureContent(Failure failure) - { - if (string.IsNullOrWhiteSpace(failure.TraceId)) - { - failure.TraceId = Activity.Current?.Id ?? this.HttpContext.TraceIdentifier; - } - - if (this.FailureCodeLookup.TryGetValue(failure.Code, out int statusCode)) - { - return new FailureObjectResult(failure, statusCode); - } - - return new FailureObjectResult(failure); - } - - private static Dictionary CreateFailureCodeLookup() - { - var result = new Dictionary(); - var type = typeof(FailureCode); - var names = Enum.GetNames(type); - var failureMappings = CreateFailureCodeMappings(); - foreach (var name in names) - { - var failureCode = FailureCode.Parse(name); - var isDefined = false; - foreach (var mapping in failureMappings) - { - var key = mapping.Key; - var statusCode = mapping.Value; - if (name.Contains(key, StringComparison.OrdinalIgnoreCase)) - { - isDefined = true; - result.Add(failureCode, statusCode); - break; - } - } - - if (isDefined == false) - { - result.Add(failureCode, StatusCodes.Status500InternalServerError); - } - } - - return result; - } -} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/FailureCode.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/FailureCode.cs index a57ade75..4eec0439 100644 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/FailureCode.cs +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Models/FailureCode.cs @@ -2,16 +2,5 @@ namespace Lab.Context.Trace.WebAPI.Models; public enum FailureCode { - UnknownError = 0, - InputInvalid = 1, - MemberNotFound, - MemberAlreadyExist, - ServerError, - DataConflict, - DataConcurrency, - DataNotFound, - DbError, - S3Error, - CellphoneFormatInvalid, Unauthorized } \ No newline at end of file From 0c1df77080c93f2fcb8c9e2549c1f05518fc6f0b Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 8 Oct 2023 15:04:42 +0800 Subject: [PATCH 342/424] refactor --- .../Lab.Context.Trace.ConsoleApp.csproj | 10 +++ .../Lab.Context.Trace.ConsoleApp/Program.cs | 69 +++++++++++++++++++ ...ab.Context.Trace.WebAPI.TestProject.csproj | 23 +++++++ .../TestServer.cs | 19 +++++ .../UnitTest1.cs | 52 ++++++++++++++ .../Usings.cs | 1 + .../Lab.Context.Trace.WebAPI/Program.cs | 12 ++-- Trace/Lab.Context.Trace/Lab.Context.Trace.sln | 12 ++++ 8 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.ConsoleApp/Lab.Context.Trace.ConsoleApp.csproj create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.ConsoleApp/Program.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/Lab.Context.Trace.WebAPI.TestProject.csproj create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/TestServer.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/UnitTest1.cs create mode 100644 Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/Usings.cs diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.ConsoleApp/Lab.Context.Trace.ConsoleApp.csproj b/Trace/Lab.Context.Trace/Lab.Context.Trace.ConsoleApp/Lab.Context.Trace.ConsoleApp.csproj new file mode 100644 index 00000000..2b14c817 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.ConsoleApp/Lab.Context.Trace.ConsoleApp.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + enable + enable + + + diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.ConsoleApp/Program.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.ConsoleApp/Program.cs new file mode 100644 index 00000000..9df00e03 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.ConsoleApp/Program.cs @@ -0,0 +1,69 @@ +// See https://aka.ms/new-console-template for more information + +using System.Diagnostics; + +Console.WriteLine("Starting request..."); + +using (var httpClient = new HttpClient()) +{ + var url = $"https://localhost:7004/demo"; + + var tasks = new List>(); + for (var i = 0; i < 2; i++) + { + tasks.Add(SendAsync(httpClient, url)); + } + + var data = await Task.WhenAll(tasks); + + // 列出重複的 trace id + var duplicateData = data.GroupBy(p => p?.TraceId) + .Where(p => p?.Count() > 1) + .Where(p => string.IsNullOrWhiteSpace(p?.Key) == false) + .Select(p => p?.Key); + + var items = new List(); + + foreach (var item in duplicateData) + { + if (string.IsNullOrWhiteSpace(item)) + { + continue; + } + + items.Add(item); + Console.WriteLine(item); + } + + if (items.Any()) + { + Debug.Fail("有重複的 trace id"); + } + + Console.WriteLine("All requests completed."); +} + +static async Task SendAsync(HttpClient httpClient, string url) +{ + try + { + var response = await httpClient.GetAsync(url); + response.Headers.TryGetValues("x-trace-id", out var traceIds); + var traceId = traceIds.FirstOrDefault(); + return new Data() + { + TraceId = traceId + }; + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + + return null; +} + +class Data +{ + public string TraceId { get; set; } +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/Lab.Context.Trace.WebAPI.TestProject.csproj b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/Lab.Context.Trace.WebAPI.TestProject.csproj new file mode 100644 index 00000000..232b89c7 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/Lab.Context.Trace.WebAPI.TestProject.csproj @@ -0,0 +1,23 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/TestServer.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/TestServer.cs new file mode 100644 index 00000000..be937d60 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/TestServer.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestPlatform.TestHost; + +namespace Lab.Context.Trace.WebAPI.TestProject; + +public class TestServer : WebApplicationFactory +{ + private void ConfigureServices(IServiceCollection services) + { + + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureServices(this.ConfigureServices); + } +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/UnitTest1.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/UnitTest1.cs new file mode 100644 index 00000000..ca891d21 --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/UnitTest1.cs @@ -0,0 +1,52 @@ +namespace Lab.Context.Trace.WebAPI.TestProject; + +class Data +{ + public string? TraceId { get; set; } +} + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public async Task TestMethod1() + { + var server = new TestServer(); + var httpClient = server.CreateDefaultClient(); + + var url = "https://localhost:7004/demo"; + + var tasks = new List>(); + for (var i = 0; i < 100; i++) + { + tasks.Add(SendAsync(httpClient, url)); + } + + var data = await Task.WhenAll(tasks); + + var duplicateData = data.GroupBy(p => p.TraceId) + .Where(p => p.Count() > 1) + .Select(p => p.Key); + + foreach (var item in duplicateData) + { + Console.WriteLine(item); + } + + if (duplicateData.Any()) + { + Assert.Fail("有重複的 trace id"); + } + } + + static async Task SendAsync(HttpClient httpClient, string url) + { + var response = await httpClient.GetAsync(url); + response.Headers.TryGetValues("x-trace-id", out var traceIds); + var traceId = traceIds.FirstOrDefault(); + return new Data() + { + TraceId = traceId + }; + } +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/Usings.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs index bfbc74b3..e5704161 100644 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI/Program.cs @@ -36,9 +36,9 @@ // builder.Services.AddScoped>(p => p.GetService>()); // builder.Services.AddScoped>(p => p.GetService>()); - builder.Services.AddSingleton>(); - builder.Services.AddSingleton>(p => p.GetService>()); - builder.Services.AddSingleton>(p => p.GetService>()); + builder.Services.AddSingleton>(); + builder.Services.AddSingleton>(p => p.GetService>()); + builder.Services.AddSingleton>(p => p.GetService>()); var app = builder.Build(); @@ -49,11 +49,11 @@ app.UseSwaggerUI(); } + app.UseSerilogRequestLogging(); app.UseHttpsRedirection(); app.UseAuthorization(); app.UseMiddleware(); - app.MapControllers(); app.Run(); return 0; @@ -66,4 +66,6 @@ finally { Log.CloseAndFlush(); -} \ No newline at end of file +} + +public partial class Program { } diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.sln b/Trace/Lab.Context.Trace/Lab.Context.Trace.sln index d35ea5dc..d91ea657 100644 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace.sln +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.sln @@ -9,6 +9,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution docker-compose.yml = docker-compose.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Context.Trace.WebAPI.TestProject", "Lab.Context.Trace.WebAPI.TestProject\Lab.Context.Trace.WebAPI.TestProject.csproj", "{072B154D-149F-416C-AC1A-E009FED7706E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Context.Trace.ConsoleApp", "Lab.Context.Trace.ConsoleApp\Lab.Context.Trace.ConsoleApp.csproj", "{395822D5-7405-41B8-AC00-9106BF00EB76}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,5 +27,13 @@ Global {04987A3C-032F-49CB-B59A-80D188BF1372}.Debug|Any CPU.Build.0 = Debug|Any CPU {04987A3C-032F-49CB-B59A-80D188BF1372}.Release|Any CPU.ActiveCfg = Release|Any CPU {04987A3C-032F-49CB-B59A-80D188BF1372}.Release|Any CPU.Build.0 = Release|Any CPU + {072B154D-149F-416C-AC1A-E009FED7706E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {072B154D-149F-416C-AC1A-E009FED7706E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {072B154D-149F-416C-AC1A-E009FED7706E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {072B154D-149F-416C-AC1A-E009FED7706E}.Release|Any CPU.Build.0 = Release|Any CPU + {395822D5-7405-41B8-AC00-9106BF00EB76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {395822D5-7405-41B8-AC00-9106BF00EB76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {395822D5-7405-41B8-AC00-9106BF00EB76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {395822D5-7405-41B8-AC00-9106BF00EB76}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 2cb434e62e5b55d6686c8670c92f035394a56a71 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 8 Oct 2023 15:17:20 +0800 Subject: [PATCH 343/424] refactor --- .../Lab.Context.Trace.WebAPI.TestProject/UnitTest1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/UnitTest1.cs b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/UnitTest1.cs index ca891d21..642c4bbc 100644 --- a/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/UnitTest1.cs +++ b/Trace/Lab.Context.Trace/Lab.Context.Trace.WebAPI.TestProject/UnitTest1.cs @@ -17,7 +17,7 @@ public async Task TestMethod1() var url = "https://localhost:7004/demo"; var tasks = new List>(); - for (var i = 0; i < 100; i++) + for (var i = 0; i < 10000; i++) { tasks.Add(SendAsync(httpClient, url)); } From fef758c746ace5f42e2e291182cc52250ddda795 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 22 Oct 2023 21:54:28 +0800 Subject: [PATCH 344/424] refactor --- DI/Lab.MsDI/WinFormNet48/Form1.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DI/Lab.MsDI/WinFormNet48/Form1.cs b/DI/Lab.MsDI/WinFormNet48/Form1.cs index 3c3e1406..f0023ace 100644 --- a/DI/Lab.MsDI/WinFormNet48/Form1.cs +++ b/DI/Lab.MsDI/WinFormNet48/Form1.cs @@ -6,6 +6,7 @@ namespace WinFormNet48 { public partial class Form1 : Form { + int _counter = 1; public Form1() { this.InitializeComponent(); @@ -15,7 +16,9 @@ private void button1_Click(object sender, EventArgs e) { var serviceProvider = DependencyInjectionConfig.ServiceProvider; var work = serviceProvider.GetRequiredService(); - Console.WriteLine(work.Get()); + Console.WriteLine($"{this._counter}=>\r\n"+work.Get()); + + this._counter++; } } } \ No newline at end of file From 9698c1075a6527afc7ed36a67e32ba9704d4dd42 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 30 Oct 2023 10:12:02 +0800 Subject: [PATCH 345/424] add project --- Shnapshot/Lab.Snapshot/.dockerignore | 25 ++++ Shnapshot/Lab.Snapshot/.gitlab-ci.yml | 18 +++ .../Lab.Snapshot/Lab.Snapshot.DB/Account.cs | 8 ++ .../Lab.Snapshot.DB/Lab.Snapshot.DB.csproj | 13 ++ .../Lab.Snapshot.DB/MemberDataEntity.cs | 35 +++++ .../Lab.Snapshot.DB/MemberDbContext.cs | 69 ++++++++++ .../Lab.Snapshot/Lab.Snapshot.DB/Profile.cs | 8 ++ .../Lab.Snapshot.DB/SnapshotDataEntity.cs | 24 ++++ .../Lab.Snapshot.Test/EnvironmentNames.cs | 6 + .../Lab.Snapshot.Test.csproj | 32 +++++ .../Lab.Snapshot.Test/NpgsqlGenerateScript.cs | 28 ++++ .../Lab.Snapshot.Test/ServiceConfiguration.cs | 24 ++++ .../Lab.Snapshot.Test/TestAssistant.cs | 13 ++ .../Lab.Snapshot.Test/TestHook.cs | 36 +++++ .../Lab.Snapshot.Test/UnitTest1.cs | 128 ++++++++++++++++++ .../Lab.Snapshot/Lab.Snapshot.Test/Usings.cs | 1 + Shnapshot/Lab.Snapshot/Lab.Snapshot.sln | 22 +++ 17 files changed, 490 insertions(+) create mode 100644 Shnapshot/Lab.Snapshot/.dockerignore create mode 100644 Shnapshot/Lab.Snapshot/.gitlab-ci.yml create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Account.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Lab.Snapshot.DB.csproj create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDataEntity.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContext.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Profile.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/SnapshotDataEntity.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/EnvironmentNames.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Lab.Snapshot.Test.csproj create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/NpgsqlGenerateScript.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/ServiceConfiguration.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestAssistant.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestHook.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Usings.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.sln diff --git a/Shnapshot/Lab.Snapshot/.dockerignore b/Shnapshot/Lab.Snapshot/.dockerignore new file mode 100644 index 00000000..cd967fc3 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/.gitlab-ci.yml b/Shnapshot/Lab.Snapshot/.gitlab-ci.yml new file mode 100644 index 00000000..a269c045 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/.gitlab-ci.yml @@ -0,0 +1,18 @@ + stages: + - build + - test + + job1: + stage: build + script: + - echo "This job runs in the build stage." + + last-job: + stage: .post + script: + - echo "This job runs in the .post stage, after all other stages." + + job2: + stage: test + script: + - echo "This job runs in the test stage." \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Account.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Account.cs new file mode 100644 index 00000000..2a387ab1 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Account.cs @@ -0,0 +1,8 @@ +namespace Lab.Snapshot.DB; + +public class Account +{ + public string Id { get; set; } + + public string Type { get; set; } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Lab.Snapshot.DB.csproj b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Lab.Snapshot.DB.csproj new file mode 100644 index 00000000..1bb7ec62 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Lab.Snapshot.DB.csproj @@ -0,0 +1,13 @@ + + + + net7.0 + enable + enable + + + + + + + diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDataEntity.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDataEntity.cs new file mode 100644 index 00000000..3e3b6459 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDataEntity.cs @@ -0,0 +1,35 @@ +namespace Lab.Snapshot.DB; + +public class MemberDataEntity +{ + public string Id { get; set; } + + public Profile Profile { get; set; } + + public List Accounts { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset UpdatedAt { get; set; } + + public string UpdatedBy { get; set; } + + public int Version { get; set; } + + public Dictionary ToDictionary() + { + return new Dictionary + { + { nameof(this.Id), this.Id }, + { nameof(this.Profile), this.Profile }, + { nameof(this.Accounts), this.Accounts }, + { nameof(this.CreatedAt), this.CreatedAt }, + { nameof(this.CreatedBy), this.CreatedBy }, + { nameof(this.UpdatedAt), this.UpdatedAt }, + { nameof(this.UpdatedBy), this.UpdatedBy }, + { nameof(this.Version), this.Version } + }; + } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContext.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContext.cs new file mode 100644 index 00000000..3ac711c7 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContext.cs @@ -0,0 +1,69 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.Extensions.Logging; + +namespace Lab.Snapshot.DB; + +public class MemberDbContext : DbContext +{ + public DbSet Members { get; set; } + + public DbSet Snapshots { get; set; } + + public MemberDbContext(DbContextOptions options) + : base(options) + { + } + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.HasSequence("member_collection_seqno") + .StartsAt(1) + .IncrementsBy(1); + + modelBuilder.ApplyConfiguration(new MemberConfiguration()); + } + + internal class MemberConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Member"); + builder.HasKey(x => x.Id); + builder.Property(x => x.Accounts).HasColumnType("jsonb").IsRequired(); + builder.Property(x => x.Profile).HasColumnType("jsonb").IsRequired(false); + builder.Property(x => x.CreatedAt).HasColumnType("timestamp with time zone").IsRequired(); + builder.Property(x => x.CreatedBy).HasMaxLength(50).IsRequired(); + builder.Property(x => x.UpdatedAt).HasColumnType("timestamp with time zone").IsRequired(); + builder.Property(x => x.UpdatedBy).HasMaxLength(50).IsRequired(); + builder.Property(p => p.Version).IsRequired(); + + // indexes + builder.HasIndex(p => new { p.Id, p.Version }).IsUnique(); + builder.HasIndex(x => x.Accounts).HasMethod("GIN"); + } + } + + internal class SnapshotConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Snapshot"); + builder.HasKey(x => x.Id); + builder.Property(x => x.Data).HasColumnType("jsonb").IsRequired(); + builder.Property(x => x.Type).IsRequired(); + builder.Property(x => x.CreatedAt).HasColumnType("timestamp with time zone").IsRequired(); + builder.Property(x => x.CreatedBy).HasMaxLength(50).IsRequired(); + builder.Property(x => x.UpdatedAt).HasColumnType("timestamp with time zone").IsRequired(); + builder.Property(x => x.UpdatedBy).HasMaxLength(50).IsRequired(); + builder.Property(p => p.Version).IsRequired(); + + // indexes + builder.HasIndex(p => new { p.Id, p.Version }).IsUnique(); + builder.HasIndex(x => x.Data).HasMethod("GIN"); + } + } + +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Profile.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Profile.cs new file mode 100644 index 00000000..2877b8e7 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Profile.cs @@ -0,0 +1,8 @@ +namespace Lab.Snapshot.DB; + +public class Profile +{ + public int Age { get; set; } + + public string Name { get; set; } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/SnapshotDataEntity.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/SnapshotDataEntity.cs new file mode 100644 index 00000000..35959a82 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/SnapshotDataEntity.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json; + +namespace Lab.Snapshot.DB; + +public class SnapshotDataEntity +{ + public string Id { get; set; } + + public string Type { get; set; } + + [Column(TypeName = "jsonb")] + public Dictionary Data { get; set; } = new(); + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset UpdatedAt { get; set; } + + public string UpdatedBy { get; set; } + + public int Version { get; set; } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/EnvironmentNames.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/EnvironmentNames.cs new file mode 100644 index 00000000..b5780613 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/EnvironmentNames.cs @@ -0,0 +1,6 @@ +namespace Lab.Snapshot.Test; + +class EnvironmentNames +{ + public const string DbConnectionString = "DB_CONNECTION_STRING"; +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Lab.Snapshot.Test.csproj b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Lab.Snapshot.Test.csproj new file mode 100644 index 00000000..ece6fb34 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Lab.Snapshot.Test.csproj @@ -0,0 +1,32 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/NpgsqlGenerateScript.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/NpgsqlGenerateScript.cs new file mode 100644 index 00000000..21bc4797 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/NpgsqlGenerateScript.cs @@ -0,0 +1,28 @@ +namespace Lab.Snapshot.Test; + +internal class NpgsqlGenerateScript +{ + public static string ClearAllRecord() + { + return @" +DO $$ +DECLARE row RECORD; +BEGIN + FOR row IN SELECT table_name + FROM information_schema.tables + WHERE table_type='BASE TABLE' + AND table_schema='public' + AND table_name NOT IN ('admins', 'admin_roles', '__EFMigrationsHistory') + LOOP + EXECUTE format('TRUNCATE TABLE %I CONTINUE IDENTITY CASCADE;', row.table_name); + END LOOP; +END; +$$; +"; + } + + public static string ReseedMemberCollectionSeq() + { + return "ALTER SEQUENCE member_collection_seqno RESTART WITH 1;"; + } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/ServiceConfiguration.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/ServiceConfiguration.cs new file mode 100644 index 00000000..81b7efdd --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/ServiceConfiguration.cs @@ -0,0 +1,24 @@ +using Lab.Snapshot.DB; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.Snapshot.Test; + +public class ServiceConfiguration +{ + public static void ConfigDb(IServiceCollection services) + { + services.AddSingleton(p => { return LoggerFactory.Create(builder => { builder.AddConsole(); }); }); + services.AddDbContextFactory((p, options) => + { + var connectionString = Environment.GetEnvironmentVariable(EnvironmentNames.DbConnectionString); + options.UseNpgsql(connectionString, + builder => builder.EnableRetryOnFailure( + 10, + TimeSpan.FromSeconds(30), + new List { "57P01" })) + ; + }); + } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestAssistant.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestAssistant.cs new file mode 100644 index 00000000..24aeb592 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestAssistant.cs @@ -0,0 +1,13 @@ +using Npgsql; + +namespace Lab.Snapshot.Test; + +public class TestAssistant +{ + public const string DbConnectionString = + "Host=localhost;Port=5432;Database=member_integration_test;Username=postgres;Password=guest"; + + public static DateTimeOffset Now { get; set; } = DateTimeOffset.UtcNow; + + public static string UserId { get; set; } = "@@TestUser@@"; +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestHook.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestHook.cs new file mode 100644 index 00000000..298e72a2 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestHook.cs @@ -0,0 +1,36 @@ +using Lab.Snapshot.DB; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Lab.Snapshot.Test; + +[TestClass] +public class TestHook +{ + private static readonly IServiceCollection s_services = new ServiceCollection(); + static IServiceProvider s_serviceProvider; + + [AssemblyInitialize] + public static void AssemblyInitialize(TestContext context) + { + Console.WriteLine("AssemblyInitialize"); + Environment.SetEnvironmentVariable(EnvironmentNames.DbConnectionString, TestAssistant.DbConnectionString); + ServiceConfiguration.ConfigDb(s_services); + s_serviceProvider = s_services.BuildServiceProvider(); + using var dbContext = s_serviceProvider.GetService>().CreateDbContext(); + + // drop and create database + dbContext.Database.EnsureDeleted(); + dbContext.Database.EnsureCreated(); + } + + [AssemblyCleanup] + public static void AssemblyCleanup() + { + Console.WriteLine("AssemblyCleanup"); + + // drop database + using var dbContext = s_serviceProvider.GetService>().CreateDbContext(); + dbContext.Database.EnsureDeleted(); + } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs new file mode 100644 index 00000000..9e5d09e1 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs @@ -0,0 +1,128 @@ +using Lab.Snapshot.DB; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Lab.Snapshot.Test; + +[TestClass] +public class UnitTest1 +{ + private IServiceProvider _serviceProvider; + + IDbContextFactory DbContextFactory => + _serviceProvider.GetService>(); + + [TestInitialize] + public void TestInitialize() + { + Console.WriteLine("TestInitialize"); + if (this._serviceProvider == null) + { + var services = new ServiceCollection(); + ServiceConfiguration.ConfigDb(services); + this._serviceProvider = services.BuildServiceProvider(); + } + + this.CleanAllRecord(this.DbContextFactory.CreateDbContext()); + } + + [TestCleanup] + public void TestCleanup() + { + Console.WriteLine("TestCleanup"); + this.CleanAllRecord(this.DbContextFactory.CreateDbContext()); + } + + /// + /// 刪除資料庫所有的資料 + /// + /// + private void CleanAllRecord(DbContext dbContext) + { + dbContext.Database.ExecuteSqlRaw(NpgsqlGenerateScript.ClearAllRecord()); + } + + [TestMethod] + public async Task 更新資料產生差異快照() + { + await this.InsertAsync(); + + var now = TestAssistant.Now; + var userId = TestAssistant.UserId; + var search = "[{\"Id\": \"yao\"}]"; + + await using var dbContext = await this.DbContextFactory.CreateDbContextAsync(); + + // var member = dbContext.Members + // .Where(s => EF.Functions.JsonContains(s.Accounts, search)) + // .AsNoTracking() + // .FirstOrDefault(); + var queryable = from member in dbContext.Members + join snapshot in dbContext.Snapshots + on + new + { + member.Id, + member.Version + } + equals + new + { + snapshot.Id, + snapshot.Version + } + where EF.Functions.JsonContains(member.Accounts, search) + select new { member, snapshot }; + var data = await queryable.AsNoTracking().FirstOrDefaultAsync(); + var profile = new Profile() + { + Age = 20 + }; + } + + private async Task InsertAsync() + { + await using var dbContext = await this.DbContextFactory.CreateDbContextAsync(); + + var now = TestAssistant.Now; + var userId = TestAssistant.UserId; + + var member = new MemberDataEntity + { + Id = "1", + Profile = new Profile + { + Age = 18, + Name = "yao-chang" + }, + Accounts = new List() + { + new() + { + Id = "yao", + Type = "VIP" + } + }, + CreatedAt = now, + CreatedBy = userId, + UpdatedAt = now, + UpdatedBy = userId, + Version = 1 + }; + dbContext.Members.Add(member); + + dbContext.Snapshots.Add(new SnapshotDataEntity + { + Id = member.Id, + Type = typeof(MemberDataEntity).ToString(), + Data = member.ToDictionary(), + CreatedAt = now, + CreatedBy = userId, + UpdatedAt = now, + UpdatedBy = userId, + Version = member.Version + }); + return await dbContext.SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Usings.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Usings.cs new file mode 100644 index 00000000..ab67c7ea --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.sln b/Shnapshot/Lab.Snapshot/Lab.Snapshot.sln new file mode 100644 index 00000000..adc2da5d --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Snapshot.Test", "Lab.Snapshot.Test\Lab.Snapshot.Test.csproj", "{BEA4D8AA-807A-46FE-A660-116F3D5F3BC5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Snapshot.DB", "Lab.Snapshot.DB\Lab.Snapshot.DB.csproj", "{472E967C-E725-494F-B906-A60F8B206895}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BEA4D8AA-807A-46FE-A660-116F3D5F3BC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BEA4D8AA-807A-46FE-A660-116F3D5F3BC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BEA4D8AA-807A-46FE-A660-116F3D5F3BC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BEA4D8AA-807A-46FE-A660-116F3D5F3BC5}.Release|Any CPU.Build.0 = Release|Any CPU + {472E967C-E725-494F-B906-A60F8B206895}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {472E967C-E725-494F-B906-A60F8B206895}.Debug|Any CPU.Build.0 = Debug|Any CPU + {472E967C-E725-494F-B906-A60F8B206895}.Release|Any CPU.ActiveCfg = Release|Any CPU + {472E967C-E725-494F-B906-A60F8B206895}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From cb3691defe4685b941518060d45a77138ca3c9e1 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 2 Nov 2023 00:38:39 +0800 Subject: [PATCH 346/424] =?UTF-8?q?=E5=BE=9E=E8=B3=87=E6=96=99=E5=BA=AB?= =?UTF-8?q?=E9=82=84=E5=8E=9F=E5=BF=AB=E7=85=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Lab.Snapshot.DB/Lab.Snapshot.DB.csproj | 2 +- .../Lab.Snapshot.DB/MemberDbContext.cs | 39 +- .../Lab.Snapshot.DB/SnapshotDataEntity.cs | 11 +- .../Lab.Snapshot.Test/DataFormat.cs | 7 + .../Lab.Snapshot.Test.csproj | 20 +- .../Lab.Snapshot.Test/TestHook.cs | 3 +- .../Lab.Snapshot.Test/UnitTest1.cs | 601 +++++++++++++++++- 7 files changed, 641 insertions(+), 42 deletions(-) create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/DataFormat.cs diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Lab.Snapshot.DB.csproj b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Lab.Snapshot.DB.csproj index 1bb7ec62..987967e8 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Lab.Snapshot.DB.csproj +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Lab.Snapshot.DB.csproj @@ -7,7 +7,7 @@ - + diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContext.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContext.cs index 3ac711c7..a0f3887d 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContext.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContext.cs @@ -15,6 +15,15 @@ public MemberDbContext(DbContextOptions options) : base(options) { } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + // optionsBuilder.UseExceptionProcessor(); + optionsBuilder.ConfigureWarnings(b => + b.Log((CoreEventId.SaveChangesFailed, LogLevel.Warning), + (RelationalEventId.CommandError, LogLevel.Warning))); + } + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -24,6 +33,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .IncrementsBy(1); modelBuilder.ApplyConfiguration(new MemberConfiguration()); + modelBuilder.ApplyConfiguration(new SnapshotConfiguration()); } internal class MemberConfiguration : IEntityTypeConfiguration @@ -31,39 +41,42 @@ internal class MemberConfiguration : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { builder.ToTable("Member"); - builder.HasKey(x => x.Id); - builder.Property(x => x.Accounts).HasColumnType("jsonb").IsRequired(); - builder.Property(x => x.Profile).HasColumnType("jsonb").IsRequired(false); - builder.Property(x => x.CreatedAt).HasColumnType("timestamp with time zone").IsRequired(); - builder.Property(x => x.CreatedBy).HasMaxLength(50).IsRequired(); + builder.HasKey(p => new + { + p.Id + }); + builder.Property(p => p.Accounts).HasColumnType("jsonb").IsRequired(); + builder.Property(p => p.Profile).HasColumnType("jsonb").IsRequired(false); + builder.Property(p => p.CreatedAt).HasColumnType("timestamp with time zone").IsRequired(); + builder.Property(p => p.CreatedBy).HasMaxLength(50).IsRequired(); builder.Property(x => x.UpdatedAt).HasColumnType("timestamp with time zone").IsRequired(); builder.Property(x => x.UpdatedBy).HasMaxLength(50).IsRequired(); builder.Property(p => p.Version).IsRequired(); // indexes - builder.HasIndex(p => new { p.Id, p.Version }).IsUnique(); builder.HasIndex(x => x.Accounts).HasMethod("GIN"); } } - + internal class SnapshotConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("Snapshot"); - builder.HasKey(x => x.Id); + builder.HasKey(x => new + { + x.Id, + x.Version + }); builder.Property(x => x.Data).HasColumnType("jsonb").IsRequired(); - builder.Property(x => x.Type).IsRequired(); + builder.Property(x => x.DataFormat).IsRequired(); + builder.Property(x => x.DataType).IsRequired(); builder.Property(x => x.CreatedAt).HasColumnType("timestamp with time zone").IsRequired(); builder.Property(x => x.CreatedBy).HasMaxLength(50).IsRequired(); - builder.Property(x => x.UpdatedAt).HasColumnType("timestamp with time zone").IsRequired(); - builder.Property(x => x.UpdatedBy).HasMaxLength(50).IsRequired(); builder.Property(p => p.Version).IsRequired(); // indexes - builder.HasIndex(p => new { p.Id, p.Version }).IsUnique(); builder.HasIndex(x => x.Data).HasMethod("GIN"); } } - } \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/SnapshotDataEntity.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/SnapshotDataEntity.cs index 35959a82..76a263ca 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/SnapshotDataEntity.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/SnapshotDataEntity.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json; +using System.Text.Json.Nodes; namespace Lab.Snapshot.DB; @@ -7,18 +8,16 @@ public class SnapshotDataEntity { public string Id { get; set; } - public string Type { get; set; } + public string DataType { get; set; } [Column(TypeName = "jsonb")] - public Dictionary Data { get; set; } = new(); + public JsonNode Data { get; set; } + + public string DataFormat { get; set; } public DateTimeOffset CreatedAt { get; set; } public string CreatedBy { get; set; } - public DateTimeOffset UpdatedAt { get; set; } - - public string UpdatedBy { get; set; } - public int Version { get; set; } } \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/DataFormat.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/DataFormat.cs new file mode 100644 index 00000000..6a9814ca --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/DataFormat.cs @@ -0,0 +1,7 @@ +namespace Lab.Snapshot.Test; + +public enum DataFormat +{ + Full, + Diff +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Lab.Snapshot.Test.csproj b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Lab.Snapshot.Test.csproj index ece6fb34..484eb821 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Lab.Snapshot.Test.csproj +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Lab.Snapshot.Test.csproj @@ -9,24 +9,28 @@ - + + + + - - - - - + + + + + + - + - + diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestHook.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestHook.cs index 298e72a2..f05f6bfb 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestHook.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestHook.cs @@ -31,6 +31,7 @@ public static void AssemblyCleanup() // drop database using var dbContext = s_serviceProvider.GetService>().CreateDbContext(); - dbContext.Database.EnsureDeleted(); + + // dbContext.Database.EnsureDeleted(); } } \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs index 9e5d09e1..0fe32a68 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs @@ -1,7 +1,21 @@ +using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.JsonDiffPatch; +using System.Text.Json.JsonDiffPatch.Diffs.Formatters; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using FluentAssertions; +using JsonDiffPatchDotNet; +using JsonDiffPatchDotNet.Formatters.JsonPatch; using Lab.Snapshot.DB; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using JsonConverter = System.Text.Json.Serialization.JsonConverter; +using JsonSerializer = System.Text.Json.JsonSerializer; namespace Lab.Snapshot.Test; @@ -13,6 +27,19 @@ public class UnitTest1 IDbContextFactory DbContextFactory => _serviceProvider.GetService>(); + static JsonSerializerOptions Options = new() + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Converters = + { + new JsonStringEnumConverter() + } + }; + [TestInitialize] public void TestInitialize() { @@ -40,13 +67,558 @@ public void TestCleanup() /// private void CleanAllRecord(DbContext dbContext) { - dbContext.Database.ExecuteSqlRaw(NpgsqlGenerateScript.ClearAllRecord()); + // dbContext.Database.ExecuteSqlRaw(NpgsqlGenerateScript.ClearAllRecord()); + } + + [TestMethod] + public async Task Diff() + { + var left = """ + { + "id": 100, + "revision": 5, + "items": [ + "car", + "bus" + ], + "tagline": "I can't do it. This text is too long for me to handle! Please help me JsonDiffPatch!", + "author": "wbish" + } + """; + + var right = """ + { + "id": 100, + "revision": 6, + "items": [ + "bike", + "bus", + "car" + ], + "tagline": "I can do it. This text is not too long. Thanks JsonDiffPatch!", + "author": { + "first": "w", + "last": "bish" + } + } + """; + + var jdp = new JsonDiffPatch(); + var output = jdp.Diff(left, right); + var formatter = new JsonDeltaFormatter(); + var operations = formatter.Format(output); + var patch = new JsonDiffPatch().Diff(left, right); + } + + [TestMethod] + public async Task Patch() + { + var left = JObject.Parse("{ \"name\": \"Justin\" }"); + var right = JObject.Parse("{ \"name\" : \"John\", \"age\": 34 }"); + var patch = new JsonDiffPatch().Diff(left, right); + var formatter = new JsonDeltaFormatter(); + var operations = formatter.Format(patch); + var jToken = new JsonDiffPatch().Patch(left, patch); + } + + [TestMethod] + public async Task Newtonsoft_DiffPatch() + { + var oldMember = new MemberDataEntity + { + Id = "1", + Profile = new Profile + { + Age = 19, + Name = "yao-chang" + }, + Accounts = new List + { + new() + { + Id = "yao", + Type = "VIP" + } + }, + CreatedAt = DateTimeOffset.UtcNow, + CreatedBy = "test", + UpdatedAt = DateTimeOffset.UtcNow, + UpdatedBy = "test", + Version = 1 + }; + + var newMember = new MemberDataEntity + { + Id = "1", + Profile = new Profile + { + Age = 19, + Name = "小章" + }, + Accounts = new List + { + new() + { + Id = "yao", + Type = "VIP" + }, + new() + { + Id = "yao1", + Type = "VIP1" + } + }, + CreatedAt = DateTimeOffset.UtcNow, + CreatedBy = "test", + UpdatedAt = DateTimeOffset.UtcNow, + UpdatedBy = "test", + Version = 2 + }; + + var oldData = JsonConvert.SerializeObject(oldMember); + var newData = JsonConvert.SerializeObject(newMember); + var diff = new JsonDiffPatch().Diff(oldData, newData); + var patchData = new JsonDiffPatch().Patch(oldData, diff); + + var actual = JsonConvert.DeserializeObject(patchData); + actual.Should().BeEquivalentTo(newMember); + } + + [TestMethod] + public async Task SystemTextJson_DiffPatch() + { + var oldMember = new MemberDataEntity + { + Id = "1", + Profile = new Profile + { + Age = 19, + Name = "yao-chang" + }, + Accounts = new List + { + new() + { + Id = "yao", + Type = "VIP" + } + }, + CreatedAt = DateTimeOffset.UtcNow, + CreatedBy = "test", + UpdatedAt = DateTimeOffset.UtcNow, + UpdatedBy = "test", + Version = 1 + }; + + var newMember = new MemberDataEntity + { + Id = "1", + Profile = new Profile + { + Age = 19, + Name = "小章" + }, + Accounts = new List + { + new() + { + Id = "yao", + Type = "VIP" + }, + new() + { + Id = "yao1", + Type = "VIP1" + } + }, + CreatedAt = DateTimeOffset.UtcNow, + CreatedBy = "test", + UpdatedAt = DateTimeOffset.UtcNow, + UpdatedBy = "test", + Version = 2 + }; + + var oldData = JsonSerializer.Serialize(oldMember, Options); + var newData = JsonSerializer.Serialize(newMember, Options); + + var diff = JsonDiffPatcher.Diff(oldData, newData, new JsonDiffOptions + { + JsonElementComparison = JsonElementComparison.Semantic + }); + var diff1 = JsonDiffPatcher.Diff(oldData, newData); + var diff2 = JsonDiffPatcher.Diff(oldData, newData, new JsonPatchDeltaFormatter(), new JsonDiffOptions + { + JsonElementComparison = JsonElementComparison.Semantic + }); + var result = JsonNode.Parse(oldData); + JsonDiffPatcher.Patch(ref result, diff); + var actual = result.Deserialize(Options); + actual.Should().BeEquivalentTo(newMember); + } + + [TestMethod] + public async Task SystemTextJson_DiffPatch_1() + { + var original = """ + { + "id": "1", + "profile": { + "age": 18, + "name": "yao-chang" + }, + "version": 1, + "accounts": [ + { + "id": "yao", + "type": "VIP" + } + ], + "createdAt": "2023-11-01T16:25:29.5966704+00:00", + "createdBy": "@@TestUser@@", + "updatedAt": "2023-11-01T16:25:29.5966704+00:00", + "updatedBy": "@@TestUser@@" + } + """; + var diffText = """ + { + "profile": { + "age": [ + 18, + 20 + ], + "name": [ + "yao-chang", + "jordan" + ] + }, + "version": [ + 1, + 0 + ], + "accounts": { + "1": [ + { + "id": "yao1", + "type": "VIP1" + } + ], + "2": [ + { + "id": "jordan1", + "type": "VVVIP" + } + ], + "_t": "a" + }, + "createdAt": [ + "2023-11-01T16:25:29.59667+00:00", + "2023-11-01T16:25:30.2735717+00:00" + ], + "createdBy": [ + "@@TestUser@@", + "test" + ], + "updatedAt": [ + "2023-11-01T16:25:29.59667+00:00", + "2023-11-01T16:25:30.2735718+00:00" + ], + "updatedBy": [ + "@@TestUser@@", + "test" + ] + } + """; + var result = JsonNode.Parse(original); + var diff = JsonNode.Parse(diffText); + JsonDiffPatcher.Patch(ref result, diff); + var actual = result.Deserialize(Options); + } + + [TestMethod] + public async Task SystemTextJson_RestoreInMemory() + { + var oldMember = new MemberDataEntity + { + Id = "1", + Profile = new Profile + { + Age = 19, + Name = "小章" + }, + Accounts = new List + { + new() + { + Id = "yao", + Type = "VIP" + }, + new() + { + Id = "yao1", + Type = "VIP1" + } + }, + CreatedAt = DateTimeOffset.UtcNow, + CreatedBy = "test", + UpdatedAt = DateTimeOffset.UtcNow, + UpdatedBy = "test", + Version = 2 + }; + + var newMember = new MemberDataEntity + { + Id = "1", + Profile = new Profile + { + Age = 19, + Name = "小章" + }, + Accounts = new List + { + new() + { + Id = "yao1", + Type = "VIP1" + }, + new() + { + Id = "yao", + Type = "VIP" + }, + }, + CreatedAt = DateTimeOffset.UtcNow, + CreatedBy = "test", + UpdatedAt = DateTimeOffset.UtcNow, + UpdatedBy = "test", + Version = 2 + }; + + var oldData = JsonSerializer.Serialize(oldMember, Options); + var newData = JsonSerializer.Serialize(newMember, Options); + + var diff = JsonDiffPatcher.Diff(oldData, newData, new JsonDiffOptions + { + JsonElementComparison = JsonElementComparison.Semantic + }); + var diff1 = JsonDiffPatcher.Diff(oldData, newData); + var diff2 = JsonDiffPatcher.Diff(oldData, newData, new JsonPatchDeltaFormatter(), new JsonDiffOptions + { + JsonElementComparison = JsonElementComparison.Semantic + }); + var result = JsonNode.Parse(oldData); + JsonDiffPatcher.Patch(ref result, diff); + var actual = result.Deserialize(Options); + actual.Should().BeEquivalentTo(newMember); + } + + [TestMethod] + public async Task SystemTextJson_RestoreInDb() + { + await this.InsertOrGetAsync(); + await this.Update(GenerateMember(20, "jordan", new Account + { + Id = "jordan1", + Type = "VVVIP" + })); + await this.Update(GenerateMember(18, "yao", null)); + await this.Update(GenerateMember(32, "yao-chang", null)); + + await using var dbContext = await this.DbContextFactory.CreateDbContextAsync(); + var snapshots = await dbContext.Snapshots + .Where(p => p.Id == "1") + .OrderBy(p => p.Version) + .AsNoTracking() + .ToListAsync(); + + JsonNode full = null; + foreach (var snapshot in snapshots) + { + if (snapshot.Version==1) + { + full = snapshot.Data; + continue; + } + JsonDiffPatcher.Patch(ref full, snapshot.Data); + + // var diff = new JsonDiffPatch().Diff(oldData, newData); + // var patchData = new JsonDiffPatch().Patch(oldData, diff); + } + + var jsonString = full.ToJsonString(); + } + + private static string ToJsonString(JsonDocument doc) + { + using var stream = new MemoryStream(); + var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }); + doc.WriteTo(writer); + writer.Flush(); + return Encoding.UTF8.GetString(stream.ToArray()); + } + + private async Task Update(MemberDataEntity newMember) + { + await using var dbContext = await this.DbContextFactory.CreateDbContextAsync(); + + // 取得資料 sub query + var queryable = from member in dbContext.Members + where member.Id == "1" + select new + { + member, + snapshots = dbContext.Snapshots + .Where(p => p.Id == "1") + .ToList() + }; + var data = await queryable.FirstOrDefaultAsync(); + var oldMember = data.member; + + // 比對差異 + var oldData = JsonSerializer.Serialize(oldMember, Options); + var newData = JsonSerializer.Serialize(newMember, Options); + var diff = JsonDiffPatcher.Diff(oldData, newData); + + // var oldData = JsonConvert.SerializeObject(oldMember); + // var newData = JsonConvert.SerializeObject(newMember); + // var jsonDiffPatch = new JsonDiffPatch(); + // var diff = jsonDiffPatch.Diff(oldData, newData); + // if (string.IsNullOrWhiteSpace(diff)) + // { + // return; + // } + + var newVersion = oldMember.Version + 1; + dbContext.Entry(oldMember).CurrentValues.SetValues(newMember); + oldMember.Version = newVersion; + var entity = new SnapshotDataEntity + { + Id = oldMember.Id, + Data = diff, + DataType = typeof(MemberDataEntity).ToString(), + DataFormat = DataFormat.Diff.ToString(), + CreatedAt = DateTimeOffset.UtcNow, + CreatedBy = "test", + Version = newVersion + }; + dbContext.Snapshots.Add(entity); + + var changes = await dbContext.SaveChangesAsync(); + } + + private static MemberDataEntity GenerateMember(int age, string name, Account account) + { + var newMember = new MemberDataEntity() + { + Id = "1", + Profile = new Profile + { + Age = age, + Name = name + }, + Accounts = new List + { + new() + { + Id = "yao", + Type = "VIP" + }, + new() + { + Id = "yao1", + Type = "VIP1" + } + }, + CreatedAt = DateTimeOffset.UtcNow, + CreatedBy = "test", + UpdatedAt = DateTimeOffset.UtcNow, + UpdatedBy = "test", + }; + if (account != null) + { + newMember.Accounts.Add(account); + } + + return newMember; } [TestMethod] public async Task 更新資料產生差異快照() { - await this.InsertAsync(); + var oldMember = await this.InsertOrGetAsync(); + var newMember = new MemberDataEntity() + { + Id = "1", + Profile = new Profile + { + Age = 19, + Name = "小章" + }, + Accounts = new List + { + new() + { + Id = "yao", + Type = "VIP" + }, + new() + { + Id = "yao1", + Type = "VIP1" + } + }, + CreatedAt = DateTimeOffset.UtcNow, + CreatedBy = "test", + UpdatedAt = DateTimeOffset.UtcNow, + UpdatedBy = "test", + Version = 2 + }; + + var search = "[{\"Id\": \"yao\"}]"; + + await using var dbContext = await this.DbContextFactory.CreateDbContextAsync(); + + var queryable = from member in dbContext.Members + join snapshot in dbContext.Snapshots + on + new + { + member.Id, + member.Version + } + equals + new + { + snapshot.Id, + snapshot.Version + } + where member.Id == "1" + select new { member, snapshot }; + + var data = await queryable.FirstOrDefaultAsync(); + var oldData = JsonConvert.SerializeObject(oldMember); + var newData = JsonConvert.SerializeObject(newMember); + var jsonDiffPatch = new JsonDiffPatch(); + var diff = jsonDiffPatch.Diff(oldData, newData); + var formatter = new JsonDeltaFormatter(); + + dbContext.Entry(data.member).CurrentValues.SetValues(newData); + dbContext.Snapshots.Add(new SnapshotDataEntity + { + Id = oldMember.Id, + Data = JsonNode.Parse(diff), + DataType = typeof(MemberDataEntity).ToString(), + DataFormat = "Diff", + CreatedAt = data.snapshot.CreatedAt, + CreatedBy = data.snapshot.CreatedBy, + Version = newMember.Version + }); + var changes = dbContext.SaveChanges(); + } + + [TestMethod] + public async Task ORM查詢Jsonb() + { + await this.InsertOrGetAsync(); var now = TestAssistant.Now; var userId = TestAssistant.UserId; @@ -58,6 +630,7 @@ public async Task 更新資料產生差異快照() // .Where(s => EF.Functions.JsonContains(s.Accounts, search)) // .AsNoTracking() // .FirstOrDefault(); + var queryable = from member in dbContext.Members join snapshot in dbContext.Snapshots on @@ -75,20 +648,22 @@ join snapshot in dbContext.Snapshots where EF.Functions.JsonContains(member.Accounts, search) select new { member, snapshot }; var data = await queryable.AsNoTracking().FirstOrDefaultAsync(); - var profile = new Profile() - { - Age = 20 - }; + data.member.Id.Should().Be("1"); } - private async Task InsertAsync() + private async Task InsertOrGetAsync() { await using var dbContext = await this.DbContextFactory.CreateDbContextAsync(); var now = TestAssistant.Now; var userId = TestAssistant.UserId; + var member = await dbContext.Members.Where(p => p.Id == "1").AsNoTracking().FirstOrDefaultAsync(); + if (member != null) + { + return member; + } - var member = new MemberDataEntity + member = new MemberDataEntity { Id = "1", Profile = new Profile @@ -115,14 +690,14 @@ private async Task InsertAsync() dbContext.Snapshots.Add(new SnapshotDataEntity { Id = member.Id, - Type = typeof(MemberDataEntity).ToString(), - Data = member.ToDictionary(), + Data = JsonNode.Parse(JsonSerializer.Serialize(member, Options)), + DataType = typeof(MemberDataEntity).ToString(), + DataFormat = "Full", CreatedAt = now, CreatedBy = userId, - UpdatedAt = now, - UpdatedBy = userId, Version = member.Version }); - return await dbContext.SaveChangesAsync(); + var count = await dbContext.SaveChangesAsync(); + return member; } } \ No newline at end of file From f7f87bf6fdd088e60b3e658239b72262c9b40d84 Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 4 Nov 2023 16:24:46 +0800 Subject: [PATCH 347/424] add test case --- .../Lab.Snapshot.Test/UnitTest1.cs | 212 +++++++++--------- 1 file changed, 110 insertions(+), 102 deletions(-) diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs index 0fe32a68..8e68167a 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs @@ -27,7 +27,7 @@ public class UnitTest1 IDbContextFactory DbContextFactory => _serviceProvider.GetService>(); - static JsonSerializerOptions Options = new() + static JsonSerializerOptions JsonSerializerOptions = new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, PropertyNameCaseInsensitive = true, @@ -67,7 +67,7 @@ public void TestCleanup() /// private void CleanAllRecord(DbContext dbContext) { - // dbContext.Database.ExecuteSqlRaw(NpgsqlGenerateScript.ClearAllRecord()); + dbContext.Database.ExecuteSqlRaw(NpgsqlGenerateScript.ClearAllRecord()); } [TestMethod] @@ -238,8 +238,8 @@ public async Task SystemTextJson_DiffPatch() Version = 2 }; - var oldData = JsonSerializer.Serialize(oldMember, Options); - var newData = JsonSerializer.Serialize(newMember, Options); + var oldData = JsonSerializer.Serialize(oldMember, JsonSerializerOptions); + var newData = JsonSerializer.Serialize(newMember, JsonSerializerOptions); var diff = JsonDiffPatcher.Diff(oldData, newData, new JsonDiffOptions { @@ -252,86 +252,81 @@ public async Task SystemTextJson_DiffPatch() }); var result = JsonNode.Parse(oldData); JsonDiffPatcher.Patch(ref result, diff); - var actual = result.Deserialize(Options); + var actual = result.Deserialize(JsonSerializerOptions); actual.Should().BeEquivalentTo(newMember); } + /// + /// 集合內容一樣,但順序不一樣 + /// [TestMethod] - public async Task SystemTextJson_DiffPatch_1() + public async Task SystemTextJson_Diff_ListSortDiff() { - var original = """ - { - "id": "1", - "profile": { - "age": 18, - "name": "yao-chang" - }, - "version": 1, - "accounts": [ - { - "id": "yao", - "type": "VIP" - } - ], - "createdAt": "2023-11-01T16:25:29.5966704+00:00", - "createdBy": "@@TestUser@@", - "updatedAt": "2023-11-01T16:25:29.5966704+00:00", - "updatedBy": "@@TestUser@@" - } - """; - var diffText = """ - { - "profile": { - "age": [ - 18, - 20 - ], - "name": [ - "yao-chang", - "jordan" - ] - }, - "version": [ - 1, - 0 - ], - "accounts": { - "1": [ - { - "id": "yao1", - "type": "VIP1" - } - ], - "2": [ - { - "id": "jordan1", - "type": "VVVIP" - } - ], - "_t": "a" - }, - "createdAt": [ - "2023-11-01T16:25:29.59667+00:00", - "2023-11-01T16:25:30.2735717+00:00" - ], - "createdBy": [ - "@@TestUser@@", - "test" - ], - "updatedAt": [ - "2023-11-01T16:25:29.59667+00:00", - "2023-11-01T16:25:30.2735718+00:00" - ], - "updatedBy": [ - "@@TestUser@@", - "test" - ] - } - """; - var result = JsonNode.Parse(original); - var diff = JsonNode.Parse(diffText); + var first = GenerateMember(20, "jordan", new Account + { + Id = "jordan1", + Type = "VVVIP" + }, 1); + first.Accounts = first.Accounts.OrderBy(p => p.Id).ToList(); + var second = GenerateMember(20, "jordan", new Account + { + Id = "jordan1", + Type = "VVVIP" + }, 1); + second.Accounts = second.Accounts.OrderByDescending(p => p.Id).ToList(); + var options = new JsonDiffOptions + { + JsonElementComparison = JsonElementComparison.Semantic, + }; + var jsonPatchDeltaFormatter = new JsonPatchDeltaFormatter(); + var left = JsonSerializer.Serialize(first.Accounts, JsonSerializerOptions); + var right = JsonSerializer.Serialize(second.Accounts, JsonSerializerOptions); + + var diff = JsonDiffPatcher.Diff(left + , right + // , jsonPatchDeltaFormatter + , options + ); + + first.Accounts.Should().BeEquivalentTo(second.Accounts); + + var result = JsonNode.Parse(left); + JsonDiffPatcher.Patch(ref result, diff); - var actual = result.Deserialize(Options); + var actual = result.Deserialize>(JsonSerializerOptions); + actual.Should().BeEquivalentTo(second.Accounts); + + result = JsonNode.Parse(right); + JsonDiffPatcher.Patch(ref result, diff); + actual = result.Deserialize>(JsonSerializerOptions); + actual.Should().BeEquivalentTo(second.Accounts); + } + + /// + /// 集合內容一樣,順序一樣 + /// + [TestMethod] + public async Task SystemTextJson_Diff_ListSortSame() + { + var first = GenerateMember(20, "jordan", new Account + { + Id = "jordan1", + Type = "VVVIP" + }, 1); + first.Accounts = first.Accounts.OrderBy(p => p.Id).ToList(); + var left = JsonSerializer.Serialize(first.Accounts, JsonSerializerOptions); + var right = JsonSerializer.Serialize(first.Accounts, JsonSerializerOptions); + var options = new JsonDiffOptions + { + JsonElementComparison = JsonElementComparison.Semantic + }; + var jsonPatchDeltaFormatter = new JsonPatchDeltaFormatter(); + var diff = JsonDiffPatcher.Diff(left + , right + , jsonPatchDeltaFormatter + , options + ); + diff.AsArray().Count.Should().Be(0); } [TestMethod] @@ -393,8 +388,8 @@ public async Task SystemTextJson_RestoreInMemory() Version = 2 }; - var oldData = JsonSerializer.Serialize(oldMember, Options); - var newData = JsonSerializer.Serialize(newMember, Options); + var oldData = JsonSerializer.Serialize(oldMember, JsonSerializerOptions); + var newData = JsonSerializer.Serialize(newMember, JsonSerializerOptions); var diff = JsonDiffPatcher.Diff(oldData, newData, new JsonDiffOptions { @@ -407,21 +402,26 @@ public async Task SystemTextJson_RestoreInMemory() }); var result = JsonNode.Parse(oldData); JsonDiffPatcher.Patch(ref result, diff); - var actual = result.Deserialize(Options); + var actual = result.Deserialize(JsonSerializerOptions); actual.Should().BeEquivalentTo(newMember); } [TestMethod] public async Task SystemTextJson_RestoreInDb() { - await this.InsertOrGetAsync(); + await this.InsertOrGetAsync(1); await this.Update(GenerateMember(20, "jordan", new Account { Id = "jordan1", Type = "VVVIP" - })); - await this.Update(GenerateMember(18, "yao", null)); - await this.Update(GenerateMember(32, "yao-chang", null)); + }, 2)); + await this.Update(GenerateMember(18, "yao", null, 3)); + await this.Update(GenerateMember(30, "yao1", new Account + { + Id = "chang", + Type = "Normal" + }, 4)); + await this.Update(GenerateMember(32, "yao-chang", null, 5)); await using var dbContext = await this.DbContextFactory.CreateDbContextAsync(); var snapshots = await dbContext.Snapshots @@ -433,18 +433,18 @@ public async Task SystemTextJson_RestoreInDb() JsonNode full = null; foreach (var snapshot in snapshots) { - if (snapshot.Version==1) + if (snapshot.Version == 1) { full = snapshot.Data; continue; } - JsonDiffPatcher.Patch(ref full, snapshot.Data); - // var diff = new JsonDiffPatch().Diff(oldData, newData); - // var patchData = new JsonDiffPatch().Patch(oldData, diff); + JsonDiffPatcher.Patch(ref full, snapshot.Data); } - var jsonString = full.ToJsonString(); + var actual = full.Deserialize(JsonSerializerOptions); + var expected = await dbContext.Members.FirstOrDefaultAsync(p => p.Id == "1"); + actual.Should().BeEquivalentTo(expected); } private static string ToJsonString(JsonDocument doc) @@ -458,6 +458,7 @@ private static string ToJsonString(JsonDocument doc) private async Task Update(MemberDataEntity newMember) { + var now = TestAssistant.Now; await using var dbContext = await this.DbContextFactory.CreateDbContextAsync(); // 取得資料 sub query @@ -474,9 +475,14 @@ private async Task Update(MemberDataEntity newMember) var oldMember = data.member; // 比對差異 - var oldData = JsonSerializer.Serialize(oldMember, Options); - var newData = JsonSerializer.Serialize(newMember, Options); - var diff = JsonDiffPatcher.Diff(oldData, newData); + var oldData = JsonSerializer.Serialize(oldMember, JsonSerializerOptions); + var newData = JsonSerializer.Serialize(newMember, JsonSerializerOptions); + var diff = JsonDiffPatcher.Diff(oldData, newData, + // new JsonPatchDeltaFormatter(), + new JsonDiffOptions + { + JsonElementComparison = JsonElementComparison.Semantic, + }); // var oldData = JsonConvert.SerializeObject(oldMember); // var newData = JsonConvert.SerializeObject(newMember); @@ -496,7 +502,7 @@ private async Task Update(MemberDataEntity newMember) Data = diff, DataType = typeof(MemberDataEntity).ToString(), DataFormat = DataFormat.Diff.ToString(), - CreatedAt = DateTimeOffset.UtcNow, + CreatedAt = now, CreatedBy = "test", Version = newVersion }; @@ -505,8 +511,9 @@ private async Task Update(MemberDataEntity newMember) var changes = await dbContext.SaveChangesAsync(); } - private static MemberDataEntity GenerateMember(int age, string name, Account account) + private static MemberDataEntity GenerateMember(int age, string name, Account account, int version) { + var now = TestAssistant.Now; var newMember = new MemberDataEntity() { Id = "1", @@ -528,10 +535,11 @@ private static MemberDataEntity GenerateMember(int age, string name, Account acc Type = "VIP1" } }, - CreatedAt = DateTimeOffset.UtcNow, + CreatedAt = now, CreatedBy = "test", - UpdatedAt = DateTimeOffset.UtcNow, + UpdatedAt = now, UpdatedBy = "test", + Version = version }; if (account != null) { @@ -544,7 +552,7 @@ private static MemberDataEntity GenerateMember(int age, string name, Account acc [TestMethod] public async Task 更新資料產生差異快照() { - var oldMember = await this.InsertOrGetAsync(); + var oldMember = await this.InsertOrGetAsync(1); var newMember = new MemberDataEntity() { Id = "1", @@ -618,7 +626,7 @@ join snapshot in dbContext.Snapshots [TestMethod] public async Task ORM查詢Jsonb() { - await this.InsertOrGetAsync(); + await this.InsertOrGetAsync(1); var now = TestAssistant.Now; var userId = TestAssistant.UserId; @@ -651,7 +659,7 @@ where EF.Functions.JsonContains(member.Accounts, search) data.member.Id.Should().Be("1"); } - private async Task InsertOrGetAsync() + private async Task InsertOrGetAsync(int version) { await using var dbContext = await this.DbContextFactory.CreateDbContextAsync(); @@ -683,14 +691,14 @@ private async Task InsertOrGetAsync() CreatedBy = userId, UpdatedAt = now, UpdatedBy = userId, - Version = 1 + Version = version }; dbContext.Members.Add(member); dbContext.Snapshots.Add(new SnapshotDataEntity { Id = member.Id, - Data = JsonNode.Parse(JsonSerializer.Serialize(member, Options)), + Data = JsonNode.Parse(JsonSerializer.Serialize(member, JsonSerializerOptions)), DataType = typeof(MemberDataEntity).ToString(), DataFormat = "Full", CreatedAt = now, From 1235f481ee47293be65e9becfb01953216a142fc Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 5 Nov 2023 17:51:58 +0800 Subject: [PATCH 348/424] add web api --- .../Lab.Snapshot/Lab.Snapshot.DB/Account.cs | 2 +- .../Lab.Snapshot.DB/MemberDataEntity.cs | 2 +- .../Lab.Snapshot/Lab.Snapshot.DB/Profile.cs | 2 +- .../Lab.Snapshot.Test/EnvironmentNames.cs | 6 - .../Lab.Snapshot.Test.csproj | 1 + .../Lab.Snapshot.Test/ServiceConfiguration.cs | 1 + .../Lab.Snapshot.Test/TestHook.cs | 1 + .../Lab.Snapshot.Test/UnitTest1.cs | 26 +-- .../Lab.Snapshot.WebAPI/ContextAccessor.cs | 34 +++ .../Controllers/MembersController.cs | 70 ++++++ .../DataFormat.cs | 2 +- .../Lab.Snapshot.WebAPI/EnvironmentNames.cs | 16 ++ .../Lab.Snapshot.WebAPI/Failure.cs | 27 +++ .../Lab.Snapshot.WebAPI/ISystemClock.cs | 6 + .../JsonSerializeFactory.cs | 21 ++ .../Lab.Snapshot.WebAPI.csproj | 25 +++ .../Lab.Snapshot.WebAPI/Mapper.cs | 17 ++ .../Lab.Snapshot.WebAPI/MemberRepository.cs | 208 ++++++++++++++++++ .../Lab.Snapshot.WebAPI/Program.cs | 83 +++++++ .../Properties/launchSettings.json | 41 ++++ .../ServiceModels/Account.cs | 8 + .../ServiceModels/InsertMemberRequest.cs | 15 ++ .../ServiceModels/MemberResponse.cs | 20 ++ .../ServiceModels/Profile.cs | 8 + .../Lab.Snapshot.WebAPI/SystemClock.cs | 6 + .../Lab.Snapshot.WebAPI/WeatherForecast.cs | 12 + .../appsettings.Development.json | 8 + .../Lab.Snapshot.WebAPI/appsettings.json | 9 + Shnapshot/Lab.Snapshot/Lab.Snapshot.sln | 6 + 29 files changed, 660 insertions(+), 23 deletions(-) delete mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/EnvironmentNames.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ContextAccessor.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Controllers/MembersController.cs rename Shnapshot/Lab.Snapshot/{Lab.Snapshot.Test => Lab.Snapshot.WebAPI}/DataFormat.cs (57%) create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/EnvironmentNames.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Failure.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ISystemClock.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/JsonSerializeFactory.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Lab.Snapshot.WebAPI.csproj create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Mapper.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Program.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Properties/launchSettings.json create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/Account.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/InsertMemberRequest.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/MemberResponse.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/Profile.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/SystemClock.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/WeatherForecast.cs create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/appsettings.Development.json create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/appsettings.json diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Account.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Account.cs index 2a387ab1..791c4887 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Account.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Account.cs @@ -1,6 +1,6 @@ namespace Lab.Snapshot.DB; -public class Account +public record Account { public string Id { get; set; } diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDataEntity.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDataEntity.cs index 3e3b6459..5c48f427 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDataEntity.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDataEntity.cs @@ -1,6 +1,6 @@ namespace Lab.Snapshot.DB; -public class MemberDataEntity +public record MemberDataEntity { public string Id { get; set; } diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Profile.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Profile.cs index 2877b8e7..36f3c0b9 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Profile.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/Profile.cs @@ -1,6 +1,6 @@ namespace Lab.Snapshot.DB; -public class Profile +public record Profile { public int Age { get; set; } diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/EnvironmentNames.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/EnvironmentNames.cs deleted file mode 100644 index b5780613..00000000 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/EnvironmentNames.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Lab.Snapshot.Test; - -class EnvironmentNames -{ - public const string DbConnectionString = "DB_CONNECTION_STRING"; -} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Lab.Snapshot.Test.csproj b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Lab.Snapshot.Test.csproj index 484eb821..9fd06f7c 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Lab.Snapshot.Test.csproj +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/Lab.Snapshot.Test.csproj @@ -27,6 +27,7 @@ + diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/ServiceConfiguration.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/ServiceConfiguration.cs index 81b7efdd..0733b02d 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/ServiceConfiguration.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/ServiceConfiguration.cs @@ -1,4 +1,5 @@ using Lab.Snapshot.DB; +using Lab.Snapshot.WebAPI; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestHook.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestHook.cs index f05f6bfb..2a49f66b 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestHook.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/TestHook.cs @@ -1,4 +1,5 @@ using Lab.Snapshot.DB; +using Lab.Snapshot.WebAPI; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs index 8e68167a..fd5a7929 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs @@ -9,6 +9,7 @@ using JsonDiffPatchDotNet; using JsonDiffPatchDotNet.Formatters.JsonPatch; using Lab.Snapshot.DB; +using Lab.Snapshot.WebAPI; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -284,7 +285,8 @@ public async Task SystemTextJson_Diff_ListSortDiff() var diff = JsonDiffPatcher.Diff(left , right - // , jsonPatchDeltaFormatter + + // , jsonPatchDeltaFormatter , options ); @@ -422,6 +424,7 @@ public async Task SystemTextJson_RestoreInDb() Type = "Normal" }, 4)); await this.Update(GenerateMember(32, "yao-chang", null, 5)); + await this.Update(GenerateMember(32, "yao-chang", null, 5)); await using var dbContext = await this.DbContextFactory.CreateDbContextAsync(); var snapshots = await dbContext.Snapshots @@ -473,29 +476,27 @@ private async Task Update(MemberDataEntity newMember) }; var data = await queryable.FirstOrDefaultAsync(); var oldMember = data.member; + newMember.Version = oldMember.Version; // 比對差異 var oldData = JsonSerializer.Serialize(oldMember, JsonSerializerOptions); var newData = JsonSerializer.Serialize(newMember, JsonSerializerOptions); var diff = JsonDiffPatcher.Diff(oldData, newData, + // new JsonPatchDeltaFormatter(), new JsonDiffOptions { JsonElementComparison = JsonElementComparison.Semantic, }); + if (diff == null) + { + return; + } - // var oldData = JsonConvert.SerializeObject(oldMember); - // var newData = JsonConvert.SerializeObject(newMember); - // var jsonDiffPatch = new JsonDiffPatch(); - // var diff = jsonDiffPatch.Diff(oldData, newData); - // if (string.IsNullOrWhiteSpace(diff)) - // { - // return; - // } + // 有異動,進版號 + newMember.Version = oldMember.Version + 1; - var newVersion = oldMember.Version + 1; dbContext.Entry(oldMember).CurrentValues.SetValues(newMember); - oldMember.Version = newVersion; var entity = new SnapshotDataEntity { Id = oldMember.Id, @@ -504,7 +505,7 @@ private async Task Update(MemberDataEntity newMember) DataFormat = DataFormat.Diff.ToString(), CreatedAt = now, CreatedBy = "test", - Version = newVersion + Version = newMember.Version }; dbContext.Snapshots.Add(entity); @@ -539,7 +540,6 @@ private static MemberDataEntity GenerateMember(int age, string name, Account acc CreatedBy = "test", UpdatedAt = now, UpdatedBy = "test", - Version = version }; if (account != null) { diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ContextAccessor.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ContextAccessor.cs new file mode 100644 index 00000000..d3f23e9c --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ContextAccessor.cs @@ -0,0 +1,34 @@ +namespace Lab.Snapshot.WebAPI; + +public record AuthContext +{ + public string TraceId { get; set; } + + public DateTimeOffset Now { get; set; } + + public string UserId { get; set; } +} + +public interface IContextGetter where T : class +{ + T? Get(); +} + +public interface IContextSetter where T : class +{ + void Set(T value); +} + +public class ContextAccessor : IContextGetter, IContextSetter where T : class +{ + private static AsyncLocal> _context = new(); + + public T? Get() => _context.Value?.Context; + + public void Set(T value) => _context.Value = new ContextHolder { Context = value }; +} + +public class ContextHolder where T : class +{ + public T Context { get; set; } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Controllers/MembersController.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Controllers/MembersController.cs new file mode 100644 index 00000000..112b6373 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Controllers/MembersController.cs @@ -0,0 +1,70 @@ +using System.Net; +using Lab.Snapshot.WebAPI.ServiceModels; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace Lab.Snapshot.WebAPI.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class MembersController : ControllerBase +{ + private readonly ILogger _logger; + readonly MemberRepository _memberRepository; + + public MembersController(ILogger logger, MemberRepository memberRepository) + { + this._logger = logger; + this._memberRepository = memberRepository; + } + + [HttpPost(Name = "InsertMember")] + public async Task> Post(InsertMemberRequest request) + { + var insertMemberResult = await this._memberRepository.InsertMemberAsync(request); + if (insertMemberResult.Failure != null) + { + return new ObjectResult(insertMemberResult.Failure) + { + ContentTypes = new MediaTypeCollection() + { + "application/json" + }, + StatusCode = (int)HttpStatusCode.BadRequest + }; + } + + return this.NoContent(); + } + + [HttpPut("{currentAccount}/bind", Name = "BindMember")] + public async Task> Bind(string currentAccount, UpdateMemberRequest request) + { + var insertMemberResult = await this._memberRepository.BindMemberAsync(currentAccount, request); + if (insertMemberResult.Failure != null) + { + return new ObjectResult(insertMemberResult.Failure) + { + ContentTypes = new MediaTypeCollection() + { + "application/json" + }, + StatusCode = (int)HttpStatusCode.BadRequest + }; + } + + return this.NoContent(); + } + + [HttpGet("{account}", Name = "GetMemberByAccount")] + public async Task> Get(string account, int? version) + { + var getMemberResult = await this._memberRepository.GetMemberAsync(account, version); + if (getMemberResult == null) + { + return this.NotFound(); + } + + return this.Ok(getMemberResult); + } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/DataFormat.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/DataFormat.cs similarity index 57% rename from Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/DataFormat.cs rename to Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/DataFormat.cs index 6a9814ca..f28f6841 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/DataFormat.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/DataFormat.cs @@ -1,4 +1,4 @@ -namespace Lab.Snapshot.Test; +namespace Lab.Snapshot.WebAPI; public enum DataFormat { diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/EnvironmentNames.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/EnvironmentNames.cs new file mode 100644 index 00000000..68b5b37c --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/EnvironmentNames.cs @@ -0,0 +1,16 @@ +namespace Lab.Snapshot.WebAPI; + +public class EnvironmentNames +{ + public const string DbConnectionString = "DB_CONNECTION_STRING"; + + public static Dictionary Values = new() + { + { + DbConnectionString, + "Host=localhost;Port=5432;Database=member_integration_test;Username=postgres;Password=guest" + } + }; + + +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Failure.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Failure.cs new file mode 100644 index 00000000..f8b25991 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Failure.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; + +namespace Lab.Snapshot.WebAPI; + +public enum FailureCode +{ + MemberExist, + MemberNotExist +} + +public class Failure +{ + public FailureCode Code { get; set; } + + public string Message { get; set; } + + public Failure(FailureCode code, string message) + { + this.Code = code; + this.Message = message; + } + + public List Failures { get; set; } + + [JsonIgnore] + public Exception Fetal { get; set; } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ISystemClock.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ISystemClock.cs new file mode 100644 index 00000000..563abfe1 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ISystemClock.cs @@ -0,0 +1,6 @@ +namespace Lab.Snapshot.WebAPI; + +public interface ISystemClock +{ + DateTimeOffset Now { get; } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/JsonSerializeFactory.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/JsonSerializeFactory.cs new file mode 100644 index 00000000..72045e37 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/JsonSerializeFactory.cs @@ -0,0 +1,21 @@ +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Lab.Snapshot.WebAPI; + +internal class JsonSerializeFactory +{ + public static JsonSerializerOptions Apply(JsonSerializerOptions options) + { + options.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; + options.PropertyNameCaseInsensitive = true; + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; + options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.Converters.Add(new JsonStringEnumConverter()); + return options; + } + + public static JsonSerializerOptions Init() => Apply(new JsonSerializerOptions()); +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Lab.Snapshot.WebAPI.csproj b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Lab.Snapshot.WebAPI.csproj new file mode 100644 index 00000000..5cece286 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Lab.Snapshot.WebAPI.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + + + + + + + diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Mapper.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Mapper.cs new file mode 100644 index 00000000..386830b9 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Mapper.cs @@ -0,0 +1,17 @@ +namespace Lab.Snapshot.WebAPI; + +public class Mapper +{ + public class DomainToResponseMappingProfile : AutoMapper.Profile + { + public DomainToResponseMappingProfile() + { + this.CreateMap(); + + this.CreateMap(); + this.CreateMap(); + this.CreateMap(); + this.CreateMap(); + } + } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs new file mode 100644 index 00000000..b9f30d63 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs @@ -0,0 +1,208 @@ +using System.Text.Json; +using System.Text.Json.JsonDiffPatch; +using System.Text.Json.JsonDiffPatch.Diffs; +using System.Text.Json.Nodes; +using AutoMapper; +using Lab.Snapshot.DB; +using Lab.Snapshot.WebAPI.ServiceModels; +using Microsoft.EntityFrameworkCore; + +namespace Lab.Snapshot.WebAPI; + +public class MemberRepository +{ + private readonly IDbContextFactory _memberDbContextFactory; + private readonly IMapper _mapper; + private readonly JsonSerializerOptions _jsonSerializerOptions; + private readonly IContextGetter _contextGetter; + + public MemberRepository(IDbContextFactory memberDbContextFactory, + IMapper mapper, + JsonSerializerOptions jsonSerializerOptions, + IContextGetter contextGetter) + { + this._memberDbContextFactory = memberDbContextFactory; + this._mapper = mapper; + this._jsonSerializerOptions = jsonSerializerOptions; + this._contextGetter = contextGetter; + } + + public async Task<(Failure Failure, bool Data)> InsertMemberAsync(InsertMemberRequest request) + { + var authContext = this._contextGetter.Get(); + var search = $"[{{\"Id\": \"{request.Account.Id}\"}}]"; + + await using var dbContext = await this._memberDbContextFactory.CreateDbContextAsync(); + var accountExist = dbContext.Members.AsNoTracking().Any(p => EF.Functions.JsonContains(p.Accounts, search)); + if (accountExist) + { + return (new Failure(FailureCode.MemberExist, $"Member({request.Account.Id}) exist"), false); + } + + var member = new MemberDataEntity + { + Id = Guid.NewGuid().ToString(), + Profile = this._mapper.Map(request.Profile), + Accounts = new List + { + this._mapper.Map(request.Account) + }, + CreatedAt = authContext.Now, + CreatedBy = authContext.UserId, + UpdatedAt = authContext.Now, + UpdatedBy = authContext.UserId, + Version = 1 + }; + var snapshot = new SnapshotDataEntity + { + Id = member.Id, + DataType = typeof(MemberDataEntity).ToString(), + Data = JsonNode.Parse(JsonSerializer.Serialize(member, this._jsonSerializerOptions)), + DataFormat = DataFormat.Full.ToString(), + CreatedAt = authContext.Now, + CreatedBy = authContext.UserId, + Version = member.Version + }; + await dbContext.Members.AddAsync(member); + await dbContext.Snapshots.AddAsync(snapshot); + await dbContext.SaveChangesAsync(); + return (null, true); + } + + public async Task<(Failure Failure, bool Data)> BindMemberAsync(string currentAccount, + UpdateMemberRequest request) + { + var authContext = this._contextGetter.Get(); + var search = $"[{{\"Id\": \"{currentAccount}\"}}]"; + + await using var dbContext = await this._memberDbContextFactory.CreateDbContextAsync(); + var oldMember = await dbContext.Members.FirstOrDefaultAsync(p => EF.Functions.JsonContains(p.Accounts, search)); + if (oldMember == null) + { + return (new Failure(FailureCode.MemberNotExist, $"Member({currentAccount}) not exist"), false); + } + + var newMember = oldMember with + { + Accounts = this._mapper.Map>(oldMember.Accounts), + Profile = this._mapper.Map(oldMember.Profile) + }; + + newMember.Profile = this._mapper.Map(request.Profile); + + // 帳號不存在才加入 + foreach (var srcAccount in request.Accounts) + { + var accountExist = false; + foreach (var destAccount in newMember.Accounts) + { + if (srcAccount.Id == destAccount.Id) + { + accountExist = true; + break; + } + } + + if (accountExist == false) + { + var map = this._mapper.Map(srcAccount); + newMember.Accounts.Add(map); + } + } + + var oldData = JsonSerializer.Serialize(oldMember, this._jsonSerializerOptions); + var newData = JsonSerializer.Serialize(newMember, this._jsonSerializerOptions); + var diff = JsonDiffPatcher.Diff(oldData, newData, + new JsonDiffOptions + { + JsonElementComparison = JsonElementComparison.Semantic, + }); + + if (diff == null) + { + return (null, true); + } + + // 不納入比對的欄位,最後再更新成最新狀態 + newMember.UpdatedAt = authContext.Now; + newMember.UpdatedBy = authContext.UserId; + newMember.Version = oldMember.Version + 1; + + oldData = JsonSerializer.Serialize(oldMember, this._jsonSerializerOptions); + newData = JsonSerializer.Serialize(newMember, this._jsonSerializerOptions); + diff = JsonDiffPatcher.Diff(oldData, newData, + new JsonDiffOptions + { + JsonElementComparison = JsonElementComparison.Semantic, + }); + + var snapshot = new SnapshotDataEntity + { + Id = newMember.Id, + DataType = typeof(MemberDataEntity).ToString(), + Data = diff, + DataFormat = DataFormat.Diff.ToString(), + CreatedAt = newMember.UpdatedAt, + CreatedBy = newMember.UpdatedBy, + Version = newMember.Version + }; + + var entry = dbContext.Members.Entry(oldMember); + entry.CurrentValues.SetValues(newMember); + await dbContext.Snapshots.AddAsync(snapshot); + await dbContext.SaveChangesAsync(); + return (null, true); + } + + public async Task GetMemberAsync(string account, int? version) + { + var search = $"[{{\"Id\": \"{account}\"}}]"; + + await using var dbContext = await this._memberDbContextFactory.CreateDbContextAsync(); + if (version.HasValue == false) + { + var queryable = from member in dbContext.Members + where EF.Functions.JsonContains(member.Accounts, search) + select new { member }; + var data = await queryable.AsNoTracking().FirstOrDefaultAsync(); + if (data == null) + { + return null; + } + + return this._mapper.Map(data.member); + } + + // 處理快照 + var entities = dbContext.Snapshots + .Where(p => p.Version <= version) + .OrderBy(p => p.Version) + ; + var snapshots = await entities.AsNoTracking().ToListAsync(); + JsonNode finial = null; + foreach (var snapshot in snapshots) + { + if (snapshot.Version == 1) + { + finial = snapshot.Data; + continue; + } + + JsonDiffPatcher.Patch(ref finial, snapshot.Data); + } + + // search account in JsonNode + var accounts = finial!["accounts"]!.AsArray(); + foreach (var item in accounts) + { + var id = item["id"]!.GetValue(); + if (id == account) + { + var result = finial.Deserialize(this._jsonSerializerOptions); + return result; + } + } + + return null; + } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Program.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Program.cs new file mode 100644 index 00000000..bd7bc100 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Program.cs @@ -0,0 +1,83 @@ +using Lab.Snapshot.DB; +using Lab.Snapshot.WebAPI; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers() + .AddJsonOptions(options => JsonSerializeFactory.Apply(options.JsonSerializerOptions)) + ; + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// builder.Services.AddScoped(p => new AuthContext +// { +// TraceId = Guid.NewGuid().ToString(), +// Now = DateTimeOffset.UtcNow, +// UserId = "System-" + Guid.NewGuid().ToString() +// }); + +builder.Services.AddSingleton>(); +builder.Services.AddSingleton>(p => p.GetService>()); +builder.Services.AddSingleton>(p => p.GetService>()); + +builder.Services.AddSingleton(_ => JsonSerializeFactory.Init()); +builder.Services.AddSingleton(); +builder.Services.AddAutoMapper(typeof(Mapper)); +ConfigDb(builder.Services); + +var app = builder.Build(); + +app.Use(async (context, next) => +{ + var contextSetter = context.RequestServices.GetService>(); + var authContext = new AuthContext + { + TraceId = Guid.NewGuid().ToString(), + Now = DateTimeOffset.UtcNow, + UserId = "System" + }; + contextSetter.Set(authContext); + context.Response.Headers.Add("X-Trace-Id", authContext.TraceId); + + await next.Invoke(); +}); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); + +static void ConfigDb(IServiceCollection services) +{ + //依照真實情況注入連線字串 + Environment.SetEnvironmentVariable(EnvironmentNames.DbConnectionString, + EnvironmentNames.Values[EnvironmentNames.DbConnectionString]); + + services.AddSingleton(p => { return LoggerFactory.Create(builder => { builder.AddConsole(); }); }); + services.AddDbContextFactory((p, options) => + { + //依照真實情況取得連線字串 + var connectionString = Environment.GetEnvironmentVariable(EnvironmentNames.DbConnectionString); + options.UseNpgsql(connectionString, + builder => builder.EnableRetryOnFailure( + 10, + TimeSpan.FromSeconds(30), + new List { "57P01" })) + ; + }); +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Properties/launchSettings.json b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Properties/launchSettings.json new file mode 100644 index 00000000..fc68fbb9 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:56731", + "sslPort": 44307 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5060", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7176;http://localhost:5060", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/Account.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/Account.cs new file mode 100644 index 00000000..262f9ae9 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/Account.cs @@ -0,0 +1,8 @@ +namespace Lab.Snapshot.WebAPI.ServiceModels; + +public class Account +{ + public string Id { get; set; } + + public string Type { get; set; } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/InsertMemberRequest.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/InsertMemberRequest.cs new file mode 100644 index 00000000..4d6ebd53 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/InsertMemberRequest.cs @@ -0,0 +1,15 @@ +namespace Lab.Snapshot.WebAPI.ServiceModels; + +public class InsertMemberRequest +{ + public Account Account { get; set; } + + public Profile Profile { get; set; } +} + +public class UpdateMemberRequest +{ + public List Accounts { get; set; } + + public Profile Profile { get; set; } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/MemberResponse.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/MemberResponse.cs new file mode 100644 index 00000000..f913b165 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/MemberResponse.cs @@ -0,0 +1,20 @@ +namespace Lab.Snapshot.WebAPI.ServiceModels; + +public class MemberResponse +{ + public string Id { get; set; } + + public Profile Profile { get; set; } + + public List Accounts { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset UpdatedAt { get; set; } + + public string UpdatedBy { get; set; } + + public int Version { get; set; } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/Profile.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/Profile.cs new file mode 100644 index 00000000..7bcb57c7 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/ServiceModels/Profile.cs @@ -0,0 +1,8 @@ +namespace Lab.Snapshot.WebAPI.ServiceModels; + +public class Profile +{ + public int Age { get; set; } + + public string Name { get; set; } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/SystemClock.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/SystemClock.cs new file mode 100644 index 00000000..df537255 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/SystemClock.cs @@ -0,0 +1,6 @@ +namespace Lab.Snapshot.WebAPI; + +public class SystemClock: ISystemClock +{ + public DateTimeOffset Now => DateTimeOffset.UtcNow; +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/WeatherForecast.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/WeatherForecast.cs new file mode 100644 index 00000000..1cc8c31e --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace Lab.Snapshot.WebAPI; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/appsettings.Development.json b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/appsettings.json b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.sln b/Shnapshot/Lab.Snapshot/Lab.Snapshot.sln index adc2da5d..3734c2b2 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.sln +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.sln @@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Snapshot.Test", "Lab.Sn EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Snapshot.DB", "Lab.Snapshot.DB\Lab.Snapshot.DB.csproj", "{472E967C-E725-494F-B906-A60F8B206895}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Snapshot.WebAPI", "Lab.Snapshot.WebAPI\Lab.Snapshot.WebAPI.csproj", "{2DC3F6CC-DBE5-4539-81EC-7368CA927387}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {472E967C-E725-494F-B906-A60F8B206895}.Debug|Any CPU.Build.0 = Debug|Any CPU {472E967C-E725-494F-B906-A60F8B206895}.Release|Any CPU.ActiveCfg = Release|Any CPU {472E967C-E725-494F-B906-A60F8B206895}.Release|Any CPU.Build.0 = Release|Any CPU + {2DC3F6CC-DBE5-4539-81EC-7368CA927387}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DC3F6CC-DBE5-4539-81EC-7368CA927387}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DC3F6CC-DBE5-4539-81EC-7368CA927387}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DC3F6CC-DBE5-4539-81EC-7368CA927387}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 6f41ad27395ad3fb571618b38e9d2dd6068758dc Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 14 Nov 2023 13:10:11 +0800 Subject: [PATCH 349/424] refactor --- .../Controllers/MembersController.cs | 6 +- .../Lab.Snapshot.WebAPI/MemberRepository.cs | 135 +++++++++++------- 2 files changed, 87 insertions(+), 54 deletions(-) diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Controllers/MembersController.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Controllers/MembersController.cs index 112b6373..b277772d 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Controllers/MembersController.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Controllers/MembersController.cs @@ -40,10 +40,10 @@ public async Task> Post(InsertMemberRequest request [HttpPut("{currentAccount}/bind", Name = "BindMember")] public async Task> Bind(string currentAccount, UpdateMemberRequest request) { - var insertMemberResult = await this._memberRepository.BindMemberAsync(currentAccount, request); - if (insertMemberResult.Failure != null) + var bindMemberResult = await this._memberRepository.BindMemberAsync(currentAccount, request); + if (bindMemberResult.Failure != null) { - return new ObjectResult(insertMemberResult.Failure) + return new ObjectResult(bindMemberResult.Failure) { ContentTypes = new MediaTypeCollection() { diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs index b9f30d63..153d324f 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs @@ -1,6 +1,5 @@ using System.Text.Json; using System.Text.Json.JsonDiffPatch; -using System.Text.Json.JsonDiffPatch.Diffs; using System.Text.Json.Nodes; using AutoMapper; using Lab.Snapshot.DB; @@ -82,59 +81,24 @@ public MemberRepository(IDbContextFactory memberDbContextFactor return (new Failure(FailureCode.MemberNotExist, $"Member({currentAccount}) not exist"), false); } - var newMember = oldMember with - { - Accounts = this._mapper.Map>(oldMember.Accounts), - Profile = this._mapper.Map(oldMember.Profile) - }; + var newMember = this.DeepClone(oldMember); - newMember.Profile = this._mapper.Map(request.Profile); + this.UpdateAccounts(request.Accounts, newMember); - // 帳號不存在才加入 - foreach (var srcAccount in request.Accounts) - { - var accountExist = false; - foreach (var destAccount in newMember.Accounts) - { - if (srcAccount.Id == destAccount.Id) - { - accountExist = true; - break; - } - } - - if (accountExist == false) - { - var map = this._mapper.Map(srcAccount); - newMember.Accounts.Add(map); - } - } - - var oldData = JsonSerializer.Serialize(oldMember, this._jsonSerializerOptions); - var newData = JsonSerializer.Serialize(newMember, this._jsonSerializerOptions); - var diff = JsonDiffPatcher.Diff(oldData, newData, - new JsonDiffOptions - { - JsonElementComparison = JsonElementComparison.Semantic, - }); - - if (diff == null) + // 比對 member 欄位是否有異動 + if (this.IsDiff(oldMember, newMember) == false) { + // 沒有異動,不做任何事 return (null, true); } - // 不納入比對的欄位,最後再更新成最新狀態 + // 有異動,進版號 newMember.UpdatedAt = authContext.Now; newMember.UpdatedBy = authContext.UserId; newMember.Version = oldMember.Version + 1; - oldData = JsonSerializer.Serialize(oldMember, this._jsonSerializerOptions); - newData = JsonSerializer.Serialize(newMember, this._jsonSerializerOptions); - diff = JsonDiffPatcher.Diff(oldData, newData, - new JsonDiffOptions - { - JsonElementComparison = JsonElementComparison.Semantic, - }); + // 差異內容 + var diff = this.JsonDiff(oldMember, newMember); var snapshot = new SnapshotDataEntity { @@ -154,11 +118,77 @@ public MemberRepository(IDbContextFactory memberDbContextFactor return (null, true); } - public async Task GetMemberAsync(string account, int? version) + private JsonNode? JsonDiff(MemberDataEntity oldMember, MemberDataEntity newMember) + { + var oldData = JsonSerializer.Serialize(oldMember, this._jsonSerializerOptions); + var newData = JsonSerializer.Serialize(newMember, this._jsonSerializerOptions); + var diff = JsonDiffPatcher.Diff(oldData, newData, + new JsonDiffOptions + { + JsonElementComparison = JsonElementComparison.Semantic, + }); + return diff; + } + + private bool IsDiff(MemberDataEntity oldMember, MemberDataEntity newMember) { - var search = $"[{{\"Id\": \"{account}\"}}]"; + var oldData = JsonSerializer.Serialize(oldMember, this._jsonSerializerOptions); + var newData = JsonSerializer.Serialize(newMember, this._jsonSerializerOptions); + var diff = JsonDiffPatcher.Diff(oldData, newData, + new JsonDiffOptions + { + JsonElementComparison = JsonElementComparison.Semantic, + }); + + if (diff == null) + { + return true; + } + return false; + } + + private MemberDataEntity UpdateAccounts(List srcAccounts, MemberDataEntity destMember) + { + foreach (var srcAccount in srcAccounts) + { + var accountExist = false; + foreach (var destAccount in destMember.Accounts) + { + if (srcAccount.Id == destAccount.Id) + { + accountExist = true; + break; + } + } + + // 帳號不存在才加入 + if (accountExist == false) + { + var map = this._mapper.Map(srcAccount); + destMember.Accounts.Add(map); + } + } + + return destMember; + } + + public MemberDataEntity DeepClone(MemberDataEntity oldMember) + { + var newMember = oldMember with + { + Accounts = this._mapper.Map>(oldMember.Accounts), + Profile = this._mapper.Map(oldMember.Profile) + }; + return newMember; + } + + public async Task GetMemberAsync(string account, int? version) + { + var search = $"[{{\"id\": \"{account}\"}}]"; await using var dbContext = await this._memberDbContextFactory.CreateDbContextAsync(); + + // 讀取最新資料 if (version.HasValue == false) { var queryable = from member in dbContext.Members @@ -173,13 +203,16 @@ where EF.Functions.JsonContains(member.Accounts, search) return this._mapper.Map(data.member); } - // 處理快照 - var entities = dbContext.Snapshots - .Where(p => p.Version <= version) - .OrderBy(p => p.Version) + // 讀取快照 + var query = "select * from \"Snapshot\" where \"Data\" -> 'accounts' @> '[{{\"id\": \"yao3\"}}]' ::jsonb"; + var snapshots = await dbContext.Snapshots + .FromSqlRaw(query) + .ToListAsync() ; - var snapshots = await entities.AsNoTracking().ToListAsync(); + JsonNode finial = null; + + // 依序合併快照 foreach (var snapshot in snapshots) { if (snapshot.Version == 1) From 8c217faa05e7e0964b8ba47d5560eb918039108d Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 14 Nov 2023 14:01:56 +0800 Subject: [PATCH 350/424] recfactor --- .../Lab.Snapshot.WebAPI/MemberRepository.cs | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs index 153d324f..ae645f9e 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs @@ -5,6 +5,8 @@ using Lab.Snapshot.DB; using Lab.Snapshot.WebAPI.ServiceModels; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Profile = Lab.Snapshot.WebAPI.ServiceModels.Profile; namespace Lab.Snapshot.WebAPI; @@ -84,21 +86,22 @@ public MemberRepository(IDbContextFactory memberDbContextFactor var newMember = this.DeepClone(oldMember); this.UpdateAccounts(request.Accounts, newMember); + this.UpdateAccounts(request.Profile, newMember); - // 比對 member 欄位是否有異動 - if (this.IsDiff(oldMember, newMember) == false) + // 比對兩個 member 內容是否有差異 + if (this.Diff(oldMember, newMember).Result == false) { - // 沒有異動,不做任何事 + // 沒有差異,不做任何事 return (null, true); } - // 有異動,進版號 + // 有差異,進版號 newMember.UpdatedAt = authContext.Now; newMember.UpdatedBy = authContext.UserId; newMember.Version = oldMember.Version + 1; - // 差異內容 - var diff = this.JsonDiff(oldMember, newMember); + // 產生差異內容 + var diff = this.Diff(oldMember, newMember).Data; var snapshot = new SnapshotDataEntity { @@ -111,26 +114,16 @@ public MemberRepository(IDbContextFactory memberDbContextFactor Version = newMember.Version }; + // 更新時,可以使用樂觀鎖定,Update Where Version=oldMember.Version,這裡我沒有實作 var entry = dbContext.Members.Entry(oldMember); entry.CurrentValues.SetValues(newMember); + await dbContext.Snapshots.AddAsync(snapshot); await dbContext.SaveChangesAsync(); return (null, true); } - private JsonNode? JsonDiff(MemberDataEntity oldMember, MemberDataEntity newMember) - { - var oldData = JsonSerializer.Serialize(oldMember, this._jsonSerializerOptions); - var newData = JsonSerializer.Serialize(newMember, this._jsonSerializerOptions); - var diff = JsonDiffPatcher.Diff(oldData, newData, - new JsonDiffOptions - { - JsonElementComparison = JsonElementComparison.Semantic, - }); - return diff; - } - - private bool IsDiff(MemberDataEntity oldMember, MemberDataEntity newMember) + private (JsonNode Data, bool Result) Diff(MemberDataEntity oldMember, MemberDataEntity newMember) { var oldData = JsonSerializer.Serialize(oldMember, this._jsonSerializerOptions); var newData = JsonSerializer.Serialize(newMember, this._jsonSerializerOptions); @@ -142,10 +135,10 @@ private bool IsDiff(MemberDataEntity oldMember, MemberDataEntity newMember) if (diff == null) { - return true; + return (null, false); } - return false; + return (diff, true); } private MemberDataEntity UpdateAccounts(List srcAccounts, MemberDataEntity destMember) @@ -173,6 +166,16 @@ private MemberDataEntity UpdateAccounts(List srcAccounts, return destMember; } + private void UpdateAccounts(Profile srcProfile, MemberDataEntity destMember) + { + if (srcProfile.Equals(destMember.Profile)) + { + return; + } + + destMember.Profile = this._mapper.Map(srcProfile); + } + public MemberDataEntity DeepClone(MemberDataEntity oldMember) { var newMember = oldMember with From c78f2855c02ba7fa31b0f33054075e263f3e3262 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 14 Nov 2023 14:02:55 +0800 Subject: [PATCH 351/424] refactor --- .../Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs index ae645f9e..60e6bd47 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs @@ -5,8 +5,6 @@ using Lab.Snapshot.DB; using Lab.Snapshot.WebAPI.ServiceModels; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; -using Profile = Lab.Snapshot.WebAPI.ServiceModels.Profile; namespace Lab.Snapshot.WebAPI; @@ -86,7 +84,7 @@ public MemberRepository(IDbContextFactory memberDbContextFactor var newMember = this.DeepClone(oldMember); this.UpdateAccounts(request.Accounts, newMember); - this.UpdateAccounts(request.Profile, newMember); + this.UpdateProfile(request.Profile, newMember); // 比對兩個 member 內容是否有差異 if (this.Diff(oldMember, newMember).Result == false) @@ -166,7 +164,7 @@ private MemberDataEntity UpdateAccounts(List srcAccounts, return destMember; } - private void UpdateAccounts(Profile srcProfile, MemberDataEntity destMember) + private void UpdateProfile(ServiceModels.Profile srcProfile, MemberDataEntity destMember) { if (srcProfile.Equals(destMember.Profile)) { From 7932fbb206ac3c2fd4acdd8adfc2d8a46c89735a Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 19 Nov 2023 12:20:07 +0800 Subject: [PATCH 352/424] refactor --- .../Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs index 60e6bd47..56d99bcb 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs @@ -186,7 +186,7 @@ public MemberDataEntity DeepClone(MemberDataEntity oldMember) public async Task GetMemberAsync(string account, int? version) { - var search = $"[{{\"id\": \"{account}\"}}]"; + var search = $"'[{{{{\"id\": \"{account}\"}}}}]'"; await using var dbContext = await this._memberDbContextFactory.CreateDbContextAsync(); // 讀取最新資料 @@ -205,7 +205,8 @@ where EF.Functions.JsonContains(member.Accounts, search) } // 讀取快照 - var query = "select * from \"Snapshot\" where \"Data\" -> 'accounts' @> '[{{\"id\": \"yao3\"}}]' ::jsonb"; + var query = $@"select * from ""Snapshot"" where ""Data"" -> 'accounts' @> {search}::jsonb"; + var snapshots = await dbContext.Snapshots .FromSqlRaw(query) .ToListAsync() From e664bbbec21b73f48f6305781ccf2a5b5550f2b8 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 19 Nov 2023 12:25:05 +0800 Subject: [PATCH 353/424] fix bug --- .../Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs index 56d99bcb..b54816df 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs @@ -227,7 +227,12 @@ where EF.Functions.JsonContains(member.Accounts, search) } // search account in JsonNode - var accounts = finial!["accounts"]!.AsArray(); + if (finial == null) + { + return null; + } + + var accounts = finial["accounts"]!.AsArray(); foreach (var item in accounts) { var id = item["id"]!.GetValue(); From c3819796c98b9b8cafb527813f638ab9d757fa35 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 2 Jan 2024 23:07:10 +0800 Subject: [PATCH 354/424] =?UTF-8?q?feat:=20specflow=20=E6=AF=94=E5=B0=8D?= =?UTF-8?q?=20json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BaseStep.cs | 151 ++++++++++++++++++ .../GlobalUsings.cs | 1 + .../Lab.SpecflowCreateAndCompareJson.csproj | 28 ++++ .../Member.cs | 35 ++++ .../SpecFlowFeature1.feature | 23 +++ .../TableExtensions.cs | 64 ++++++++ .../Lab.SpecflowTips/Lab.SpecflowTips.sln | 16 ++ 7 files changed, 318 insertions(+) create mode 100644 Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/BaseStep.cs create mode 100644 Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/GlobalUsings.cs create mode 100644 Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/Lab.SpecflowCreateAndCompareJson.csproj create mode 100644 Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/Member.cs create mode 100644 Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/SpecFlowFeature1.feature create mode 100644 Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/TableExtensions.cs create mode 100644 Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowTips.sln diff --git a/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/BaseStep.cs b/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/BaseStep.cs new file mode 100644 index 00000000..7b5b0ccc --- /dev/null +++ b/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/BaseStep.cs @@ -0,0 +1,151 @@ +using System.Text.Json; +using FluentAssertions; +using TechTalk.SpecFlow; +using TechTalk.SpecFlow.Assist; + +namespace Lab.SpecflowCreateAndCompareJson; + +[Binding] +public class BaseStep +{ + [Given(@"已準備 Member 資料\(錯誤\)")] + public void Given已準備Member資料錯誤(Table table) + { + var members = table.CreateSet(row => + { + var member = new Member + { + Id = null, + Name = null, + IpData = null, + Orders = null + }; + + return member; + }); + } + + [Given(@"已準備 Member 資料\(正確\)")] + public void Given已準備Member資料正確(Table table) + { + var members = new List(); + foreach (var row in table.Rows) + { + var member = new Member(); + foreach (var header in table.Header) + { + switch (header) + { + case nameof(Member.Id): + member.Id = row[header]; + break; + case nameof(Member.Age): + member.Age = int.Parse(row[header]); + break; + case nameof(Member.State): + member.State = Enum.Parse(row[header]); + break; + case nameof(Member.Name): + member.Name = TryGetName(row); + break; + case nameof(Member.IpData): + member.IpData = TryGetIpData(row); + break; + case nameof(Member.Orders): + member.Orders = TryGetOrders(row); + break; + } + } + + members.Add(member); + } + } + + private static Name? TryGetName(TableRow row) + { + var data = row.TryGetValue(nameof(Member.Name), out var name) + ? JsonSerializer.Deserialize(name) + : null; + return data; + } + + private static List? TryGetOrders(TableRow row) + { + var data = row.TryGetValue(nameof(Member.Orders), out var orders) + ? JsonSerializer.Deserialize>(orders) + : new List(); + return data; + } + + private static List? TryGetIpData(TableRow row) + { + var data = row.TryGetValue(nameof(Member.IpData), out var ip) + ? JsonSerializer.Deserialize>(ip) + : new List(); + return data; + } + + [Given(@"已準備 Member 資料\(擴充方法\)")] + public void Given已準備Member資料擴充方法(Table table) + { + var members = table.CreateJsonSet(); + } + + [Then(@"預期得到 Member 資料\(錯誤\)")] + public void Then預期得到Member資料錯誤(Table table) + { + var actual = CreateActualMembers(); + table.CompareToSet(actual); + } + + [Then(@"預期得到 Member 資料\(正確\)")] + public void Then預期得到Member資料正確(Table table) + { + var actual = CreateActualMembers(); + var expected = table.CreateJsonSet(); + var header = table.Header.ToHashSet(); + + // actual.Should().BeEquivalentTo(expected, options => options + // .Including(x => x.Id) + // .Including(x => x.Age) + // .Including(x => x.Name) + // .Including(x => x.State) + // .Including(x => x.IpData) + // .Including(x => x.Orders) + // ); + + actual.Should().BeEquivalentTo(expected, options => + { + options.Including(info => header.Contains(info.Name)); + if (header.Contains(nameof(Member.Name))) + { + options.Including(info => info.Name); + } + return options; + }); + + // actual.Should() + // .BeEquivalentTo(expected); + } + + private static List CreateActualMembers() + { + List actual = + [ + new Member + { + Id = "1", + Age = 18, + Name = new Name + { + FirstName = "yaochang", + LastName = "yu" + }, + State = State.Active, + IpData = ["192.168.0.1", "192.168.0.2"], + Orders = [new Order { Id = "123" }] + } + ]; + return actual; + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/GlobalUsings.cs b/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/GlobalUsings.cs new file mode 100644 index 00000000..8c927eb7 --- /dev/null +++ b/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/Lab.SpecflowCreateAndCompareJson.csproj b/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/Lab.SpecflowCreateAndCompareJson.csproj new file mode 100644 index 00000000..46472657 --- /dev/null +++ b/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/Lab.SpecflowCreateAndCompareJson.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/Member.cs b/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/Member.cs new file mode 100644 index 00000000..9ba339b6 --- /dev/null +++ b/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/Member.cs @@ -0,0 +1,35 @@ +namespace Lab.SpecflowCreateAndCompareJson; + +public class Member +{ + public string Id { get; set; } + + public int Age { get; set; } + + public Name Name { get; set; } + + public State State { get; set; } + + public List IpData { get; set; } + + public List Orders { get; set; } +} + +public enum State +{ + None, + Active, + Inactive, +} + +public class Order +{ + public string Id { get; set; } +} + +public class Name +{ + public string FirstName { get; set; } + + public string LastName { get; set; } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/SpecFlowFeature1.feature b/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/SpecFlowFeature1.feature new file mode 100644 index 00000000..4c1316e2 --- /dev/null +++ b/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/SpecFlowFeature1.feature @@ -0,0 +1,23 @@ +Feature: SpecFlowFeature1 +Simple calculator for adding two numbers + + Scenario: 建立一筆會員(錯誤) + Given 已準備 Member 資料(錯誤) + | Id | Age | IpData | Orders | State | + | 1 | 18 | ["192.168.0.1","192.168.0.2"] | [{"Id":"123"}] | Active | + Then 預期得到 Member 資料(錯誤) + | Id | Age | IpData | Orders | State | + | 1 | 18 | ["192.168.0.1","192.168.0.2"] | [{"Id":"123"}] | Active | + + Scenario: 建立一筆會員(正確) + Given 已準備 Member 資料(正確) + | Id | Age | Name | IpData | Orders | State | + | 1 | 18 | {"FirstName":"yaochang","LastName":"yu"} | ["192.168.0.1","192.168.0.2"] | [{"Id":"123"}] | Active | + Then 預期得到 Member 資料(正確) + | Id | Age | Name | IpData | Orders | State | + | 1 | 18 | {"FirstName":"yaochang","LastName":"yu1"} | ["192.168.0.1","192.168.0.3"] | [{"Id":"124"}] | Active | + + Scenario: 建立一筆會員(擴充方法) + Given 已準備 Member 資料(擴充方法) + | Id | Age | Name | IpData | Orders | State | + | 1 | 18 | {"FirstName":"yaochang","LastName":"yu"} | ["192.168.0.1","192.168.0.2"] | [{"Id":"123"}] | Active | \ No newline at end of file diff --git a/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/TableExtensions.cs b/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/TableExtensions.cs new file mode 100644 index 00000000..08ed68b7 --- /dev/null +++ b/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowCreateAndCompareJson/TableExtensions.cs @@ -0,0 +1,64 @@ +using System.Text.Json; +using TechTalk.SpecFlow; + +namespace Lab.SpecflowCreateAndCompareJson; + +public static class TableExtensions +{ + public static IEnumerable? CreateJsonSet(this Table table) + { + var results = new List(); + var type = typeof(T); + foreach (var row in table.Rows) + { + var instance = Activator.CreateInstance(); + foreach (var header in table.Header) + { + var property = type.GetProperty(header); + if (property == null) + { + continue; + } + + var value = row[header]; + if (string.IsNullOrWhiteSpace(value)) + { + continue; + } + + var propertyType = property.PropertyType; + + //若是泛型且是集合 + if (propertyType.IsGenericType + && propertyType.GetGenericTypeDefinition() == typeof(List<>)) + { + var listType = propertyType.GetGenericArguments()[0]; + var list = JsonSerializer.Deserialize(value, typeof(List<>).MakeGenericType(listType)); + property.SetValue(instance, list); + } + + //若是物件且不是字串 + else if (propertyType.IsClass + && propertyType != typeof(string)) + { + var obj = JsonSerializer.Deserialize(value, propertyType); + property.SetValue(instance, obj); + } + + //若是列舉 + else if (propertyType.IsEnum) + { + property.SetValue(instance, Enum.Parse(propertyType, value)); + } + else + { + property.SetValue(instance, Convert.ChangeType(value, propertyType)); + } + } + + results.Add(instance); + } + + return results; + } +} \ No newline at end of file diff --git a/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowTips.sln b/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowTips.sln new file mode 100644 index 00000000..1d9c31b8 --- /dev/null +++ b/Test/Specflow3/Lab.SpecflowTips/Lab.SpecflowTips.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SpecflowCreateAndCompareJson", "Lab.SpecflowCreateAndCompareJson\Lab.SpecflowCreateAndCompareJson.csproj", "{692293AC-DA77-47AB-860F-341C7136C7FD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {692293AC-DA77-47AB-860F-341C7136C7FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {692293AC-DA77-47AB-860F-341C7136C7FD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {692293AC-DA77-47AB-860F-341C7136C7FD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {692293AC-DA77-47AB-860F-341C7136C7FD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From 34db158365a9b7015e23af00e0ab1d943732d5bf Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 7 Jan 2024 15:29:56 +0800 Subject: [PATCH 355/424] feat: cursor paging --- Paging/Lab.CursorPaging/.dockerignore | 25 +++ .../Controllers/MembersController.cs | 149 ++++++++++++++++++ .../Lab.CursorPaging.WebApi/Dockerfile | 23 +++ .../Lab.CursorPaging.WebApi.csproj | 28 ++++ .../Lab.CursorPaging.WebApi.http | 6 + .../Member/Repository/MemberDbContext.cs | 70 ++++++++ .../20240106135936_InitialCreate.Designer.cs | 76 +++++++++ .../20240106135936_InitialCreate.cs | 45 ++++++ .../MemberDbContextModelSnapshot.cs | 73 +++++++++ .../Lab.CursorPaging.WebApi/Program.cs | 41 +++++ .../Properties/launchSettings.json | 41 +++++ .../appsettings.Development.json | 8 + .../Lab.CursorPaging.WebApi/appsettings.json | 9 ++ Paging/Lab.CursorPaging/Lab.CursorPaging.sln | 21 +++ Paging/Lab.CursorPaging/docker-compose.yml | 8 + 15 files changed, 623 insertions(+) create mode 100644 Paging/Lab.CursorPaging/.dockerignore create mode 100644 Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs create mode 100644 Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Dockerfile create mode 100644 Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Lab.CursorPaging.WebApi.csproj create mode 100644 Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Lab.CursorPaging.WebApi.http create mode 100644 Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Member/Repository/MemberDbContext.cs create mode 100644 Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240106135936_InitialCreate.Designer.cs create mode 100644 Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240106135936_InitialCreate.cs create mode 100644 Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/MemberDbContextModelSnapshot.cs create mode 100644 Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Program.cs create mode 100644 Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Properties/launchSettings.json create mode 100644 Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/appsettings.Development.json create mode 100644 Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/appsettings.json create mode 100644 Paging/Lab.CursorPaging/Lab.CursorPaging.sln create mode 100644 Paging/Lab.CursorPaging/docker-compose.yml diff --git a/Paging/Lab.CursorPaging/.dockerignore b/Paging/Lab.CursorPaging/.dockerignore new file mode 100644 index 00000000..cd967fc3 --- /dev/null +++ b/Paging/Lab.CursorPaging/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs new file mode 100644 index 00000000..af7dbb28 --- /dev/null +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs @@ -0,0 +1,149 @@ +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using Lab.CursorPaging.WebApi.Member.Repository; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Primitives; + +namespace Lab.CursorPaging.WebApi.Controllers; + +[ApiController] +public partial class MembersController : ControllerBase +{ + private readonly IDbContextFactory _memberDbContextFactory; + + public MembersController(IDbContextFactory memberDbContextFactory) + { + this._memberDbContextFactory = memberDbContextFactory; + } + + [HttpGet] + [Route("/api/members")] + public async Task>> Get() + { + var pageSize = this.TryGetPageSize(); + var pageTokenResult = this.TryGetPageToken(); + var lastId = pageTokenResult.LastId; + var lastSequenceId = pageTokenResult.LastSequenceId; + + await using var dbContext = await this._memberDbContextFactory.CreateDbContextAsync(); + var query = dbContext.Members.Select(p => p); + if (string.IsNullOrWhiteSpace(lastId) == false) + { + query = query.Where(p => p.Id.CompareTo(lastId) > 0); + } + + if (lastSequenceId.HasValue) + { + query = query.Where(p => p.SequenceId > lastSequenceId); + } + + query = query.Take(pageSize + 1); + var results = await query.AsNoTracking().ToListAsync(); + + // 是否有下一頁 + bool hasNextPage = results.Count > pageSize; + + if (hasNextPage) + { + // 有下一頁,刪除最後一筆 + results.RemoveAt(results.Count - 1); + + // 產生下一頁的令牌 + var after = results.LastOrDefault(); + if (after != null) + { + var nextToken = EncodePageToken(after.Id, after.SequenceId); + this.Response.Headers.Add("X-Next-Page-Token", nextToken); + } + } + + return this.Ok(results); + } + + private int TryGetPageSize() => + this.Request.Headers.TryGetValue("X-Page-Size", out var sizes) + ? int.Parse(sizes.FirstOrDefault() ?? string.Empty) + : 10; + + private (string? LastId, long? LastSequenceId) TryGetPageToken() + { + if (this.Request.Headers.TryGetValue("X-Next-Page-Token", out var nextToken)) + { + var decodeResult = DecodePageToken(nextToken); + return (decodeResult.lastId, decodeResult.lastSequenceId); + } + + return (null, null); + } + + // 將 Id 和 Version 轉換為下一頁的令牌 + private static string EncodePageToken(string? lastId, long? lastSequenceId) + { + if (lastId == null || lastSequenceId == null) + { + return null; + } + + var json = JsonSerializer.Serialize(new { lastId, lastSequenceId }); + return Convert.ToBase64String(Encoding.UTF8.GetBytes(json)); + } + + // 將下一頁的令牌解碼為 Id 和 Version + private static (string lastId, long lastSequenceId) DecodePageToken(string nextToken) + { + if (string.IsNullOrEmpty(nextToken)) + { + return (null, 0); + } + + string lastId = null; + long lastSequenceId = 0; + var base64Bytes = Convert.FromBase64String(nextToken); + var json = Encoding.UTF8.GetString(base64Bytes); + var jsonNode = JsonNode.Parse(json); + var jsonObject = jsonNode.AsObject(); + if (jsonObject.TryGetPropertyValue("lastSequenceId", out var lastSequenceIdNode)) + { + lastSequenceId = lastSequenceIdNode.GetValue(); + } + + if (jsonObject.TryGetPropertyValue("lastId", out var lastIdNode)) + { + lastId = lastIdNode.GetValue(); + } + + return (lastId, lastSequenceId); + } + + [HttpPost] + [Route("/api/members:batch-generate")] + public async Task BatchGenerate() + { + await using var dbContext = await this._memberDbContextFactory.CreateDbContextAsync(); + for (var i = 0; i < 1000; i++) + { + var now = DateTimeOffset.UtcNow; + var member = new MemberDataEntity + { + Id = Guid.NewGuid().ToString(), + Name = Faker.Name.FullName(), + Age = DateTime.Now.Year - Faker.Date.Birthday().Year, + Email = Faker.User.Email(), + Phone = Faker.Phone.GetPhoneNumber(), + Address = Faker.Address.SecondaryAddress(), + CreatedAt = now, + CreatedBy = "sys", + UpdatedAt = now, + UpdatedBy = "sys", + }; + + dbContext.Members.Add(member); + } + + var count = await dbContext.SaveChangesAsync(); + + return this.NoContent(); + } +} \ No newline at end of file diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Dockerfile b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Dockerfile new file mode 100644 index 00000000..43c017dd --- /dev/null +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Dockerfile @@ -0,0 +1,23 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER $APP_UID +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Lab.CursorPaging.WebApi/Lab.CursorPaging.WebApi.csproj", "Lab.CursorPaging.WebApi/"] +RUN dotnet restore "Lab.CursorPaging.WebApi/Lab.CursorPaging.WebApi.csproj" +COPY . . +WORKDIR "/src/Lab.CursorPaging.WebApi" +RUN dotnet build "Lab.CursorPaging.WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "Lab.CursorPaging.WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Lab.CursorPaging.WebApi.dll"] diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Lab.CursorPaging.WebApi.csproj b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Lab.CursorPaging.WebApi.csproj new file mode 100644 index 00000000..f9a63c51 --- /dev/null +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Lab.CursorPaging.WebApi.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + true + Linux + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + .dockerignore + + + + diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Lab.CursorPaging.WebApi.http b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Lab.CursorPaging.WebApi.http new file mode 100644 index 00000000..224f87c1 --- /dev/null +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Lab.CursorPaging.WebApi.http @@ -0,0 +1,6 @@ +@Lab.CursorPaging.WebApi_HostAddress = http://localhost:5164 + +GET {{Lab.CursorPaging.WebApi_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Member/Repository/MemberDbContext.cs b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Member/Repository/MemberDbContext.cs new file mode 100644 index 00000000..cb3541ed --- /dev/null +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Member/Repository/MemberDbContext.cs @@ -0,0 +1,70 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Lab.CursorPaging.WebApi.Member.Repository; + +public class MemberDbContext : DbContext +{ + private static readonly bool[] s_migrated = { false }; + + public MemberDbContext(DbContextOptions options) : base(options) + { + if (!s_migrated[0]) + { + lock (s_migrated) + { + if (!s_migrated[0]) + { + this.Database.Migrate(); + s_migrated[0] = true; + } + } + } + } + + public DbSet Members { get; set; } = default!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(builder => + { + builder.ToTable("Member"); + builder.HasKey(x => x.Id); + builder.Property(x => x.Name).IsRequired(); + builder.Property(x => x.Age).IsRequired(); + builder.Property(x => x.CreatedAt).IsRequired(); + builder.Property(x => x.CreatedBy).IsRequired(); + builder.Property(x => x.UpdatedAt).IsRequired(); + builder.Property(x => x.UpdatedBy).IsRequired(); + builder.Property(p => p.SequenceId).ValueGeneratedOnAdd(); + }); + } +} + +public class MemberDataEntity +{ + public string Id { get; set; } + + public string Name { get; set; } = default!; + + public int Age { get; set; } + + public string? Email { get; set; } + + public string? Phone { get; set; } + + public string? Address { get; set; } + + public DateTimeOffset CreatedAt { get; set; } + + public string CreatedBy { get; set; } + + public DateTimeOffset UpdatedAt { get; set; } + + public string UpdatedBy { get; set; } + + // [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long SequenceId { get; set; } +} \ No newline at end of file diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240106135936_InitialCreate.Designer.cs b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240106135936_InitialCreate.Designer.cs new file mode 100644 index 00000000..98cd888c --- /dev/null +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240106135936_InitialCreate.Designer.cs @@ -0,0 +1,76 @@ +// +using System; +using Lab.CursorPaging.WebApi.Member.Repository; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Lab.CursorPaging.WebApi.Migrations +{ + [DbContext(typeof(MemberDbContext))] + [Migration("20240106135936_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Lab.CursorPaging.WebApi.Member.Repository.MemberDataEntity", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Address") + .HasColumnType("text"); + + b.Property("Age") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Member", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240106135936_InitialCreate.cs b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240106135936_InitialCreate.cs new file mode 100644 index 00000000..6f990125 --- /dev/null +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240106135936_InitialCreate.cs @@ -0,0 +1,45 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Lab.CursorPaging.WebApi.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Member", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + Age = table.Column(type: "integer", nullable: false), + Email = table.Column(type: "text", nullable: true), + Phone = table.Column(type: "text", nullable: true), + Address = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedBy = table.Column(type: "text", nullable: false), + SequenceId = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + }, + constraints: table => + { + table.PrimaryKey("PK_Member", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Member"); + } + } +} diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/MemberDbContextModelSnapshot.cs b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/MemberDbContextModelSnapshot.cs new file mode 100644 index 00000000..d570c4be --- /dev/null +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/MemberDbContextModelSnapshot.cs @@ -0,0 +1,73 @@ +// +using System; +using Lab.CursorPaging.WebApi.Member.Repository; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Lab.CursorPaging.WebApi.Migrations +{ + [DbContext(typeof(MemberDbContext))] + partial class MemberDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Lab.CursorPaging.WebApi.Member.Repository.MemberDataEntity", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Address") + .HasColumnType("text"); + + b.Property("Age") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("SequenceId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceId")); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Member", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Program.cs b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Program.cs new file mode 100644 index 00000000..eb9db404 --- /dev/null +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Program.cs @@ -0,0 +1,41 @@ +using Lab.CursorPaging.WebApi.Member.Repository; +using Microsoft.EntityFrameworkCore; + +Environment.SetEnvironmentVariable("DbConnectionString", "Host=localhost;Port=5432;Database=Member;Username=postgres;Password=guest;"); +Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development"); + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddDbContextFactory((sp, options) => +{ + // var connProvider = sp.GetRequiredService(); + var connString = Environment.GetEnvironmentVariable("DbConnectionString"); + options + .UseNpgsql( + connString, + builder => builder.EnableRetryOnFailure( + 10, + TimeSpan.FromSeconds(30), + new List { "57P01" })) + .UseLoggerFactory(sp.GetService()) + ; +}); +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +// app.UseHttpsRedirection(); +app.UseRouting(); +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Properties/launchSettings.json b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Properties/launchSettings.json new file mode 100644 index 00000000..0247862f --- /dev/null +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:4298", + "sslPort": 44369 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5164", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7293;http://localhost:5164", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/appsettings.Development.json b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/appsettings.json b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.sln b/Paging/Lab.CursorPaging/Lab.CursorPaging.sln new file mode 100644 index 00000000..a755c4fe --- /dev/null +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.sln @@ -0,0 +1,21 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.CursorPaging.WebApi", "Lab.CursorPaging.WebApi\Lab.CursorPaging.WebApi.csproj", "{764FE9E4-9198-4812-A099-0AE48FE0816A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{735C1FFE-0C51-436D-8A8C-27CA1E7DF20C}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {764FE9E4-9198-4812-A099-0AE48FE0816A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {764FE9E4-9198-4812-A099-0AE48FE0816A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {764FE9E4-9198-4812-A099-0AE48FE0816A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {764FE9E4-9198-4812-A099-0AE48FE0816A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Paging/Lab.CursorPaging/docker-compose.yml b/Paging/Lab.CursorPaging/docker-compose.yml new file mode 100644 index 00000000..a760b5eb --- /dev/null +++ b/Paging/Lab.CursorPaging/docker-compose.yml @@ -0,0 +1,8 @@ +services: + db: + image: postgres + container_name: postgres-latest + environment: + - POSTGRES_PASSWORD=guest + ports: + - 5432:5432 \ No newline at end of file From c55ca7d2710760a890e41b11a251723506df9677 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 7 Jan 2024 16:17:29 +0800 Subject: [PATCH 356/424] refactor --- .../Lab.CursorPaging.WebApi/Controllers/MembersController.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs index af7dbb28..179bdf46 100644 --- a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs @@ -4,7 +4,6 @@ using Lab.CursorPaging.WebApi.Member.Repository; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Primitives; namespace Lab.CursorPaging.WebApi.Controllers; @@ -78,7 +77,7 @@ private int TryGetPageSize() => return (null, null); } - // 將 Id 和 Version 轉換為下一頁的令牌 + // 將 Id 和 SequenceId 轉換為下一頁的令牌 private static string EncodePageToken(string? lastId, long? lastSequenceId) { if (lastId == null || lastSequenceId == null) @@ -90,7 +89,7 @@ private static string EncodePageToken(string? lastId, long? lastSequenceId) return Convert.ToBase64String(Encoding.UTF8.GetBytes(json)); } - // 將下一頁的令牌解碼為 Id 和 Version + // 將下一頁的令牌解碼為 Id 和 SequenceId private static (string lastId, long lastSequenceId) DecodePageToken(string nextToken) { if (string.IsNullOrEmpty(nextToken)) From b715f70fcefd1a4f8326e04ee7605f84d226b1d4 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 7 Jan 2024 22:36:28 +0800 Subject: [PATCH 357/424] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20offset-bas?= =?UTF-8?q?ed=20=E5=88=86=E9=A0=81=E5=AF=AB=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/MembersController.cs | 36 ++++++++++++++++--- .../Member/Repository/MemberDbContext.cs | 7 ++-- ... 20240107142909_InitialCreate.Designer.cs} | 5 ++- ...ate.cs => 20240107142909_InitialCreate.cs} | 6 ++++ .../MemberDbContextModelSnapshot.cs | 3 ++ 5 files changed, 49 insertions(+), 8 deletions(-) rename Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/{20240106135936_InitialCreate.Designer.cs => 20240107142909_InitialCreate.Designer.cs} (95%) rename Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/{20240106135936_InitialCreate.cs => 20240107142909_InitialCreate.cs} (91%) diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs index 179bdf46..6d42ced1 100644 --- a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs @@ -18,8 +18,33 @@ public MembersController(IDbContextFactory memberDbContextFacto } [HttpGet] - [Route("/api/members")] - public async Task>> Get() + [Route("/api/members:page-index")] + public async Task>> GetPageIndex() + { + int pageIndex = this.Request.Headers.TryGetValue("X-Page-Index", out var pages) + ? int.TryParse(pages.FirstOrDefault(), out var parsedPageIndex) + ? parsedPageIndex + : 1 + : 1; + var pageSize = this.TryGetPageSize(); + + await using var dbContext = await this._memberDbContextFactory.CreateDbContextAsync(); + var query = dbContext.Members.Select(p => p); + var totalCount = await dbContext.Members.CountAsync(); + query = query + .Skip((pageIndex - 1) * pageSize) + .Take(pageSize); + query = query.Take(pageSize + 1); + + var results = await query.AsNoTracking().ToListAsync(); + this.Response.Headers.Add("X-Total-Count", totalCount.ToString()); + + return this.Ok(results); + } + + [HttpGet] + [Route("/api/members:cursor")] + public async Task>> GetCursor() { var pageSize = this.TryGetPageSize(); var pageTokenResult = this.TryGetPageToken(); @@ -78,7 +103,7 @@ private int TryGetPageSize() => } // 將 Id 和 SequenceId 轉換為下一頁的令牌 - private static string EncodePageToken(string? lastId, long? lastSequenceId) + public static string EncodePageToken(string? lastId, long? lastSequenceId) { if (lastId == null || lastSequenceId == null) { @@ -90,7 +115,7 @@ private static string EncodePageToken(string? lastId, long? lastSequenceId) } // 將下一頁的令牌解碼為 Id 和 SequenceId - private static (string lastId, long lastSequenceId) DecodePageToken(string nextToken) + public static (string lastId, long lastSequenceId) DecodePageToken(string nextToken) { if (string.IsNullOrEmpty(nextToken)) { @@ -102,7 +127,8 @@ private static (string lastId, long lastSequenceId) DecodePageToken(string nextT var base64Bytes = Convert.FromBase64String(nextToken); var json = Encoding.UTF8.GetString(base64Bytes); var jsonNode = JsonNode.Parse(json); - var jsonObject = jsonNode.AsObject(); + JsonObject jsonObject = jsonNode.AsObject(); + if (jsonObject.TryGetPropertyValue("lastSequenceId", out var lastSequenceIdNode)) { lastSequenceId = lastSequenceIdNode.GetValue(); diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Member/Repository/MemberDbContext.cs b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Member/Repository/MemberDbContext.cs index cb3541ed..23e81f42 100644 --- a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Member/Repository/MemberDbContext.cs +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Member/Repository/MemberDbContext.cs @@ -1,5 +1,4 @@ -using System.ComponentModel.DataAnnotations.Schema; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; namespace Lab.CursorPaging.WebApi.Member.Repository; @@ -30,6 +29,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity(builder => { + //property builder.ToTable("Member"); builder.HasKey(x => x.Id); builder.Property(x => x.Name).IsRequired(); @@ -39,6 +39,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) builder.Property(x => x.UpdatedAt).IsRequired(); builder.Property(x => x.UpdatedBy).IsRequired(); builder.Property(p => p.SequenceId).ValueGeneratedOnAdd(); + + //index + builder.HasIndex(x => x.SequenceId).IsUnique(); }); } } diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240106135936_InitialCreate.Designer.cs b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240107142909_InitialCreate.Designer.cs similarity index 95% rename from Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240106135936_InitialCreate.Designer.cs rename to Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240107142909_InitialCreate.Designer.cs index 98cd888c..67614025 100644 --- a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240106135936_InitialCreate.Designer.cs +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240107142909_InitialCreate.Designer.cs @@ -12,7 +12,7 @@ namespace Lab.CursorPaging.WebApi.Migrations { [DbContext(typeof(MemberDbContext))] - [Migration("20240106135936_InitialCreate")] + [Migration("20240107142909_InitialCreate")] partial class InitialCreate { /// @@ -68,6 +68,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("SequenceId") + .IsUnique(); + b.ToTable("Member", (string)null); }); #pragma warning restore 612, 618 diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240106135936_InitialCreate.cs b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240107142909_InitialCreate.cs similarity index 91% rename from Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240106135936_InitialCreate.cs rename to Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240107142909_InitialCreate.cs index 6f990125..bd56945c 100644 --- a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240106135936_InitialCreate.cs +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/20240107142909_InitialCreate.cs @@ -33,6 +33,12 @@ protected override void Up(MigrationBuilder migrationBuilder) { table.PrimaryKey("PK_Member", x => x.Id); }); + + migrationBuilder.CreateIndex( + name: "IX_Member_SequenceId", + table: "Member", + column: "SequenceId", + unique: true); } /// diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/MemberDbContextModelSnapshot.cs b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/MemberDbContextModelSnapshot.cs index d570c4be..39b91195 100644 --- a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/MemberDbContextModelSnapshot.cs +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Migrations/MemberDbContextModelSnapshot.cs @@ -65,6 +65,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("SequenceId") + .IsUnique(); + b.ToTable("Member", (string)null); }); #pragma warning restore 612, 618 From a6e6c839fb73b283f6be56460064a6e213a28395 Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 7 Jan 2024 22:37:17 +0800 Subject: [PATCH 358/424] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20cursor-bas?= =?UTF-8?q?ed=20=E5=AF=AB=E6=B3=95=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/MembersController.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs index 6d42ced1..4a4ca6cd 100644 --- a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Controllers/MembersController.cs @@ -86,6 +86,43 @@ public async Task>> GetCursor() return this.Ok(results); } + [HttpGet] + [Route("/api/members:cursor2")] + public async Task>> GetCursor2() + { + var pageSize = this.TryGetPageSize(); + long? nextPageId = this.Request.Headers.TryGetValue("X-Next-Page-Id", out var data) + ? long.TryParse(data.FirstOrDefault(), out var id) + ? id + : null + : null; + + await using var dbContext = await this._memberDbContextFactory.CreateDbContextAsync(); + var query = dbContext.Members.Select(p => p); + + if (nextPageId.HasValue) + { + query = query.Where(p => p.SequenceId > nextPageId); + } + + query = query.Take(pageSize + 1); + var results = await query.AsNoTracking().ToListAsync(); + + // 是否有下一頁 + bool hasNextPage = results.Count > pageSize; + + if (hasNextPage) + { + // 有下一頁,刪除最後一筆 + results.RemoveAt(results.Count - 1); + + var after = results.LastOrDefault(); + this.Response.Headers.Add("X-Next-Page-Id", after.SequenceId.ToString()); + } + + return this.Ok(results); + } + private int TryGetPageSize() => this.Request.Headers.TryGetValue("X-Page-Size", out var sizes) ? int.Parse(sizes.FirstOrDefault() ?? string.Empty) From 1fb3a8e86bf1de3f64222486e1bb695308701629 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 15 Jan 2024 00:30:14 +0800 Subject: [PATCH 359/424] refactor --- Shnapshot/Lab.Snapshot/.dockerignore | 25 ---- .../Lab.Snapshot.DB/MemberDbContext.cs | 22 ++++ .../Lab.Snapshot.DB/MemberDbContextFactory.cs | 15 +++ .../Lab.Snapshot.Test/UnitTest1.cs | 11 +- .../Controllers/MembersController.cs | 31 ++++- .../Lab.Snapshot.WebAPI/MemberRepository.cs | 107 +++++++++++++----- Shnapshot/Lab.Snapshot/Lab.Snapshot.sln | 5 + Shnapshot/Lab.Snapshot/docker-compose.yml | 8 ++ 8 files changed, 156 insertions(+), 68 deletions(-) delete mode 100644 Shnapshot/Lab.Snapshot/.dockerignore create mode 100644 Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContextFactory.cs create mode 100644 Shnapshot/Lab.Snapshot/docker-compose.yml diff --git a/Shnapshot/Lab.Snapshot/.dockerignore b/Shnapshot/Lab.Snapshot/.dockerignore deleted file mode 100644 index cd967fc3..00000000 --- a/Shnapshot/Lab.Snapshot/.dockerignore +++ /dev/null @@ -1,25 +0,0 @@ -**/.dockerignore -**/.env -**/.git -**/.gitignore -**/.project -**/.settings -**/.toolstarget -**/.vs -**/.vscode -**/.idea -**/*.*proj.user -**/*.dbmdl -**/*.jfm -**/azds.yaml -**/bin -**/charts -**/docker-compose* -**/Dockerfile* -**/node_modules -**/npm-debug.log -**/obj -**/secrets.dev.yaml -**/values.dev.yaml -LICENSE -README.md \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContext.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContext.cs index a0f3887d..89ead280 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContext.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContext.cs @@ -11,9 +11,31 @@ public class MemberDbContext : DbContext public DbSet Snapshots { get; set; } + private static readonly bool[] s_migrated = { false }; + public MemberDbContext(DbContextOptions options) : base(options) { + if (!s_migrated[0]) + { + lock (s_migrated) + { + if (!s_migrated[0]) + { + var migrations = this.Database.GetMigrations(); + if (migrations.Any()) + { + this.Database.Migrate(); + } + else + { + this.Database.EnsureCreated(); + } + + s_migrated[0] = true; + } + } + } } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContextFactory.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContextFactory.cs new file mode 100644 index 00000000..10d0d166 --- /dev/null +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.DB/MemberDbContextFactory.cs @@ -0,0 +1,15 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace Lab.Snapshot.DB; + +public class MemberDbContextFactory : IDesignTimeDbContextFactory +{ + public MemberDbContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseNpgsql("Host=localhost;Database=member;Username=postgres;Password=guest"); + + return new MemberDbContext(optionsBuilder.Options); + } +} \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs index fd5a7929..514e1f03 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.Test/UnitTest1.cs @@ -449,16 +449,7 @@ public async Task SystemTextJson_RestoreInDb() var expected = await dbContext.Members.FirstOrDefaultAsync(p => p.Id == "1"); actual.Should().BeEquivalentTo(expected); } - - private static string ToJsonString(JsonDocument doc) - { - using var stream = new MemoryStream(); - var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }); - doc.WriteTo(writer); - writer.Flush(); - return Encoding.UTF8.GetString(stream.ToArray()); - } - + private async Task Update(MemberDataEntity newMember) { var now = TestAssistant.Now; diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Controllers/MembersController.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Controllers/MembersController.cs index b277772d..56ec39d2 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Controllers/MembersController.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/Controllers/MembersController.cs @@ -56,10 +56,35 @@ public async Task> Bind(string currentAccount, Upda return this.NoContent(); } - [HttpGet("{account}", Name = "GetMemberByAccount")] - public async Task> Get(string account, int? version) + [HttpGet("{account}:query-account-id", Name = "QueryMemberByAccount")] + public async Task> QueryMemberByAccountAsync(string account, int? version) { - var getMemberResult = await this._memberRepository.GetMemberAsync(account, version); + var queryMemberResult = await this._memberRepository.QueryMemberByAccountAsync(account, version); + if (queryMemberResult == null) + { + return this.NotFound(); + } + + return this.Ok(queryMemberResult); + } + + [HttpGet(Name = "GetMembers")] + //todo:待實作分頁 + public async Task> GetMembersAsync() + { + var getMemberResult = await this._memberRepository.GetMembersAsync(); + if (getMemberResult == null) + { + return this.NotFound(); + } + + return this.Ok(getMemberResult); + } + + [HttpGet("{id}", Name = "GetMember")] + public async Task> GetMemberAsync(string id, int? version) + { + var getMemberResult = await this._memberRepository.GetMemberAsync(id, version); if (getMemberResult == null) { return this.NotFound(); diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs index b54816df..39bbd1e3 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.WebAPI/MemberRepository.cs @@ -87,7 +87,8 @@ public MemberRepository(IDbContextFactory memberDbContextFactor this.UpdateProfile(request.Profile, newMember); // 比對兩個 member 內容是否有差異 - if (this.Diff(oldMember, newMember).Result == false) + var diffResult = this.Diff(oldMember, newMember); + if (diffResult.Result == false) { // 沒有差異,不做任何事 return (null, true); @@ -99,20 +100,20 @@ public MemberRepository(IDbContextFactory memberDbContextFactor newMember.Version = oldMember.Version + 1; // 產生差異內容 - var diff = this.Diff(oldMember, newMember).Data; + var diffData = diffResult.Data; var snapshot = new SnapshotDataEntity { Id = newMember.Id, DataType = typeof(MemberDataEntity).ToString(), - Data = diff, + Data = diffData, DataFormat = DataFormat.Diff.ToString(), CreatedAt = newMember.UpdatedAt, CreatedBy = newMember.UpdatedBy, Version = newMember.Version }; - // 更新時,可以使用樂觀鎖定,Update Where Version=oldMember.Version,這裡我沒有實作 + // 更新時,可以使用樂觀鎖定,Update Where Version=oldMember.Version,或是悲觀鎖,這裡我沒有實作 var entry = dbContext.Members.Entry(oldMember); entry.CurrentValues.SetValues(newMember); @@ -128,7 +129,7 @@ public MemberRepository(IDbContextFactory memberDbContextFactor var diff = JsonDiffPatcher.Diff(oldData, newData, new JsonDiffOptions { - JsonElementComparison = JsonElementComparison.Semantic, + JsonElementComparison = JsonElementComparison.RawText, }); if (diff == null) @@ -184,34 +185,32 @@ public MemberDataEntity DeepClone(MemberDataEntity oldMember) return newMember; } - public async Task GetMemberAsync(string account, int? version) + public async Task QueryMemberByAccountAsync(string account, int? version) { - var search = $"'[{{{{\"id\": \"{account}\"}}}}]'"; + var search = $"[{{\"Id\": \"{account}\"}}]"; await using var dbContext = await this._memberDbContextFactory.CreateDbContextAsync(); // 讀取最新資料 - if (version.HasValue == false) + var queryable = from member in dbContext.Members + where EF.Functions.JsonContains(member.Accounts, search) + select new { member }; + var latestMember = await queryable.AsNoTracking().FirstOrDefaultAsync(); + if (latestMember == null) { - var queryable = from member in dbContext.Members - where EF.Functions.JsonContains(member.Accounts, search) - select new { member }; - var data = await queryable.AsNoTracking().FirstOrDefaultAsync(); - if (data == null) - { - return null; - } + return null; + } - return this._mapper.Map(data.member); + if (version.HasValue == false) + { + return this._mapper.Map(latestMember.member); } // 讀取快照 - var query = $@"select * from ""Snapshot"" where ""Data"" -> 'accounts' @> {search}::jsonb"; - - var snapshots = await dbContext.Snapshots - .FromSqlRaw(query) - .ToListAsync() + var query = dbContext.Snapshots + .Where(p => p.Id == latestMember.member.Id) + .Where(p => p.Version <= version.Value) ; - + var snapshots = await query.AsNoTracking().ToListAsync(); JsonNode finial = null; // 依序合併快照 @@ -232,17 +231,65 @@ where EF.Functions.JsonContains(member.Accounts, search) return null; } - var accounts = finial["accounts"]!.AsArray(); - foreach (var item in accounts) + var lastSnapshot = snapshots.Last(); + var result = finial.Deserialize(this._jsonSerializerOptions); + result.CreatedAt = latestMember.member.CreatedAt; + result.CreatedBy = latestMember.member.CreatedBy; + result.UpdatedAt = lastSnapshot.CreatedAt; + result.UpdatedBy = lastSnapshot.CreatedBy; + result.Version = lastSnapshot.Version; + return result; + } + + public async Task GetMemberAsync(string id, int? version) + { + await using var dbContext = await this._memberDbContextFactory.CreateDbContextAsync(); + if (version.HasValue == false) + { + var query = dbContext.Members.Where(p => p.Id == id) + ; + var data = await query.AsNoTracking().FirstOrDefaultAsync(); + if (data == null) + { + return null; + } + + return this._mapper.Map(data); + } + else { - var id = item["id"]!.GetValue(); - if (id == account) + var query = dbContext.Snapshots.Where(p => p.Id == id) + .Where(p => p.Version <= version.Value) + ; + var snapshots = await query.AsNoTracking().ToListAsync(); + + JsonNode finial = null; + + // 依序合併快照 + foreach (var snapshot in snapshots) + { + if (snapshot.Version == 1) + { + finial = snapshot.Data; + continue; + } + + JsonDiffPatcher.Patch(ref finial, snapshot.Data); + } + + if (finial == null) { - var result = finial.Deserialize(this._jsonSerializerOptions); - return result; + return null; } + + return finial.Deserialize(this._jsonSerializerOptions); } + } - return null; + public async Task> GetMembersAsync() + { + await using var dbContext = await this._memberDbContextFactory.CreateDbContextAsync(); + var data = await dbContext.Members.AsNoTracking().ToListAsync(); + return this._mapper.Map>(data); } } \ No newline at end of file diff --git a/Shnapshot/Lab.Snapshot/Lab.Snapshot.sln b/Shnapshot/Lab.Snapshot/Lab.Snapshot.sln index 3734c2b2..e989cbc8 100644 --- a/Shnapshot/Lab.Snapshot/Lab.Snapshot.sln +++ b/Shnapshot/Lab.Snapshot/Lab.Snapshot.sln @@ -6,6 +6,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Snapshot.DB", "Lab.Snap EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.Snapshot.WebAPI", "Lab.Snapshot.WebAPI\Lab.Snapshot.WebAPI.csproj", "{2DC3F6CC-DBE5-4539-81EC-7368CA927387}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A1B3C0E5-A98D-4257-A5FC-22CD9720A0EE}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/Shnapshot/Lab.Snapshot/docker-compose.yml b/Shnapshot/Lab.Snapshot/docker-compose.yml new file mode 100644 index 00000000..a760b5eb --- /dev/null +++ b/Shnapshot/Lab.Snapshot/docker-compose.yml @@ -0,0 +1,8 @@ +services: + db: + image: postgres + container_name: postgres-latest + environment: + - POSTGRES_PASSWORD=guest + ports: + - 5432:5432 \ No newline at end of file From d51340288bcbd50972d43fa13050c17204421ce9 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 22 Jan 2024 01:47:17 +0800 Subject: [PATCH 360/424] =?UTF-8?q?feat:=20HttpClient=20=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=20Cookie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Lab.HttpClientWithCookie/.gitignore | 671 ++++++++++++++++++ .../Lab.HttpClientWithCookie/Taskfile.yml | 27 + .../src/Lab.HttpClientWithCookie.sln | 28 + .../WebAppA/Controllers/Demo1Controller.cs | 179 +++++ .../src/WebAppA/Program.cs | 113 +++ .../src/WebAppA/WebAppA.csproj | 16 + .../src/WebAppA/appsettings.Development.json | 8 + .../src/WebAppA/appsettings.json | 9 + .../WebAppB/Controllers/Demo2Controller.cs | 142 ++++ .../src/WebAppB/Program.cs | 72 ++ .../src/WebAppB/WebAppB.csproj | 16 + .../src/WebAppB/appsettings.Development.json | 8 + .../src/WebAppB/appsettings.json | 9 + 13 files changed, 1298 insertions(+) create mode 100644 Http Client/Lab.HttpClientWithCookie/.gitignore create mode 100644 Http Client/Lab.HttpClientWithCookie/Taskfile.yml create mode 100644 Http Client/Lab.HttpClientWithCookie/src/Lab.HttpClientWithCookie.sln create mode 100644 Http Client/Lab.HttpClientWithCookie/src/WebAppA/Controllers/Demo1Controller.cs create mode 100644 Http Client/Lab.HttpClientWithCookie/src/WebAppA/Program.cs create mode 100644 Http Client/Lab.HttpClientWithCookie/src/WebAppA/WebAppA.csproj create mode 100644 Http Client/Lab.HttpClientWithCookie/src/WebAppA/appsettings.Development.json create mode 100644 Http Client/Lab.HttpClientWithCookie/src/WebAppA/appsettings.json create mode 100644 Http Client/Lab.HttpClientWithCookie/src/WebAppB/Controllers/Demo2Controller.cs create mode 100644 Http Client/Lab.HttpClientWithCookie/src/WebAppB/Program.cs create mode 100644 Http Client/Lab.HttpClientWithCookie/src/WebAppB/WebAppB.csproj create mode 100644 Http Client/Lab.HttpClientWithCookie/src/WebAppB/appsettings.Development.json create mode 100644 Http Client/Lab.HttpClientWithCookie/src/WebAppB/appsettings.json diff --git a/Http Client/Lab.HttpClientWithCookie/.gitignore b/Http Client/Lab.HttpClientWithCookie/.gitignore new file mode 100644 index 00000000..333fe87e --- /dev/null +++ b/Http Client/Lab.HttpClientWithCookie/.gitignore @@ -0,0 +1,671 @@ +### ASPNETCore template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/ + +### Csharp template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +./logs/ \ No newline at end of file diff --git a/Http Client/Lab.HttpClientWithCookie/Taskfile.yml b/Http Client/Lab.HttpClientWithCookie/Taskfile.yml new file mode 100644 index 00000000..37d900a6 --- /dev/null +++ b/Http Client/Lab.HttpClientWithCookie/Taskfile.yml @@ -0,0 +1,27 @@ +# Taskfile.yml + +version: "3" + +tasks: + demo: + desc: WebApi Development + cmds: + - task: demo1 + - task: demo2 + + WebAppA: + desc: WebApi Development + dir: "src/WebAppA" + cmds: + - dotnet run --environment Staging + + WebAppB: + desc: WebApi Development + dir: "src/WebAppB" + cmds: + - dotnet run --environment Staging + + seq-start: + desc: start seq service + cmds: + - docker run --name seq -d --restart unless-stopped -e ACCEPT_EULA=Y -p 5341:80 datalust/seq:latest \ No newline at end of file diff --git a/Http Client/Lab.HttpClientWithCookie/src/Lab.HttpClientWithCookie.sln b/Http Client/Lab.HttpClientWithCookie/src/Lab.HttpClientWithCookie.sln new file mode 100644 index 00000000..4662c59b --- /dev/null +++ b/Http Client/Lab.HttpClientWithCookie/src/Lab.HttpClientWithCookie.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAppA", "WebAppA\WebAppA.csproj", "{CF4A808A-1217-4CD6-9B14-483E218C7497}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAppB", "WebAppB\WebAppB.csproj", "{C54987B2-7F8B-400E-B210-C4EE5460F1B7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{2076DDF6-5000-4E44-8CE1-4320085633D4}" + ProjectSection(SolutionItems) = preProject + ..\Taskfile.yml = ..\Taskfile.yml + ..\.gitignore = ..\.gitignore + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CF4A808A-1217-4CD6-9B14-483E218C7497}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF4A808A-1217-4CD6-9B14-483E218C7497}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF4A808A-1217-4CD6-9B14-483E218C7497}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF4A808A-1217-4CD6-9B14-483E218C7497}.Release|Any CPU.Build.0 = Release|Any CPU + {C54987B2-7F8B-400E-B210-C4EE5460F1B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C54987B2-7F8B-400E-B210-C4EE5460F1B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C54987B2-7F8B-400E-B210-C4EE5460F1B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C54987B2-7F8B-400E-B210-C4EE5460F1B7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Http Client/Lab.HttpClientWithCookie/src/WebAppA/Controllers/Demo1Controller.cs b/Http Client/Lab.HttpClientWithCookie/src/WebAppA/Controllers/Demo1Controller.cs new file mode 100644 index 00000000..b052af54 --- /dev/null +++ b/Http Client/Lab.HttpClientWithCookie/src/WebAppA/Controllers/Demo1Controller.cs @@ -0,0 +1,179 @@ +using System.Diagnostics; +using System.Text.Json; +using Microsoft.AspNetCore.Mvc; + +namespace WebAppA.Controllers; + +public class LabService : ILabService +{ + private readonly HttpClient _client; + + public LabService(HttpClient client) + { + this._client = client; + } + + public IEnumerable Get() + { + var url = "api/default"; + var response = this._client.GetAsync(url).GetAwaiter().GetResult(); + response.EnsureSuccessStatusCode(); + + var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + var result = JsonSerializer.Deserialize(content); + + return result; + } +} + +public interface ILabService +{ + IEnumerable Get(); +} + +[ApiController] + +// [Route("[controller]")] +public class Demo1Controller : ControllerBase +{ + private readonly ILogger _logger; + private readonly HttpClient _client; + + private readonly HttpClient s_client = new HttpClient + { + BaseAddress = new Uri(serverUrl), + }; + + private readonly IHttpClientFactory _httpClientFactory; + static string serverUrl = "https://localhost:7004/"; + private ILabService _service; + + // public Demo1Controller(ILogger logger, ILabService service) + // { + // this._logger = logger; + // this._service = service; + // } + + public Demo1Controller(ILogger logger, IHttpClientFactory httpClientFactory) + { + this._logger = logger; + this._httpClientFactory = httpClientFactory; + } + + [HttpGet] + [Route("/api/demo1")] + public async Task Get() + { + var requestId = this.HttpContext.TraceIdentifier; + + //header + if (this.Request.Headers.Any()) + { + this._logger.LogInformation("1.Id={@RequestId}, At 'Demo1', Receive request headers ={@Headers}", requestId, + this.Request.Headers); + } + + //cookie + if (this.Request.Cookies.Any()) + { + this._logger.LogInformation("2.Id={@RequestId}, At 'Demo1', Receive request cookie ={@Cookies}", requestId, + this.Request.Cookies); + } + + var url = "api/Demo2"; + + // var client = this._client; + + // var client = this.s_client; + + var client = this._httpClientFactory.CreateClient("lab"); + + // var client = new HttpClient() + // { + // BaseAddress = new Uri(serverUrl) + // }; + + //送出並行請求 + // await SendMultiRequest(client, url); + + var request = new HttpRequestMessage(HttpMethod.Get, url) + { + Headers = + { + { "A", "123" }, + } + }; + var response = await client.SendAsync(request); + + if (response.Headers.Any()) + { + this._logger.LogInformation("3.Id={@RequestId}, At 'Demo1', Receive response headers ={@Headers}", + requestId, + response.Headers); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + if (string.IsNullOrWhiteSpace(responseContent) == false) + { + this._logger.LogInformation("4.Id={@RequestId}, At 'Demo1', Receive response body ={@Body}", + requestId, + responseContent); + } + + // if (response.Headers.TryGetValues("A", out var a2)) + // { + // this._logger.LogInformation("Id={RequestId},Response Header 'A'={Data}", requestId, a2); + // } + // + // if (response.Headers.TryGetValues("B", out var b2)) + // { + // this._logger.LogInformation("Id={RequestId},Response Header 'B'={Data}", requestId, b2); + // } + // + // if (response.Headers.TryGetValues("C", out var c2)) + // { + // this._logger.LogInformation("Id={RequestId},Response Header 'C'={Data}", requestId, c2); + // } + // + // if (response.Headers.TryGetValues("D", out var d2)) + // { + // this._logger.LogInformation("Id={RequestId},Response Header 'D'={Data}", requestId, d2); + // } + // + // //取得 Response Cookies + // if (response.Headers.TryGetValues("Set-Cookie", out var cookies)) + // { + // this._logger.LogInformation("Id={RequestId},Response Cookie 'All Cookie'={Data}", requestId, cookies); + // } + return this.Ok(); + } + + private static async Task SendParallelRequest(HttpClient client, string url) + { + var processTime = new Stopwatch(); + processTime.Start(); + var tasks = new List(); + while (true) + { + //送出並行請求 + var request = new HttpRequestMessage(HttpMethod.Get, url) + { + Headers = + { + { "A", "123" }, + } + }; + + var task = client.SendAsync(request); + tasks.Add(task); + if (processTime.Elapsed.TotalSeconds > 5) + { + //五秒後就停止 + processTime.Stop(); + break; + } + } + + await Task.WhenAll(tasks); + } +} \ No newline at end of file diff --git a/Http Client/Lab.HttpClientWithCookie/src/WebAppA/Program.cs b/Http Client/Lab.HttpClientWithCookie/src/WebAppA/Program.cs new file mode 100644 index 00000000..9aa06241 --- /dev/null +++ b/Http Client/Lab.HttpClientWithCookie/src/WebAppA/Program.cs @@ -0,0 +1,113 @@ +using Serilog; +using Serilog.Events; +using Serilog.Formatting.Json; +using WebAppA.Controllers; + +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.File("logs/host-.txt", rollingInterval: RollingInterval.Day) + .CreateBootstrapLogger() + ; +try +{ + Log.Information("Starting web host"); + + var builder = WebApplication.CreateBuilder(args); + + // builder.Host.UseSerilog(); //<=== 讓 Host 使用 Serilog + var formatter = new JsonFormatter(); + + // var formatter = new MessageTemplateTextFormatter(); + // var formatter = new RawFormatter(); + // var formatter = new RenderedCompactJsonFormatter(); + // var formatter = new CompactJsonFormatter(); + // var formatter = new ExpressionTemplate( + // "{ {_t: @t, _msg: @m, _props: @p} }\n"); + builder.Host.UseSerilog((context, services, config) => + { + config.ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext() + .WriteTo.Console(formatter) + .WriteTo.Seq("http://localhost:5341") + .WriteTo.File(formatter, "logs/aspnet-.txt", rollingInterval: RollingInterval.Minute); + }); + + // Add services to the container. + + builder.Services.AddControllers(); + + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + + var serverUrl = "https://localhost:7004/"; + + // // ---會快取 cookie--- + // 建構子依賴 IHttpClientFactory + builder.Services.AddHttpClient("lab", + p => { p.BaseAddress = new Uri(serverUrl); }); + // // ---會快取 cookie--- + + // // ---不會快取 cookie--- + // // 建構子依賴 HttpClient + // builder.Services.AddSingleton(p => + // { + // var socketsHandler = new SocketsHttpHandler(); + // return new HttpClient(socketsHandler) + // { + // BaseAddress = new Uri(serverUrl) + // }; + // }); + // // ---不會快取 cookie--- + + // // ---不會快取 cookie--- + // builder.Services.AddHttpClient("lab", + // p => { p.BaseAddress = new Uri(serverUrl); }) + // .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler() + // { + // // 改成 true,會快取 Cookie + // UseCookies = false, + // }); + // // ---不會快取 cookie--- + + // // ---不會快取 cookie--- + // builder.Services.AddSingleton(p => new HttpClient + // { + // BaseAddress = new Uri(serverUrl) + // }); + // // ---不會快取 cookie--- + + builder.Services.AddScoped(); + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + + app.UseHttpsRedirection(); + + // app.UseSerilogRequestLogging(); //<=== 每一個 Request 使用 Serilog 記錄下來 + + app.UseAuthorization(); + + app.MapControllers(); + + app.Run(); + return 0; +} +catch (Exception ex) +{ + Log.Fatal(ex, "Host terminated unexpectedly"); + return 1; +} +finally +{ + Log.CloseAndFlush(); +} \ No newline at end of file diff --git a/Http Client/Lab.HttpClientWithCookie/src/WebAppA/WebAppA.csproj b/Http Client/Lab.HttpClientWithCookie/src/WebAppA/WebAppA.csproj new file mode 100644 index 00000000..901aaf53 --- /dev/null +++ b/Http Client/Lab.HttpClientWithCookie/src/WebAppA/WebAppA.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + + + + + + + + + + diff --git a/Http Client/Lab.HttpClientWithCookie/src/WebAppA/appsettings.Development.json b/Http Client/Lab.HttpClientWithCookie/src/WebAppA/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Http Client/Lab.HttpClientWithCookie/src/WebAppA/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Http Client/Lab.HttpClientWithCookie/src/WebAppA/appsettings.json b/Http Client/Lab.HttpClientWithCookie/src/WebAppA/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Http Client/Lab.HttpClientWithCookie/src/WebAppA/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Http Client/Lab.HttpClientWithCookie/src/WebAppB/Controllers/Demo2Controller.cs b/Http Client/Lab.HttpClientWithCookie/src/WebAppB/Controllers/Demo2Controller.cs new file mode 100644 index 00000000..5d4df90f --- /dev/null +++ b/Http Client/Lab.HttpClientWithCookie/src/WebAppB/Controllers/Demo2Controller.cs @@ -0,0 +1,142 @@ +using Microsoft.AspNetCore.Mvc; + +namespace WebAppB.Controllers; + +[ApiController] +public class Demo2Controller : ControllerBase +{ + private readonly ILogger _logger; + + public Demo2Controller(ILogger logger) + { + this._logger = logger; + } + + static long s_counter = 1; + static object s_lock = new(); + static Random s_random = new(); + + [HttpGet] + [Route("/api/demo2")] + public async Task Get() + { + var requestId = this.HttpContext.TraceIdentifier; + + var cookies = this.ShouldWithoutCookies(); + var headers = this.ShouldWithoutHeaders(); + if (cookies.Any() + || headers.Any()) + { + return this.Ok(new + { + Headers = headers.Any() ? headers : null, + Cookies = cookies.Any() ? cookies : null, + }); + } + + if (this.Request.Headers.TryGetValue("A", out var context)) + { + var identifier = this.HttpContext.TraceIdentifier; + var data = context.FirstOrDefault(); + + //取得 Request Id + this._logger.LogInformation("1.Id={@RequestId}, At 'Demo2', Receive request headers[A] ={@Data}", + identifier, + data); + + this.Response.Headers.Add("B", data); + this.Response.Cookies.Append("B", data); + + //每一個請求都會有一個獨立的自動增量數字 + lock (s_lock) + { + s_counter++; + this.Response.Headers.Add("C", s_counter.ToString()); + this.Response.Cookies.Append("C", s_counter.ToString()); + } + + this.Response.Headers.Add("D", s_random.NextInt64(1000).ToString()); + this.Response.Cookies.Append("D", s_random.NextInt64(1000).ToString()); + } + + return this.NoContent(); + } + + private Dictionary ShouldWithoutHeaders() + { + var identifier = this.HttpContext.TraceIdentifier; + var result = new Dictionary(); + if (this.Request.Headers.TryGetValue("B", out var b)) + { + this._logger.LogError("1.Id={RequestId}, At 'Demo2', Receive request headers[B] ={Data}", + identifier, + b); + result.Add("B", b); + } + + if (this.Request.Headers.TryGetValue("C", out var c)) + { + this._logger.LogError("2.Id={RequestId}, At 'Demo2', Receive request headers[C] ={Data}", + identifier, + c); + result.Add("C", c); + } + + if (this.Request.Headers.TryGetValue("D", out var d)) + { + this._logger.LogError("3.Id={RequestId}, At 'Demo2', Receive request headers[D] ={Data}", + identifier, + d); + result.Add("D", d); + } + + return result; + } + + private Dictionary ShouldWithoutCookies() + { + var result = new Dictionary(); + var identifier = this.HttpContext.TraceIdentifier; + if (this.Request.Cookies.TryGetValue("B", out var b)) + { + this._logger.LogError("1.Id={RequestId}, At 'Demo2', Receive request cookies[B] ={Data}", + identifier, + b); + result.Add("B", b); + } + + if (this.Request.Cookies.TryGetValue("C", out var c)) + { + this._logger.LogError("2.Id={RequestId}, At 'Demo2', Receive request cookies[C] ={Data}", + identifier, + c); + result.Add("C", c); + } + + if (this.Request.Cookies.TryGetValue("D", out var d)) + { + this._logger.LogError("3.Id={RequestId}, At 'Demo2', Receive request cookies[D] ={Data}", + identifier, + d); + result.Add("D", d); + } + + if (this.Request.Cookies.TryGetValue("E", out var e)) + { + this._logger.LogError("4.Id={RequestId}, At 'Demo2', Receive request cookies[E] ={Data}", + identifier, + e); + result.Add("E", e); + } + + if (this.Request.Cookies.TryGetValue("5.F", out var f)) + { + this._logger.LogError("Id={RequestId}, At 'Demo2', Receive request cookies[F] ={Data}", + identifier, + f); + result.Add("F", f); + } + + return result; + } +} \ No newline at end of file diff --git a/Http Client/Lab.HttpClientWithCookie/src/WebAppB/Program.cs b/Http Client/Lab.HttpClientWithCookie/src/WebAppB/Program.cs new file mode 100644 index 00000000..712fb31f --- /dev/null +++ b/Http Client/Lab.HttpClientWithCookie/src/WebAppB/Program.cs @@ -0,0 +1,72 @@ +using Serilog; +using Serilog.Events; +using Serilog.Formatting.Json; + +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.File("logs/host-.txt", rollingInterval: RollingInterval.Day) + .CreateBootstrapLogger() + ; +try +{ + Log.Information("Starting web host"); + + var builder = WebApplication.CreateBuilder(args); + + // builder.Host.UseSerilog(); //<=== 讓 Host 使用 Serilog + var formatter = new JsonFormatter(); + // var formatter = new MessageTemplateTextFormatter(); + // var formatter = new RawFormatter(); + // var formatter = new RenderedCompactJsonFormatter(); + // var formatter = new CompactJsonFormatter(); + // var formatter = new ExpressionTemplate( + // "{ {_t: @t, _msg: @m, _props: @p} }\n"); + builder.Host.UseSerilog((context, services, config) => + { + config.ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext() + .WriteTo.Console(formatter) + .WriteTo.Seq("http://localhost:5341") + .WriteTo.File(formatter, "logs/aspnet-.txt", rollingInterval: RollingInterval.Minute); + }); + + // Add services to the container. + + builder.Services.AddControllers(); + + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + + app.UseHttpsRedirection(); + // app.UseSerilogRequestLogging(); //<=== 每一個 Request 使用 Serilog 記錄下來 + + app.UseAuthorization(); + + app.MapControllers(); + + app.Run(); + return 0; +} +catch (Exception ex) +{ + Log.Fatal(ex, "Host terminated unexpectedly"); + return 1; +} +finally +{ + Log.CloseAndFlush(); +} \ No newline at end of file diff --git a/Http Client/Lab.HttpClientWithCookie/src/WebAppB/WebAppB.csproj b/Http Client/Lab.HttpClientWithCookie/src/WebAppB/WebAppB.csproj new file mode 100644 index 00000000..901aaf53 --- /dev/null +++ b/Http Client/Lab.HttpClientWithCookie/src/WebAppB/WebAppB.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + + + + + + + + + + diff --git a/Http Client/Lab.HttpClientWithCookie/src/WebAppB/appsettings.Development.json b/Http Client/Lab.HttpClientWithCookie/src/WebAppB/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Http Client/Lab.HttpClientWithCookie/src/WebAppB/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Http Client/Lab.HttpClientWithCookie/src/WebAppB/appsettings.json b/Http Client/Lab.HttpClientWithCookie/src/WebAppB/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Http Client/Lab.HttpClientWithCookie/src/WebAppB/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From 68f0f41431fd507143491f4cc48e7fba8b82ef3d Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 22 Jan 2024 01:55:18 +0800 Subject: [PATCH 361/424] refactor --- .../src/WebAppB/Controllers/Demo2Controller.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Http Client/Lab.HttpClientWithCookie/src/WebAppB/Controllers/Demo2Controller.cs b/Http Client/Lab.HttpClientWithCookie/src/WebAppB/Controllers/Demo2Controller.cs index 5d4df90f..40d4f37e 100644 --- a/Http Client/Lab.HttpClientWithCookie/src/WebAppB/Controllers/Demo2Controller.cs +++ b/Http Client/Lab.HttpClientWithCookie/src/WebAppB/Controllers/Demo2Controller.cs @@ -20,13 +20,14 @@ public Demo2Controller(ILogger logger) [Route("/api/demo2")] public async Task Get() { - var requestId = this.HttpContext.TraceIdentifier; + var identifier = this.HttpContext.TraceIdentifier; var cookies = this.ShouldWithoutCookies(); var headers = this.ShouldWithoutHeaders(); if (cookies.Any() || headers.Any()) { + // 找到非預期的 header/cookie 則回傳 return this.Ok(new { Headers = headers.Any() ? headers : null, @@ -36,7 +37,6 @@ public async Task Get() if (this.Request.Headers.TryGetValue("A", out var context)) { - var identifier = this.HttpContext.TraceIdentifier; var data = context.FirstOrDefault(); //取得 Request Id From a7874192c4f732d0aec254d6b608a7d5b309e6b1 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 1 Feb 2024 19:41:14 +0800 Subject: [PATCH 362/424] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=89=B9=E6=AC=A1?= =?UTF-8?q?=E4=B8=8A=E5=82=B3=E7=AF=84=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Lab.Aws.S3.MinIOS3.csproj | 6 ++ AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs | 75 +++++++++++++++++-- .../\344\270\212\345\202\263.csv" | 5 ++ 3 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 "AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/\344\270\212\345\202\263.csv" diff --git a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Lab.Aws.S3.MinIOS3.csproj b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Lab.Aws.S3.MinIOS3.csproj index 547f14e9..e6b043df 100644 --- a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Lab.Aws.S3.MinIOS3.csproj +++ b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Lab.Aws.S3.MinIOS3.csproj @@ -16,4 +16,10 @@ + + + Always + + + diff --git a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs index 1e2f17d0..89be5457 100644 --- a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs +++ b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs @@ -1,6 +1,6 @@ using System.Collections.Concurrent; using System.Reflection; -using Amazon; +using Amazon.Runtime; using Amazon.S3; using Amazon.S3.Model; @@ -71,16 +71,75 @@ public void test() [TestMethod] public async Task 新增一個儲存桶() { - var s3Config = new AmazonS3Config() - { - RegionEndpoint = RegionEndpoint.USEast1, - ServiceURL = "http://localhost:9000", - ForcePathStyle = true - }; - var s3Client = new AmazonS3Client(s3Config); + var s3Client = CreateS3Client(); var response = await s3Client.PutBucketAsync(new PutBucketRequest { BucketName = "test-bucket", }); } + + [TestMethod] + public async Task 分批上傳檔案() + { + await WriteObjectDataAsync("上傳.csv"); + } + [TestMethod] + public async Task 分批下載檔案() + { + await WriteObjectDataAsync("上傳.csv"); + } + + static async Task WriteObjectDataAsync(string filePath) + { + var s3Client = CreateS3Client(); + var writerStream = new MemoryStream(); + var writer = new StreamWriter(writerStream) + { + AutoFlush = true + }; + try + { + await using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); + using var reader = new StreamReader(fileStream); + var lineCount = 0; + var lineLimit = 10; + var putRequest = new PutObjectRequest() + { + BucketName = "test-bucket", + Key = $"test.csv", + UseChunkEncoding = true, + AutoCloseStream = false, + AutoResetStreamPosition = true, + }; + while (await reader.ReadLineAsync() is { } line) + { + // todo:可以處理一整批後再寫入到 s3 + await writer.WriteLineAsync(line); + putRequest.InputStream = writerStream; + var response = await s3Client.PutObjectAsync(putRequest); + lineCount++; + } + } + catch (AmazonS3Exception e) + { + Console.WriteLine("Error encountered on server. Message:'{0}' when writing object", e.Message); + } + catch (Exception e) + { + Console.WriteLine("Unknown encountered on server. Message:'{0}' when writing object", e.Message); + } + } + + static AmazonS3Client CreateS3Client() + { + var credentials = new BasicAWSCredentials("AKIAIOSFODNN7EXAMPLE", + "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"); + + var s3Config = new AmazonS3Config + { + ServiceURL = "http://localhost:9000", + ForcePathStyle = true + }; + return new AmazonS3Client(credentials, s3Config); + } } \ No newline at end of file diff --git "a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/\344\270\212\345\202\263.csv" "b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/\344\270\212\345\202\263.csv" new file mode 100644 index 00000000..728912cc --- /dev/null +++ "b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/\344\270\212\345\202\263.csv" @@ -0,0 +1,5 @@ +id,name,address,age +1d290be4-9573-4e76-9f4c-4daa679caaad,Name1,Address1,45 +6dcd4ce0-df34-4e12-9ec8-2d1bf3be287c,Name2,Address2,20 +8bf2a7cb-2178-4f56-8653-67c58c2f8725,Name3,Address3,68 +8bf2a7cb-2178-4f56-8653-67c58c2f8725,Name4,Address3,68 From d659555902b687d2449f9efcb899f7829d241f4d Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 1 Feb 2024 21:28:46 +0800 Subject: [PATCH 363/424] =?UTF-8?q?feat:=20=E5=88=86=E6=89=B9=E4=B8=8B?= =?UTF-8?q?=E8=BC=89=E6=AA=94=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs | 143 +++++++++++++++++- 1 file changed, 139 insertions(+), 4 deletions(-) diff --git a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs index 89be5457..158d75fc 100644 --- a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs +++ b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs @@ -1,8 +1,10 @@ using System.Collections.Concurrent; using System.Reflection; +using System.Text; using Amazon.Runtime; using Amazon.S3; using Amazon.S3.Model; +using Amazon.S3.Transfer; namespace Lab.Aws.S3.MinIOS3; @@ -83,15 +85,148 @@ public async Task 分批上傳檔案() { await WriteObjectDataAsync("上傳.csv"); } + [TestMethod] public async Task 分批下載檔案() { - await WriteObjectDataAsync("上傳.csv"); + await ReadObjectDataAsync2("上傳.csv", "下載.csv"); + } + + static async Task ReadObjectDataAsync2(string sourceFile, string outFile) + { + var bucketName = "test-bucket"; + var key = sourceFile; + + try + { + var s3Client = CreateS3Client(); + + long startPosition = 0; + long chunkSize = 4096; // 每次讀取的大小為 4096 bytes + + // 確定檔案的大小 + long fileSize = await GetFileSizeAsync(s3Client, bucketName, key); + + using (var outputStream = new StreamWriter(outFile)) + { + while (startPosition < fileSize) + { + long endPosition = Math.Min(startPosition + chunkSize, fileSize) - 1; + + // 設定 ByteRange + var getObjectRequest = new GetObjectRequest + { + BucketName = bucketName, + Key = key, + ByteRange = new ByteRange(startPosition, endPosition) + }; + + using (var response = await s3Client.GetObjectAsync(getObjectRequest)) + using (var reader = new StreamReader(response.ResponseStream)) + { + // 處理部分檔案內容 + var buffer = new char[chunkSize]; + int bytesRead; + string line = string.Empty; + while ((bytesRead = await reader.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + line += new string(buffer, 0, bytesRead); + int newlineIndex = line.IndexOf('\n'); + if (newlineIndex >= 0) + { + await outputStream.WriteAsync(line.Substring(0, newlineIndex + 1)); + line = line.Substring(newlineIndex + 1); + } + } + + // 處理剩餘的部分,如果有 + if (!string.IsNullOrEmpty(line)) + { + await outputStream.WriteLineAsync(line); // 添加換行符號 + } + } + + startPosition += chunkSize; + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + } + + private static async Task GetFileSizeAsync(IAmazonS3 s3Client, string bucketName, string objectKey) + { + var metadataRequest = new GetObjectMetadataRequest + { + BucketName = bucketName, + Key = objectKey + }; + + var response = await s3Client.GetObjectMetadataAsync(metadataRequest); + return response.ContentLength; + } + + static async Task ReadObjectDataAsync(string filePath) + { + var s3Client = CreateS3Client(); + var bucketName = "test-bucket"; + + var fileTransferUtilityRequest = new TransferUtilityUploadRequest() + { + }; + var transferUtilityDownloadRequest = new TransferUtilityDownloadRequest + { + BucketName = null, + Key = null, + VersionId = null, + ModifiedSinceDateUtc = default, + UnmodifiedSinceDateUtc = default, + ChecksumMode = null, + ServerSideEncryptionCustomerProvidedKey = null, + ServerSideEncryptionCustomerProvidedKeyMD5 = null, + ServerSideEncryptionCustomerMethod = null, + FilePath = null + }; + var getObjectRequest = new GetObjectRequest + { + BucketName = bucketName, + Key = filePath, + + // PartNumber = 1, + ByteRange = new ByteRange(0, 14096), + }; + var retryCount = 0; + while (true) + { + using var response = await s3Client.GetObjectAsync(getObjectRequest); + using var reader = new StreamReader(response.ResponseStream); + while (true) + { + var line = await reader.ReadLineAsync(); + if (string.IsNullOrEmpty(line)) + { + retryCount++; + break; + } + + // Process the line + Console.WriteLine(line); + retryCount = 0; + } + + if (retryCount >= 2) + { + break; + } + } } static async Task WriteObjectDataAsync(string filePath) { var s3Client = CreateS3Client(); + var bucketName = "test-bucket"; var writerStream = new MemoryStream(); var writer = new StreamWriter(writerStream) { @@ -105,8 +240,8 @@ static async Task WriteObjectDataAsync(string filePath) var lineLimit = 10; var putRequest = new PutObjectRequest() { - BucketName = "test-bucket", - Key = $"test.csv", + BucketName = bucketName, + Key = filePath, UseChunkEncoding = true, AutoCloseStream = false, AutoResetStreamPosition = true, @@ -129,7 +264,7 @@ static async Task WriteObjectDataAsync(string filePath) Console.WriteLine("Unknown encountered on server. Message:'{0}' when writing object", e.Message); } } - + static AmazonS3Client CreateS3Client() { var credentials = new BasicAWSCredentials("AKIAIOSFODNN7EXAMPLE", From 0bb26af656f89cdf439c42d4136ae12499b74ad3 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 14 Feb 2024 11:40:37 +0800 Subject: [PATCH 364/424] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20Testcontai?= =?UTF-8?q?ners.Minio=20=E7=AF=84=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AWS/Lab.AwsS3/.gitignore | 1340 +++++++++++++++++ .../Lab.Aws.S3.MinIOS3.csproj | 1 + AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs | 67 +- AWS/Lab.AwsS3/Lab.AwsS3.sln | 1 + Args/Lab.SysCommand/Lab.SysCommand.sln | 16 + .../Lab.SysCommand/Lab.SysCommand.csproj | 16 + Args/Lab.SysCommand/Lab.SysCommand/Program.cs | 11 + DI/Lab.MsDI/WinFormNet48/Form1.cs | 22 +- .../WebAppA/Controllers/Demo1Controller.cs | 2 +- .../WebApplication1/test.http | 2 +- .../Lab.MongoDB.CRUD.TestProject/UnitTest1.cs | 85 +- .../Lab.CursorPaging.WebApi/Program.cs | 4 +- Paging/Lab.CursorPaging/Lab.CursorPaging.sln | 6 + .../Controllers/WeatherForecastController.cs | 2 +- .../Lab.MsRateLimit.WebAPI.csproj | 14 + .../Lab.MsRateLimit.WebAPI/Program.cs | 26 + .../Properties/launchSettings.json | 41 + .../WeatherForecast.cs | 2 +- .../appsettings.Development.json | 8 + .../Lab.MsRateLimit.WebAPI/appsettings.json | 9 + .../Lab.MsRateLimit/Lab.MsRateLimit.sln | 22 + .../Lab.MsRateLimit/TestProject1/UnitTest1.cs | 33 + Trace/Lab.Context.Trace/k8s/ns.yml | 12 + 23 files changed, 1714 insertions(+), 28 deletions(-) create mode 100644 AWS/Lab.AwsS3/.gitignore create mode 100644 Args/Lab.SysCommand/Lab.SysCommand.sln create mode 100644 Args/Lab.SysCommand/Lab.SysCommand/Lab.SysCommand.csproj create mode 100644 Args/Lab.SysCommand/Lab.SysCommand/Program.cs rename {Test/Lab.Test.Container/Lab.TestContainers.WebApi => Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI}/Controllers/WeatherForecastController.cs (95%) create mode 100644 Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Lab.MsRateLimit.WebAPI.csproj create mode 100644 Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Program.cs create mode 100644 Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Properties/launchSettings.json rename {WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI => Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI}/WeatherForecast.cs (86%) create mode 100644 Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/appsettings.Development.json create mode 100644 Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/appsettings.json create mode 100644 Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.sln create mode 100644 Rate Limit/Lab.MsRateLimit/TestProject1/UnitTest1.cs create mode 100644 Trace/Lab.Context.Trace/k8s/ns.yml diff --git a/AWS/Lab.AwsS3/.gitignore b/AWS/Lab.AwsS3/.gitignore new file mode 100644 index 00000000..eab69e5c --- /dev/null +++ b/AWS/Lab.AwsS3/.gitignore @@ -0,0 +1,1340 @@ +### ASPNETCore template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/ + +### Csharp template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +### ASPNETCore template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/ + +### Csharp template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + diff --git a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Lab.Aws.S3.MinIOS3.csproj b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Lab.Aws.S3.MinIOS3.csproj index e6b043df..55b27f74 100644 --- a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Lab.Aws.S3.MinIOS3.csproj +++ b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/Lab.Aws.S3.MinIOS3.csproj @@ -14,6 +14,7 @@ + diff --git a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs index 158d75fc..331255cf 100644 --- a/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs +++ b/AWS/Lab.AwsS3/Lab.Aws.S3.MinIOS3/UnitTest1.cs @@ -1,10 +1,10 @@ using System.Collections.Concurrent; using System.Reflection; -using System.Text; using Amazon.Runtime; using Amazon.S3; using Amazon.S3.Model; using Amazon.S3.Transfer; +using Testcontainers.Minio; namespace Lab.Aws.S3.MinIOS3; @@ -80,6 +80,67 @@ public async Task 新增一個儲存桶() }); } + [TestMethod] + public async Task 新增一個儲存桶_For_TestContainer() + { + var s3Container = await CreateS3Container(); + var connectionString = s3Container.GetConnectionString(); + var s3Client = CreateS3Client(connectionString); + var response = await s3Client.PutBucketAsync(new PutBucketRequest + { + BucketName = "test-bucket", + }); + } + [TestMethod] + public async Task 上傳檔案到儲存桶_For_TestContainer() + { + var s3Container = await CreateS3Container(); + var connectionString = s3Container.GetConnectionString(); + var s3Client = CreateS3Client(connectionString); + var bucketName = "test-bucket"; + var createBucket = await s3Client.PutBucketAsync(new PutBucketRequest + { + BucketName = bucketName, + }); + + var inputMemory = new MemoryStream(); + await File.Open("上傳.csv", FileMode.Open).CopyToAsync(inputMemory); + var putObjectResponse = await s3Client.PutObjectAsync(new PutObjectRequest + { + BucketName = bucketName, + Key = "上傳.csv", + InputStream = inputMemory, + AutoCloseStream = false, + AutoResetStreamPosition = true, + }); + var getObjectResponse = await s3Client.GetObjectAsync(new GetObjectRequest + { + BucketName = bucketName, + Key = "上傳.csv", + }); + await using var outputStream = File.Create("下載.csv"); + await getObjectResponse.ResponseStream.CopyToAsync(outputStream); + } + + private static async Task CreateS3Container() + { + var username = "AKIAIOSFODNN7EXAMPLE"; + var password = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"; + var minioBuilder = new MinioBuilder() + .WithName("minio") + .WithHostname("localhost") + // .WithImage("quay.io/minio/minio:latest") + // .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(9000)) + .WithUsername(username) + .WithPassword(password) + .WithPortBinding(9000, assignRandomHostPort: true) + // .WithPortBinding(9001, assignRandomHostPort: false) + ; + var minioContainer = minioBuilder.Build(); + await minioContainer.StartAsync().ConfigureAwait(false); + return minioContainer; + } + [TestMethod] public async Task 分批上傳檔案() { @@ -265,14 +326,14 @@ static async Task WriteObjectDataAsync(string filePath) } } - static AmazonS3Client CreateS3Client() + static AmazonS3Client CreateS3Client(string url = "http://localhost:9000") { var credentials = new BasicAWSCredentials("AKIAIOSFODNN7EXAMPLE", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"); var s3Config = new AmazonS3Config { - ServiceURL = "http://localhost:9000", + ServiceURL = url, ForcePathStyle = true }; return new AmazonS3Client(credentials, s3Config); diff --git a/AWS/Lab.AwsS3/Lab.AwsS3.sln b/AWS/Lab.AwsS3/Lab.AwsS3.sln index c4a97c99..49cbf512 100644 --- a/AWS/Lab.AwsS3/Lab.AwsS3.sln +++ b/AWS/Lab.AwsS3/Lab.AwsS3.sln @@ -5,6 +5,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{17F77AC4-5953-4CD1-917D-28A4746AB759}" ProjectSection(SolutionItems) = preProject docker-compose.yaml = docker-compose.yaml + .gitignore = .gitignore EndProjectSection EndProject Global diff --git a/Args/Lab.SysCommand/Lab.SysCommand.sln b/Args/Lab.SysCommand/Lab.SysCommand.sln new file mode 100644 index 00000000..70953a42 --- /dev/null +++ b/Args/Lab.SysCommand/Lab.SysCommand.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.SysCommand", "Lab.SysCommand\Lab.SysCommand.csproj", "{FD82FD27-B9AF-4E34-8021-C5B3A1C34CC5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FD82FD27-B9AF-4E34-8021-C5B3A1C34CC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD82FD27-B9AF-4E34-8021-C5B3A1C34CC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD82FD27-B9AF-4E34-8021-C5B3A1C34CC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD82FD27-B9AF-4E34-8021-C5B3A1C34CC5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Args/Lab.SysCommand/Lab.SysCommand/Lab.SysCommand.csproj b/Args/Lab.SysCommand/Lab.SysCommand/Lab.SysCommand.csproj new file mode 100644 index 00000000..b66d7381 --- /dev/null +++ b/Args/Lab.SysCommand/Lab.SysCommand/Lab.SysCommand.csproj @@ -0,0 +1,16 @@ + + + + Exe + net7.0 + enable + enable + app + + + + + + + + diff --git a/Args/Lab.SysCommand/Lab.SysCommand/Program.cs b/Args/Lab.SysCommand/Lab.SysCommand/Program.cs new file mode 100644 index 00000000..c7410ae9 --- /dev/null +++ b/Args/Lab.SysCommand/Lab.SysCommand/Program.cs @@ -0,0 +1,11 @@ +// See https://aka.ms/new-console-template for more information + +using System.CommandLine; + +var rootCommand = new RootCommand(); +var sub1Command = new Command("sub1", "First-level subcommand"); +rootCommand.Add(sub1Command); +var sub1aCommand = new Command("sub1a", "Second level subcommand"); +sub1Command.Add(sub1aCommand); + +await rootCommand.InvokeAsync(args); diff --git a/DI/Lab.MsDI/WinFormNet48/Form1.cs b/DI/Lab.MsDI/WinFormNet48/Form1.cs index f0023ace..f49d6289 100644 --- a/DI/Lab.MsDI/WinFormNet48/Form1.cs +++ b/DI/Lab.MsDI/WinFormNet48/Form1.cs @@ -4,9 +4,22 @@ namespace WinFormNet48 { + public class BaseForm : Form + { + public BaseForm() + { + var serviceProvider = DependencyInjectionConfig.ServiceProvider; + using (var serviceScope = serviceProvider.CreateScope()) + { + var work = serviceScope.ServiceProvider.GetRequiredService(); + } + } + } + public partial class Form1 : Form { int _counter = 1; + public Form1() { this.InitializeComponent(); @@ -15,9 +28,12 @@ public Form1() private void button1_Click(object sender, EventArgs e) { var serviceProvider = DependencyInjectionConfig.ServiceProvider; - var work = serviceProvider.GetRequiredService(); - Console.WriteLine($"{this._counter}=>\r\n"+work.Get()); - + using (var serviceScope = serviceProvider.CreateScope()) + { + var work = serviceScope.ServiceProvider.GetRequiredService(); + Console.WriteLine($"{this._counter}=>\r\n" + work.Get()); + } + this._counter++; } } diff --git a/Http Client/Lab.HttpClientWithCookie/src/WebAppA/Controllers/Demo1Controller.cs b/Http Client/Lab.HttpClientWithCookie/src/WebAppA/Controllers/Demo1Controller.cs index b052af54..9996e2bc 100644 --- a/Http Client/Lab.HttpClientWithCookie/src/WebAppA/Controllers/Demo1Controller.cs +++ b/Http Client/Lab.HttpClientWithCookie/src/WebAppA/Controllers/Demo1Controller.cs @@ -119,7 +119,7 @@ public async Task Get() requestId, responseContent); } - + // if (response.Headers.TryGetValues("A", out var a2)) // { // this._logger.LogInformation("Id={RequestId},Response Header 'A'={Data}", requestId, a2); diff --git a/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/test.http b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/test.http index 7df4dd90..16793797 100644 --- a/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/test.http +++ b/Jetbrains HttpClient/Jetbrains HttpClient/WebApplication1/test.http @@ -5,7 +5,7 @@ %} GET {{BaseUrl}}/WeatherForecast - +ㄕ ### 取得所有 GET {{BaseUrl}}/WeatherForecast diff --git a/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/UnitTest1.cs b/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/UnitTest1.cs index ef975efd..b964f4df 100644 --- a/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/UnitTest1.cs +++ b/MongoDB/Lab.MongoDB.CRUD/Lab.MongoDB.CRUD.TestProject/UnitTest1.cs @@ -1,3 +1,4 @@ +using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; using MongoDB.Driver.Linq; @@ -15,15 +16,15 @@ public class UnitTest1 [ClassInitialize] public static async Task ClassInitialize(TestContext context) { - MongoDbContainer = new MongoDbBuilder() - .WithPortBinding(27017, true) - .Build(); - await MongoDbContainer.StartAsync(); - var mongoClientSettings = MongoClientSettings.FromConnectionString(MongoDbContainer.GetConnectionString()); - // var mongoClientSettings = new MongoClientSettings() - // { - // Server = new MongoServerAddress("localhost", 27017), - // }; + // MongoDbContainer = new MongoDbBuilder() + // .WithPortBinding(27017, true) + // .Build(); + // await MongoDbContainer.StartAsync(); + // var mongoClientSettings = MongoClientSettings.FromConnectionString(MongoDbContainer.GetConnectionString()); + var mongoClientSettings = new MongoClientSettings() + { + Server = new MongoServerAddress("localhost", 27017), + }; MongoClient = new MongoClient(mongoClientSettings); } @@ -58,7 +59,11 @@ public async Task 新增一筆資料() //新增一筆資料 await mongoCollection.InsertOneAsync(expected); - + mongoCollection.InsertOne(expected,new InsertOneOptions + { + BypassDocumentValidation = null, + Comment = null + }); //驗證 var actual = await mongoCollection.AsQueryable().FirstOrDefaultAsync(p => p.Id == "1"); Assert.AreEqual(expected, actual); @@ -79,13 +84,13 @@ public async Task 更新一筆資料() //產生資料 var products = this.GenerateProducts(); await mongoCollection.InsertManyAsync(products); - + var filter = Builders.Filter .Eq(restaurant => restaurant.Id, "1"); var update = Builders.Update .Set(restaurant => restaurant.Name, "TV"); - + //更新資料 await mongoCollection.UpdateOneAsync(filter, update); @@ -106,7 +111,7 @@ public async Task 刪除資料() var filter = Builders.Filter .Eq(restaurant => restaurant.Id, "1"); - + //更新資料 await mongoCollection.DeleteOneAsync(filter); @@ -148,27 +153,37 @@ public async Task 查詢() Assert.AreEqual(expected, data2); } + + [TestMethod] + public async Task AA() + { + var database = MongoClient.GetDatabase("example"); + var mongoCollection = database.GetCollection("counters"); + var sequenceGenerator = new SequenceGenerator (database); + var sequenceValue = sequenceGenerator.GetNextSequenceValue("_id"); + } + private List GenerateProducts() { var products = new List() { new() { - Id = "1", + // Id = "1", Name = "Air jordan 11", Price = 33.11m, Remark = this.TestData }, new() { - Id = "2", + // Id = "1", Name = "Air jordan 12", Price = 33.12m, Remark = this.TestData }, new() { - Id = "3", + // Id = "3", Name = "Air jordan 13", Price = 33.13m, Remark = this.TestData @@ -177,9 +192,12 @@ private List GenerateProducts() return products; } - public record Product { + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + + // [BsonElement("Id1")] public string Id { get; init; } public string Name { get; init; } @@ -188,4 +206,37 @@ public record Product public string Remark { get; init; } } + + public class SequenceGenerator + { + private IMongoCollection countersCollection; + + public SequenceGenerator(IMongoDatabase database) + { + countersCollection = database.GetCollection("counters"); + } + + public int GetNextSequenceValue(string sequenceName) + { + var filter = Builders.Filter.Eq("_id", sequenceName); + var update = Builders.Update.Inc("sequence_value", 1); + + var result = countersCollection.FindOneAndUpdate(filter, update); + + if (result == null) + { + // If the document doesn't exist, create it with initial value 1 + var newCounterDocument = new BsonDocument + { + { "_id", sequenceName }, + { "sequence_value", 1 } + }; + countersCollection.InsertOne(newCounterDocument); + return 1; + } + + return result["sequence_value"].AsInt32; + } + } + } \ No newline at end of file diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Program.cs b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Program.cs index eb9db404..43c95398 100644 --- a/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Program.cs +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.WebApi/Program.cs @@ -38,4 +38,6 @@ app.UseRouting(); app.MapControllers(); -app.Run(); \ No newline at end of file +app.Run(); + +public partial class Program { } \ No newline at end of file diff --git a/Paging/Lab.CursorPaging/Lab.CursorPaging.sln b/Paging/Lab.CursorPaging/Lab.CursorPaging.sln index a755c4fe..bb149dda 100644 --- a/Paging/Lab.CursorPaging/Lab.CursorPaging.sln +++ b/Paging/Lab.CursorPaging/Lab.CursorPaging.sln @@ -7,6 +7,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution docker-compose.yml = docker-compose.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject1", "TestProject1\TestProject1.csproj", "{1543BF4E-385C-43A5-85F7-92A357078549}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -17,5 +19,9 @@ Global {764FE9E4-9198-4812-A099-0AE48FE0816A}.Debug|Any CPU.Build.0 = Debug|Any CPU {764FE9E4-9198-4812-A099-0AE48FE0816A}.Release|Any CPU.ActiveCfg = Release|Any CPU {764FE9E4-9198-4812-A099-0AE48FE0816A}.Release|Any CPU.Build.0 = Release|Any CPU + {1543BF4E-385C-43A5-85F7-92A357078549}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1543BF4E-385C-43A5-85F7-92A357078549}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1543BF4E-385C-43A5-85F7-92A357078549}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1543BF4E-385C-43A5-85F7-92A357078549}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Controllers/WeatherForecastController.cs b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Controllers/WeatherForecastController.cs similarity index 95% rename from Test/Lab.Test.Container/Lab.TestContainers.WebApi/Controllers/WeatherForecastController.cs rename to Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Controllers/WeatherForecastController.cs index 5c347520..27d03e37 100644 --- a/Test/Lab.Test.Container/Lab.TestContainers.WebApi/Controllers/WeatherForecastController.cs +++ b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Controllers/WeatherForecastController.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; -namespace Lab.Test.Container.WebApi.Controllers; +namespace Lab.MsRateLimit.WebAPI.Controllers; [ApiController] [Route("[controller]")] diff --git a/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Lab.MsRateLimit.WebAPI.csproj b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Lab.MsRateLimit.WebAPI.csproj new file mode 100644 index 00000000..f2ab5566 --- /dev/null +++ b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Lab.MsRateLimit.WebAPI.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + enable + enable + + + + + + + + diff --git a/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Program.cs b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Program.cs new file mode 100644 index 00000000..329fe361 --- /dev/null +++ b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Program.cs @@ -0,0 +1,26 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Properties/launchSettings.json b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Properties/launchSettings.json new file mode 100644 index 00000000..8446254f --- /dev/null +++ b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:4451", + "sslPort": 44381 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5293", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7249;http://localhost:5293", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/WeatherForecast.cs b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/WeatherForecast.cs similarity index 86% rename from WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/WeatherForecast.cs rename to Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/WeatherForecast.cs index c747db14..f1024e56 100644 --- a/WebAPI/Swagger/Lab.RefitClient/Lab.RefitClient.WebAPI/WeatherForecast.cs +++ b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/WeatherForecast.cs @@ -1,4 +1,4 @@ -namespace Lab.RefitClient.WebAPI; +namespace Lab.MsRateLimit.WebAPI; public class WeatherForecast { diff --git a/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/appsettings.Development.json b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/appsettings.json b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.WebAPI/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.sln b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.sln new file mode 100644 index 00000000..7c579b58 --- /dev/null +++ b/Rate Limit/Lab.MsRateLimit/Lab.MsRateLimit.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.MsRateLimit.WebAPI", "Lab.MsRateLimit.WebAPI\Lab.MsRateLimit.WebAPI.csproj", "{1F2989FC-AAD0-480B-B2FB-5F5D6DE91053}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject1", "TestProject1\TestProject1.csproj", "{11E81AE5-8F85-4649-B747-EF0E196B9C05}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1F2989FC-AAD0-480B-B2FB-5F5D6DE91053}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F2989FC-AAD0-480B-B2FB-5F5D6DE91053}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F2989FC-AAD0-480B-B2FB-5F5D6DE91053}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F2989FC-AAD0-480B-B2FB-5F5D6DE91053}.Release|Any CPU.Build.0 = Release|Any CPU + {11E81AE5-8F85-4649-B747-EF0E196B9C05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11E81AE5-8F85-4649-B747-EF0E196B9C05}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11E81AE5-8F85-4649-B747-EF0E196B9C05}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11E81AE5-8F85-4649-B747-EF0E196B9C05}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Rate Limit/Lab.MsRateLimit/TestProject1/UnitTest1.cs b/Rate Limit/Lab.MsRateLimit/TestProject1/UnitTest1.cs new file mode 100644 index 00000000..f779f51b --- /dev/null +++ b/Rate Limit/Lab.MsRateLimit/TestProject1/UnitTest1.cs @@ -0,0 +1,33 @@ +using System.Threading.RateLimiting; + +namespace TestProject1; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public async Task TestMethod1() + { + RateLimiter limiter = new ConcurrencyLimiter( + new ConcurrencyLimiterOptions + { + PermitLimit = 2, + QueueProcessingOrder = QueueProcessingOrder.OldestFirst, + QueueLimit = 2, + }); + limiter.ConfigureAwait(false); + var acquireResult1 = limiter.AcquireAsync(permitCount: 2).Result; + var acquireResult2 = limiter.AcquireAsync(permitCount: 2).Result; + // thread 1 + // var acquireResult1 = await limiter.AcquireAsync(permitCount: 2); + // if (acquireResult1.IsAcquired) + // { + // } + // + // // thread 2 + // var acquireResult2 = await limiter.AcquireAsync(permitCount: 2); + // if (acquireResult2.IsAcquired) + // { + // } + } +} \ No newline at end of file diff --git a/Trace/Lab.Context.Trace/k8s/ns.yml b/Trace/Lab.Context.Trace/k8s/ns.yml new file mode 100644 index 00000000..78c095b8 --- /dev/null +++ b/Trace/Lab.Context.Trace/k8s/ns.yml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: Title +spec: + selector: + app: Title + ports: + - protocol: TCP + port: 80 + targetPort: 8080 + type: NodePort \ No newline at end of file From 69a4a86ab2feddf671fdff477b5c264752c2d2a4 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 19 Feb 2024 10:26:31 +0800 Subject: [PATCH 365/424] =?UTF-8?q?=E9=80=9A=E9=81=8E=20miniexcel=20?= =?UTF-8?q?=E8=AE=80=E5=8F=96=20excel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Lab.MiniExcelQuery.Test/GlobalUsings.cs | 1 + .../Lab.MiniExcelQuery.Test/Import.xlsx | Bin 0 -> 14212 bytes .../Lab.MiniExcelQuery.Test.csproj | 53 ++++++ .../Template/Employee.xlsx | Bin 0 -> 13476 bytes .../Template/Export.xltx | Bin 0 -> 13493 bytes .../Template/ImportWithError.xlsx | Bin 0 -> 13580 bytes .../Template/ImportWithError.xltx | Bin 0 -> 13549 bytes .../Template/Member.xlsx | Bin 0 -> 13584 bytes .../Template/Template.xltx | Bin 0 -> 13613 bytes .../Lab.MiniExcelQuery.Test/UnitTest1.cs | 168 ++++++++++++++++++ ...76\213_20230518093153_10\350\220\254.xlsx" | Bin 0 -> 3958685 bytes .../Lab.MiniExcelQuery/Lab.MiniExcelQuery.sln | 16 ++ 12 files changed, 238 insertions(+) create mode 100644 Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/GlobalUsings.cs create mode 100644 Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Import.xlsx create mode 100644 Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Lab.MiniExcelQuery.Test.csproj create mode 100644 Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/Employee.xlsx create mode 100644 Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/Export.xltx create mode 100644 Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/ImportWithError.xlsx create mode 100644 Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/ImportWithError.xltx create mode 100644 Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/Member.xlsx create mode 100644 Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/Template.xltx create mode 100644 Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/UnitTest1.cs create mode 100644 "Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/\344\277\256\346\224\271\347\255\226\347\225\245\347\257\204\344\276\213_20230518093153_10\350\220\254.xlsx" create mode 100644 Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.sln diff --git a/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/GlobalUsings.cs b/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/GlobalUsings.cs new file mode 100644 index 00000000..8c927eb7 --- /dev/null +++ b/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Import.xlsx b/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Import.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..38845f3741b7a5e21b5e632edb5ea30dfc1ffab2 GIT binary patch literal 14212 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 + + + net8.0 + enable + enable + + false + true + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + diff --git a/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/Employee.xlsx b/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/Employee.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0114a247745e695b56c15d96130e50e401cabb83 GIT binary patch literal 13476 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 LFrH-maM$1-pm7WC?(&iMW`1ubZ@#}^ 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 literal 0 HcmV?d00001 diff --git a/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/Member.xlsx b/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/Member.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..ce0e03ccc454f58664a496e213cb48bfb4991cdb GIT binary patch literal 13584 zcmeHug9RLY{1^@sg0AViw32P7l02~4UKm$O7YKhudI~rL# z>MFb08ae1Nx>{Kh=YfM#=Kw(8`v33vFSbCL@{n~8GjhAyq2T)t)v(-FDhdNm1^j23I)pvEy_zi?D9oQ4H2O&OU4HcIdVO?;>dFQbN*+E zY%%|WGS$f1mYy=<>@<%QHplj-4f9I z`6K?J9t}`w8YgT6Zr1=s23)CCwYD||RyI}g%5(<;6~=_yNE-BN`R2L@BFV$yY4jYO zYEztdqC`SwWguUzSc%}-f?<%WSK27rC=Y4?5_URvRcJRBss*<>$^Kt<;zYj7zKPJXa+f}*9gPZGB)+vr1ap1q#E zN&O(@M(x}lOtCU`+m0Kh1s_ja7rY3XoY5rIYgD zsz$Dy!}y7GpSh%>0|dcPo{z`VsThL}1||y?o`aSo*Y~(8N~Sz!RR&r1??~NrKU;QP zzEAH&d-i0I%@|Z9WkY%e!8OtNW2%weP6*YEyHoXT~KsdZY&W8l?7Yv^sP44RT_ zplYbk@@fJqMi9sjj7=--IfHa&?=++ehqQ&}EtYS8;(D_@*+GlWbiinTsHEkY9sU}0 z0&uLN@In_v1p9)dSS2vty%gi_XB@peYdaw|0Q`&rtlq?8#`nfX2&ls?FNkS#$-BN? zCS~W=vhB8RoW7P&@uDg$O%Tc=?vJv!*2v05W1bBspx>FsAe49;DG>A54N{}HF|oLc}vaJu>Mbr<(ILd3799q*=fH%-{kuY*_> z8%AbmSsP8uwH^}(w}>tOU^J!%wJe^F7t5Rnubc+0R+<&4Z8wB>^kM93mq*!ag*SectXPat*L{cwWH{&Y{w7Pf?u|pup&K%s9~~6unCHlV;3=bBf0nC zuZ565RM6E}^K*Z+$m*PQg>^{=?sf+MygdeWF5X};p}WkZG;u%TWpt|1QL=j z-&$JUJRKJR4f5vYe`bS!_4R*d29USH?JZXRyN@zO8R@>a!UgUngxMwC83Sd(k%{zJ z

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 N$~QNjVfl6U{{Rf~dUF5( literal 0 HcmV?d00001 diff --git a/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/Template.xltx b/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/Template.xltx new file mode 100644 index 0000000000000000000000000000000000000000..ac798daeefcdfd2b0b301c0ff39a5c150c92c09a GIT binary patch literal 13613 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 + var results = new List(); + foreach (var chunks in inputRows.Chunk(chunkSize)) + { + results.AddRange(chunks); + } + } + + [Fact] + public async Task 批次匯入後另存() + { + //import excel file + var inputPath = "Import.xlsx"; + var outputPath = "Data.xlsx"; + var templatePath = "Template/Member.xlsx"; + var chunkSize = 2; + + await using var inputStream = File.OpenRead(inputPath); + + // await using var templateStream = File.OpenWrite(templatePath); + // await using var outputStream = File.Create(outputPath); + + var inputRows = await inputStream.QueryAsync(); + var results = new List(); + foreach (var chunks in inputRows.Chunk(chunkSize)) + { + foreach (var row in chunks) + { + if (row.Age > 30) + { + row.Reason = "年齡超過30"; + } + } + + results.AddRange(chunks); + } + + var value = new + { + Members = results + }; + + + //結果會被覆蓋 + await MiniExcel.SaveAsByTemplateAsync(outputPath, templatePath, value); + } + + [Fact] + public async Task 填充員工表() + { + var templatePath = "Template/Employee.xlsx"; + + // var templatePath = "Template/ImportWithError.xltx"; + var outputPath = "data.xlsx"; + + var value = new + { + employees = new[] + { + new { name = "Jack", department = "HR" }, + new { name = "Lisa", department = "HR" }, + new { name = "John", department = "HR" }, + new { name = "Mike", department = "IT" }, + new { name = "Neo", department = "IT" }, + new { name = "Loan", department = "IT" } + } + }; + await MiniExcel.SaveAsByTemplateAsync(outputPath, templatePath, value); + } + + [Fact] + public async Task 填充會員表() + { + var templatePath = "Template/Member.xlsx"; + + // var templatePath = "Template/ImportWithError.xltx"; + var outputPath = "data.xlsx"; + + var value = new + { + Members = new List() + { + new() + { + Id = "1", + Name = "Alice", + Age = 25, + Phone = "1234567890", + Reason = null, + }, + new() + { + Id = "2", + Name = "Bob", + Age = 35, + Phone = "1234567890", + Reason = "年齡超過30", + } + } + }; + await MiniExcel.SaveAsByTemplateAsync(outputPath, templatePath, value); + } +} \ No newline at end of file diff --git "a/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/\344\277\256\346\224\271\347\255\226\347\225\245\347\257\204\344\276\213_20230518093153_10\350\220\254.xlsx" "b/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/\344\277\256\346\224\271\347\255\226\347\225\245\347\257\204\344\276\213_20230518093153_10\350\220\254.xlsx" new file mode 100644 index 0000000000000000000000000000000000000000..58c92c91db8503e0815719f2abc28d74911bf7e5 GIT binary patch literal 3958685 zcmeFZXH-<#)-`NG1QblDAcB|?C5nI~b3hTv2oeOrK&B)Mr66KJK}E@80+ftMEP{vv z5>;{*&_Y5{V39-BxAv(jz`lK-XMFG9_w6yd?;YdR*?Wb#=9+6AyU(0lxM=mf#q*ZT zn>SBz-T`UPO6&Ra<}G5GH*eLvB@6Tq*;rpSv%YGi>0)b!GZ1mMvfTJ`(SjY%=PiKW z|G&TfFOEQ*<`?UaV#{-MdXx_5YTtR`6BDz9FN;rq%Wu{o=SR9*(th#mm~!84uBMf| zB!8=#Wu^W$iu}6x#%*U0?(eh|nK-Mu#h4@de!Tsl!u0WoBc8A0wC|_*-;;X2ZW)=p zxGI(PR7RtT$dygBO- zUh2Dfm0NYPv(+RdwYMaO;x=fB1dQGnTAh3*^ZkUtiJp@0tDhSrU*g$!NMQfvxcP6a zBm|a9-eOyxEWY~B#*5b%i*9boD#?&wFKBeWX54#hT~>H!LYjlol$_@`e_yTVsx|MN zGOzsf z7uVz**W(_ks1}GVy+uAgtQ<=filt_j?9Q!(Bc}vlYY^4}9^wC-9~fI@W&{$vMu2 z)R2Yq=6#3O#s!gawiI=;v9~a>v9UmXEKaM*x^wSxchRr-`d=on!Tcn_wqy00-yGMxpZ=VmX(4)hSL$ia zRSKNeifKQn=N_5AFmRG)Gm_%=*+*~@JoCt24ZdorMMOYB zk0as2)f&avL(5W!xh>dp7py(H#@>Nsw{Cs?5ygzmp1MR`!INj!jeNE4-(Iq2tAAnr zyYt&udv22c7V$`puV}~7qr?N984dMI#2tOVo+N#`pG3ZSTD9a_eoUV74TCld6;YZ} z9(AOM96_5H=D6jiZ;?G;L_#a=N82IL;nVEfl_N zIYJP6xFH}mrrLw=&EBSp{%u{McdQZ&pBRg&Xf~%4XypS7osGUNyq{9Ka9QVQR|;jJ zRps{vl9uBeJ7@l_?d>D&S9uL9zeo6NF2gT<)3E(qYR;+h1KYOap;-U3jBh9g1X7@U zv!K`U&Ra4c%9!5n<`y%v_00!m3S{tq_g``9DkndIGUK57h&hEia<6E(D!QpptCwrZ zm-j+V+XN*&hT7Ke%releJt?}-=9UY&yvT7>inC+ks^NU$H+NSqa$RLfmSeFgr<5;T zVnT}N_dLe3YGq$fZ!C#}GX%FPQ}I#pu4k)dh+{jp^7y=p+H`m*^ZTtmS1QCDB(C7T zZYTljFMa>>`ov9*zIe~~>FFCogv@`@G7mM4EROJW5%@;oVLKte%w^uU-_?S`WBEr$ zmi)<{GFNe_u*iQkvGf;T>`DF@DTQmdu5R=xJ|=({dW8G+GMJ!6ZohhPvP3Aa>Cdjx zA+q_;FGV$Nkm>(~%zCTr&4Jt^^C}$&=dXibFqj{A$;|93P89neanu6B0-7E)iY>oI z79QTPscm~QZqZ@gWV2V+i81_%_dA#0lR0gL)75exJ-zbdCPB5V9bykU$EmUp#|zYI zE1p?w)_g43asKDPt+xmFgf4ZbJteIAx$b1BXKhS7k8As4ggLGtE1J8%zp z6XxZ%@2Dah37yD)FA?*e-+x+=*gF(Wt&Z+Hl~q9#S$gMM`RGfFAHqAG^^b2+O!3sw z9XqOPf6L+5_muD*5<_xatN6BG?n`+2>-+s@&(uflg5|TEdE)JTDPr651>Og(U9hj& z?AIkj^GhepBbM|l78{jK3Qh=)<)4e&aI((Gr9Al8#>4(q<-hgbS~(5yq=^};;GY+$ zn}2?W|KSk+qH9s%l#b}4lgkGx*#mpkc9WwgQXJV0zZ+QPy(u^*viAu~!zYe?^E9Nw zZW$fB+aJNQ@QB|b&6i19w>my-cIJ%|x390z-HPX&{P|O1?7(_gr6iwrm*p;Mhc|^S zN>_M6%-LT5czbYbz}?fF#>a&0Rd08H;p*G5XYixg2f^`Uw+F1d4=s{&jcUE{==Fu~ zPt3~immkro3bi|=t-#WEJl~M-*caOVCD~)Q7U!Fs5-`%7a1`@s>(9d=hW#TToMt^# z;$UX64!4Q*tUjGvy&qmGZ#CMs#8KD3D{n<^XpSQBl!jz&oz$)h$6jWh{Rr^)yU zt6W;uBs`nUqZJv^M!WE7G`9&-WgcyK`&1kLdy`uuZPbQDoAgu4o2C}wr^ZK>sKdcy zL!YjyTyrtW-IhE0saf%3a*4gl)aQ$Fb)S^nIviD|N}uT(X$K38`V~#{P71j7MI{$W zjVO)Qt6CLZY>je1XG*(gDvcK96X%%Ywh6+ zMjmd5OJ!H;R`lM}uArJkg{4Jv{F-_l$tN46D7fiZ75rq>{U1MZ+_JW0{KQFF`{HW{ z4?hSNy|LqW^>a+Fx^r!SHH+9`pEh)|)J;Eb9kt_IWS@?>$-+y8phOS9zt+bn1 z$FjYs?MTJ?h=7M)1NS8CS9=^PRNkk^88|c%6Zk$aWarz$(#Nm*Hg);IA*!P+!CDUr zZHjeI2uepR^w>G{=+962Y1`M-&uja(9Bb5Sa9Qu7utmrFV#y8#IEQ_FobSWdUu>>1 zf<4m$&fBaqOB6EEILfnLqE_vAL)X0i9hD7L z!k1`So4eXVJxBJciNpw9);Ow$o-b)a&;6Px!q&8RuXgWN%KN_8EFewj)%#^Uq~E?j z#vz7A=cdNA@=CQ=2>D~uF zI^MqH>wpwj^qUtj8sUkR$dgfb-QH`DM%5N%n`a&wQ;x5A`;+4 zGh@T+MUD5oMuyC#Z=NZ(W_#h^vbsZXy2a6BlVYpkiDtiac?;34wXNUlI;~ta>I?n9 z@4dA;UGRdGcZNIXkCuQUQsm7heLV~H){+$zObg7X0q2ep-iT3EsDXsq0 z!)^L{S3^Z%uwkVU{SSG#Ol(FbI{hLAQE4^(l$DaTfTz8}3{nH)`l@ad~c}ixk>4~S*!I!5g zGeKwCyS{10-dLg60|+9~f7ydY^$NSt6yFD_U;oVylt%1C3l-(|)!)|_3-pbyF;0nN@$BzDHe^gg24?A8|ZR}}n2=)X*f242y zoSa~9s#p2^^Ht;qb}1H~8C)^*?=~mm|L`#9aJ&A5URB&^hP%mspkuBrm>aW+$_oSp zApZfvMgHnCP*?sRE9alnc=oXkAsk_fy*tOmhyih?Dd;|^D$KpVBBOM4hid!3`!LU` zic+)Gd*)nlLE|_Lhezeg%#AE_dg=T9TPgjYpGYqyx6l79B|of`P6)V;++mR3 zaf^MMkGBWvOy~c0VW<6%fthO#0^?jB#@>7yGSq$XUlNQ(?cZq;%hZ1sFG3T;RjY6|r5Aq5v3OIiT^VtetNk>?|JGP7R6Y@SM z9@%ZC`slnb6roJ*dz_CWMxT7vG|HI?x!I9e;dIqf1xtDge1r&|G&20N>VQOM6N*T-}zRCmGAX8T(}%7ElFO#CXCls?jgvuwPV7 zzy-tTf<&D1jc^U+zJNbj2hGWw7d!lV>+ULiev&^hYsg9E@%Ih8rr)s+kGzYDd=>CW zE;2j8zh(0F0RtD;h*ssX+@e&GUrwimKH!wuo~aw^6c4z6dY&NvS=DB%Z(FBcpuK$b z>YuWaTU3P-g)fXNnw-&jMvP8hSwXgoKk34+LHznNV5I^&vm2es5|cN!2d^^xyJSyH zQGl94ly8rucDd0dDSUg)t~*i|nyN=nov~OjDSC8Rru2>CuR7niRK9#d@3{Tl9%-&$ zFZ@pRiCpaayfySf!#Up`pa*C(MwB(Iq zXLI%?sTT=4zgh7XH+n={%Rlumh%vg~lOL2BcXT*NBBrulTGUhJO)vMvi-ZL=MoYu) zWN~_U%>P>RhQB$u^opjR(5newAIi(dn^}f01bTL!ng0Aa_zlPLX0tHk&T~VKsYJD? zvqy*hCH%*{A5s2DRBJt-P%$_wQzI}n^^VY7%672)mpT2EFiJ<;&6Z*I=`%VvdhU6i zPBIrs`2C3yg=ZD_?yl-__4arZyMOqI&h{mH)}}kCE8Cb_uy*Emh{|b2xYWKUKKtV( z9yMWfTrNU1djHEPQrL&c%pP)!T-;2PODM6IyXi$jP)~&L&z=}Hh2IJ~ zDR)jo*q{{L5AJBJThHe!UZbQ_jOFfHg3XM3s<)3s-;-PIsB>z#(y0r3S5YVB-pspV z@NOUT;FcQ;8!H{KQ&EAfdikxpW2JWsx5hN3B{S=aCfCH=ryt?zYVWKL3;N0MGVfQ1 zcXGBnBKJ&7ork;#@{=AfW;KPIxz!zNe!g1ud%sSKpFKE!5dyR5wYx%su(m&jIB;0+ ztHWMqX|LM!y}N^V*h^gdA^mY8|IqVZON2&OD*E25)d>CL{TSE8|IXsA(NMvm#+a$yHi)MwC2f!0lIf8pgy>q=3pd+m2PtPRlC56{B^M^ zevAYktqa8nQpn7sN=Q(K6rA6WRUP)O<5j8B?wbs79*%a;Ibbl09KQrIR*e{}oc9@o zMcIlKYSOOJuf28EU3w-`9aY^Od%aP?=E~{l-0wMXEGuOC>PI>$ty)l>+xo*8I5_hl zcI(rtIkRN5qUWkz_(xQMo7UkWf z@>xh}LG5zSt=-c$HnaC8!M!nOX0dun6+I1*C`YGH+`N>k%1ft(<2723<4-?V$_cib ziPSkPQufS9Egs%G3#mbfR4j^=4l`0`Y#!4i6)9cruF5vwqH-x_7QOo3zmZN!nkkY` zP$a0n7#=C~Sk2b;!P@SQbKa;GbrEn_;l538nDzH;^gTE3IYna7Q{ETHAi;>`??;9i zbb{6V`?u+zXJ6HezIX4hP-)mq1ZB^(mnHhpUJ#Qpth(Q$BJjAb=MTBM^;A;{3Reb% zi*B~2fs{+{qJ1@?4f?Xs0aJhp7u4nQ*KrGOV%>IVpN|?>tx8AFEnI93Fn0yUB z7|zh*HK4^gS=$qr4xO9XVLie{taa)U7qo@PBGXKBa+*?S9$ya^d7$$~cqVV}Yr_iF7G}U!xnQZMFq?4aYFUh(Nbv7iD zZmx68$}pNX_N_@OLZC>f@k$tWOt%;gdS?M>R_Y5_{%NEP0#nCqf0y)a%tf8x;Hgemw^>fG>$_@KaBz;-xSU`GM`l76H2lqC zwZkLlu^xmP`{8$0pSd{*vmqHuNavc3XF#|}h3=ey?MY1MA}nN8Cr3R&mPafv9Xa8J z6#{#37BNKM(a$I`DmPUBsfO}!n0icffT0SBC$4wmox+kKXIUMcJ3U=0M`9g>YH-SL zpTTdCCg;!+7_8b>otg&>e5dE4SWa#p_8ipn)5w6j;kzWq{vIUTBXpGNn%}vda}3Nh zla17@6pD0Jb}>k-=XCJa#K-wJ8ElP-kmioBr9V0?RHISS>-yR8jqvLUQ{bil^=Urq zq_y=~cn=w;ZdfOrcE~`mMgYXhPPOUmx0lSE$}z9H0@RVD*Rk};IvAs*4RYn>gp0SM^SA>Izjo%~R943!tQ*mRO`IKrmy} zX^>%C`EEI;$W6C2vN@yU%v!-fVO1|7)9fPTQlq5#-JVj;!us7!$HZ#ZHov1pnW-qX z;{8f9OPk+?NyM;P^br&<_lKIh091^sQ>fCNlwZyLpv3<32CNa#D>#2>!rHol2g(B} z+zhih3is%+v8#JsTQfM)>d_#%B{MR`|YUp3!0eA`=cEKK*xq9&TxVCrMY+ z5w@k!NPt6)Y)dObol?Qh{T{OG%6AjEYFa>S*Va3^e?QkLV)P9!Sy?%0k02!i6U)DOIi}~HzgrF~%nD`V(_aBF z6Q%cop?gXz43hQJ{C+5TJ|kCbGg{uKu;f z0}E!)cPwL?C|V~_=R~&JnA99JVBP3+DhE;~5aYm8<0UncpbB{@)fsBxZo7d~tNhsC z*cL8VQwKR?7!B|TCTAE8njH{?EFzTVvASV*VYCFCh+PGe61jyK(PD)oE9aOo*|n;9 zZ6&>ewj>BUmhzKNZ}1IulF9>+y21&*3^@zcGb?9-Y)>VkB7oY94#+gS{4~6$@`P8` zN1IZ9`V>nmRZSlEc0Ofo?r}r^2 z_wrK#n8bi0DhwEII?S&@!P@D7_3)Wf(9p#IdC!6v?4n+bXbd9u)H&*S0`4pY^4Hq3 z(+i>Jk8l?_(|;&cQ>bglnx%R6TGolV=nu@*`_s@BM|-6vD} zjODju)9sI^;nK-hsG3|1Alk>!EB^1Ks>JD`4M_95$use&s4?(NMxD(ifOs;jPI-XX z1w$)6XiS7jm#1c3JCX>!_MYn+w;q%PqSM-cM;b%0U=f>bL;E|z_8{HB^ul4@pVB-N3_?C}t$XS? zkB0oCRIsSNX{ARMkvi~Kvvm0%BzF-Z&p2`>2RWh_sKy`wZ6?e%sxf>GG%~^m0a{(- zdtX4%y&f{@77cT`^d!F!**@q)KL`+K(RT>(vanseqGRdE3a=KejD;70xqIvjJ>aCp z0`%wi%o{ey00l(ssW%whAj3?vY>iSGkkXTuIi$*<|4gW?PQ9CI3S-6s|3U!gdx!?* zWD&6(#j<~Pc(ic9CWKRoTyyK+t`62y^e#x}pA%E(5kUCfrNHu{w%!Mnx=IR?C!>i8 z5gdayg^V0zh4l!`*13C`BkSppyPMGBWQ;Rx<~PZI{pKC6v0wR&d*4&i=EVe9hk2lw zELaJJzElz2ROcc2oyjQw!Uc|j@ROdGj8=jqf88uypi>kzdx2Dq@4NqgOl-^#%FxD} z&-63|T6(VOp#8fD>CAd`7`pOr6`YYgEEu3$J@2XHQoQeuKe+4 z;9uFdvn2sjVOSqQN)Yu#29Z?%{|2dD-TwwstV;q4c{Cj3pQ(e6g4W&j`hdY;>I?y} zz&a8`DHm+XW>E@iD|0zchuM{xod^?UTp(a#p(SSl(~0z)&084FEM1fqW)o2yLQP~* zO%cS*1lxWhoiP6na1Enu^LQ@9^afbpzN`Y2?@T=C7GcC%bA%c7IiI{oSV2UimG*MjmZY08gFv z!%tJ-Phb_`r@PbhhL+YkxHou6@{3*+b7;7b+})B29~~Uf3ur_NdY5$&n&=JI&O!Tx zgq1a-Kb}qlVquODhfJP2YtT;V!M<0d_o*XQ++cu2n7Tsrke)(RT!KILaFT!{^s?UO zVBIV>^4hwAW$Am2jsE!?hv|)0FGP&{PDJSBUB5AcIMLzTuAGIe8rnKzRuEtKywMY= zvPS<+>4_=uYx0cjh2f}7?X~6VuqmoRqng|~0r58$cqeP8>E$*rL^4XDm2<4Sq%`!` z&(7!^ABG)qmWO}ZnYChRt%IYPbMDjj6N>E}Hj%I9HjKsQE7!#p3#=Qsayk=hSSsVi z8Qwd1SAOW;49gTMj|>*ORr^><+a_eP@T^|l5&m8Rl3K#A9qXd3%LRNdxJsq> zdCn>=1?uMC^e-&fhL39myy5_bjr4Jt8Jdglxz^ImcV(CbBE~7rzl7c3rH_6nQwm!i zXc(Zx9ASC*W#|foR>L*^850RI>k8y-AJ7+?!G?ebW97se99s4RRl&0Vpc0=iOH3OG zH4O#SpV$6{qc_6Y+W2g_bix%))hHcYsYnol(ahVJ^cMjPCK0ZGLZbKyl1j~M|1m1P z!^$C%uHJ%YQOY>oL97-$9y5jx2K40`2HHR2o`-=RAI1pSy5`PclPwhxsJy z`aNq3+z*Hos#mADHS_Y$-e?DR-Nv z-Rpp(Yv|I!RWBQIJDw-68{~M|5Ve|#%-jbuev&<&4+(oH&id)9b)9FQhdUj-vZg2J@)Og*09 zT_xN5Hk!vph*gZJc@>(}imtmD7-9#*lIg@ycIAPxO9Gbf;&lnPBm65yxHFS=_d!#4 z(5!u(!m-1+sq&IjGp_7KHnJtn*sJiP&3E|3BBf1uz?D#68ziVyC@C3Uvi$`hWAB&Pvmyu zjb&(oGV9JmD!+>GNupoi4<3RD^d$$4bMQ7sM!jh1%ql87gq?4KoiFx{alY6$5vf)5 z^A89x&c`1-+N4zf9NvZvz=M*;&+cam(sm-x_BDE&7EZAVSP4j70^_^b-xgj`Y-H(6 zstRSh%qni`@eF)B^BoJ_2?$-CE~#+C@=s}CJ$PLyZc4rR#8FlfB2N_6y&S0WC4^?c zwymKSe8M7U(l{+yqO|+B16B7FY?CN!KfOPxa*9wiTxMh5c%Upu;B`t>sN&oH&?p|i z@K=km0ER-vULMVmGOrsyAU6}C?Bp?y^yFtFW6Abi$&K;j8}C_Zt)t+|f+COuhe-Bn zUni9M3vV?7n-pFtvG5I`=O{e` z)gn@=jPQxfginhRK8_d&ehk8}xY8IZwVFSB+#L-j4cOY2iDli+!@E6L_w`8oWV)0- zX+eLzwzuLg+;T|ZWiau)mC?gd-Cc3$%P6JpW-7;TQ++U8JJnO9UanypGB1`Eii z#6$ca@K8+4-ltApoMNFAH8v`G`jvR5IljGYfe==vkGb&3(Y30p-o0wi=6YPUu^)IL zRwp8bV|bxFCxk)crY@vG(_a;)Qbg7#ma!kP=z$5UV-|t+MsDs6zae?hqWCRF zWexCkcuteTsUK0{jY@dK=+sv$a~H>5wKHFg1=E|Pu2J7_XHnKqnonjP$9B&Om68b& zzTV00v=|+^u92p79)SgUv;A;dX)w>8HIn!P7YNmd(@-soE7f0&uI(E>GtqH*;sU`@ zb6XItY$@-(M9SK8X(!9g2t}#lIk5vMl&q>&!nHw4FH%Ut_NK`duu$rG6W49I*1t3@ z;@gZj>#Q)+lEmjgJwAr2Dw^gf=Q9>YE9(_YKa+s`u%u7orx zSlCXlq{)nu zL!>D$m~S;KdWx-=M8!=_MFV4v?ZfB05~}0U-Oh~$UA94-BG#Bjy+6LH zqX{(zIHjX0a$7FFsOqmk-$6B@nqfmvAr0r<9Gq7;W&ARH+vBzwI>ciQ0YHQ&R1aZi zk7H+_X~NE4+{`$8adV{k2m0BUESP5>w`IWNfG44PO9tv`L{=PWPw?Wv=x&X{Tp{As z6>Uv8kLU9ZR`-+My4bEw`i}ULNqOw)c(GQ~WcE=&Uzw3cyU4~UE64zojMiJz@h@6$ z8>qq(x?NYSo&jjhhNu3*;X&J8+v&eKW)DT&1b`rwyNc#}C|Tq6U8s z5-@S(Cmgq#-iPn|q@ZZi|0d3$i}jGzBoApH;#JgezIKSLCGW=S=pLc^Jvf2tD8o`e znI69&JMb~A2&=*-RPL(aAFoq8{0L51WzP(deRJftBCMS}z{q5b&WD{$KI{~cQtHRr z)qyKBeE9IMcH-oGKo>oVuD5*D>}~28u5|tj=@n>#x^a%D&8g;JS~}kqnOFVfG%1<# z3X)Lz93bMlcs(K4(#Zj4m63YARlfk>r-JdzHHZSA0>*`i_q`LE6&J9LILp@Qc4sOG zXV^zKT*yT47M%(_DQ>gtn)XEj@k_fBOARctno;pwLr-ZNqsmmpZTf}}=B}`W=aw)& z_&o{2v#jmk6Dr{}=lD?y{L!$&Ki``jBEV9&+qKAV`-bq5|FX%NZ!d8>{A1}+>RDA z*Mi92DPbAw=!W-Mh~!kEz;C11>0JUH{yY;7cXHNY@MC)|;zy1|iU5*Z6P#rBsHWN= z!!7{`f=dYKp+qJDBob;TC6JZ;8!T zeV-$H%LHN_VLrKE1LT}uWtq^zj7_~GIHxc&v3)+@lgUy{ko1H7SnZ+eUEZH?(Hx`L z_I|xp{rz^+2A2z4kRZB{6^{p1g-cJAL5j>1=M*pNG@7y3{5$seS{8}$4f+Ggn$aIf z{d!gKKI#v?Squ_?98p&>HRwRC&vu*A&7|nwAJ6w z_Dsh=k2n}E@(jL1Jdt7{vmb08k>s6WLk=67adwdj2bm7U?YRCax)`{-o7iq3XnyS1 zk@pZtH)nbvBg!)Gxy-_#9bkZ2%0`$OB;?6d|8|Pf?s48a2*;k4%!) z;SDQdA04Td&lV2G1xgbtn0iA#8+zs3BU(&}?Fb5A3J5_yfiti7B~Q0Ntbk$R{Vy|8 z!r8e_QnKC@qUq`5)Ux!wi|1BczX?D5t8i9F=<;}AfXS5u2~u2S{&eUR^s}S8S5X>+ zX?2x7eZg2emd~~*FFd6sxlAHUfJ%*Sqoz$4G`xqPnB+p^P}1S$d%{9YgltH3 zi7=xcO+V{%KL5R78vNNsd2K?vPg!R9m`G)p5!N6H6%mY(0RQv1rV`s*I$bqGFn_jl z*-%98eY+ktuEt~3h27viIc~w<`UH+hv2U!zU^X~c!rF>W3(rSs-vD? zK&X|^#>532lOo^1znO?ncXM0G-wM+3nAtU(O3*egagIuWxX{?*3R6~-yk|v2h1rh$ z1u*hq?t{pKC?_QIQXb2u2OGyFFHAOCU$vg1XSxnJ)Y*CR&0JuCisd2$1FG`3n&Xlv z_4$+>yb=rq_=G(&l(j*57_^$EIMRx4kCzw71pyO5s?@v`K#nubMKZ$0xoL9?sZ15m z3t(6?j2`#3!!{zk=n9r9QvT1ZA{oX2=tEaSKA?u6k*uqb1b3PwBjd$QJpK_}pfew* zco*l<4E`s~8fv&W?U*ou zIgefifv?^v2v+be46^Y%qLH{_^Ie9D-&Jtx3Os2w? zdMmEa$%XTXO|J|s!LHnbP`D-8XLLehk9I}{1H`IZz7@`mvEois48;7gSVg8qAJH}a zFh!crkyYeOuNL}@KT~r=>6r-Lw^8srP*!1@49VKKt)7{Nz2^L_bSfn^Wz_0=j+;WY z?Dr~UFU#Y(M9CBW64sQfWN~b(7=H5mn+;FskgKaNfI+cxidh9{IslK1ey0>(A8(Rw zvGj8(^DEJLGm1aCPwA_%ABkK6Lq;8NQd6$3xtnj0kxkj89%{rG!m&=pG^l@u8Lir= zNeWLhD)0|@k;rAD6O=j>)8r%14Nr!mZy^3(GyF=zoJv`^>_N!_N*zZ3O*uo3nk&mf zeL&8yf>OCNIbK-?gYhV3ErWp_{DPT$MCjG>D*@yr7T*U3?reE0i3d16l1*>%igCkO zgkET93pQ)VNKi}DA_m6FmgI>~**15fD<)DRBHZAlwsxpF;P5p3()WeG7f=aw0S4s3x*l68 zF-SU1*mwLyQWJIXV+EK4HPd8zr*{hJlxsGsa}&U2L)c?^|69ajWb!nuu1I$jvWwV- zJQ??Jgt=~mvkc-*I2P&RjON5Y18I4K0mdK^ zu)@EM^|yaCP%FHst|=SQPa%7H;WA#~b6suFqWL%DtWE6^F+fNhHBHJA=>6{pGF1pV zIy57{CVmV`UW=v;zMwr8R0e%!#(L=PK5;$g6jTE*Y0cm;xi#U5)B zuz`APoW4OOt?0MvLdwARmmWkva+i|Q*B|oQ84(P+rA{`w_!_rDtGMiFqx#RLc5JI! z7BJ%5cic23vx|W($8np##g92J)P5Y3hf|z<5gG2x`b;*3`o&y=ZH4r_g_U;?s(&k1 zU%l_F(?TC<|8`5&8wD1T3yS{8;=KywJnmO?4+;5jD^ge`BE}a6S57Gtogy#4HYF<+ z9=4m#cr>2Q@8eX`9#lDXqpXttgQG?2oQY5TN0Wy;LrmjGM=b9a5N;H#FTO^sldyGu z;`<x@pmQFhT|>nl?w)^Qo9^II1b^#tSHt~-=EQH6X= z(fvwYTo)86!aN&Jc1>w-vW-*hYIEglWjtgUceLnxiUNA*k#@1y&N;eSZZW6LMoJgc zvhT8aPnPh-R*t8$>ZT_PA5m`m&>pUJK8|iavMsnxdC`L!N!plPU8(7&GMgU*6*0E6 zuFW0~wyjs#CP7=wNsjB;fMPdZmU!Xnu5gO}oe#+qF4Xv<_HcXM8)b>}2%$0nQ-K7UBQT9KEKAX{5HbvF#+M!3u) zkr!~IAU5W_4w27L_=VFiN>7L>{=4uRKm zsIOZJ-A|c$*g8wI)VJ)t;IVtg@*&I0tSq9gC`H)5AHou4Ry?J5tIX)fK;zvTrrlw9 z0|Q*#@P<~Ejdpme9|Ko-FwgTow5O^w9^1ziwYM;?K3~B!`=#Tqu7QMDiwCI{045Za z^~L_<4pJAW3w-Ldm-3ZL0#peK^HPW9@-}r@ql{4pamR>(H6PlwYW&A>0LQPvG#7{X zBG+J}hd60l=b}%RQ!FD7#=TsOAo!RG2mF2^X;hwUNYudsr&8)ao=Shny|Cy{S=)~R zYe-qZD;KdYo$D>-0%ef@2h)I%ojguvie32l)qN#}VR=RE8#=XZQekm0^sh7$$iVI(!W z6A$i~(hxx7FZ@oH{wh*w@RP4K^!5(uQb7W~rR}%45FO*1Ny3zwco7UTy|9dN`#7=s=xCYx{krI8&q>yMQEQqb3m5eN z7m51qK)J7dIQOb@URkb9Ss@5EVBkG z$Jc{dw1)UavBjBScFIn@6?02Wo+2%it?ySL_bcfm>0z55A_<1&KOmMiw$2!><(U2$ zSS(RC&M>^d9dM|}ra*ErG|2LUICtLm9^1SG-Yp0-(kTc-FyeOb`^DGl;8332+Q5IL zq9wn*qQD);|2u2t4TnXx7q=tMTzxH(!c^w0ggP1GLPsYQPu*eeL1=78Cs*;$k@aKP3 zf&aKseUrfJ>jLJpI?`f_F&{3@ zDec%p+&}H-17z)Cq{TyLqHcPc6AB2Lhu19mMF>M;F=8eE4T^mulGPO2BMeq)!fwGv z^BtXNS$W?~JG|u~°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%0DYr}46(yCw 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

g>Nt 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

S}cL>sH!rvt7`QO`RN}LZfxkvmOR>(;}kN+ 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

wSztP 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�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

V~ekUCqvZzqtL>wV#cKY`an4 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 _w*Az#VMT6fY5L;**ki7#le7f|;Bqd5 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

v3+wY=KFsI$;PwzgiFP#VV+J}xXl#2Bro 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 z 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�y$kmrUBq6X2;vbM9_2_ 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

-&WoEcSS(k~;*D^K>p>m@Hu})^N9mohICB3#~ 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

t~z%LI8oJgn2w1LDv~*P>DX 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

^E*NeSN1#?QO)Y-<6DY*17j~O! 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

RSR_M2K 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

XAL< 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 Date: Sat, 24 Feb 2024 12:40:21 +0800 Subject: [PATCH 366/424] feat: http client rate limit --- .../ClientSideRateLimitedHandler.cs | 50 +++++++++++ .../Lab.HttpClientLimit.csproj | 14 ++++ .../Lab.HttpClientLimit/Program.cs | 84 +++++++++++++++++++ .../RateLimitHttpMessageHandler.cs | 58 +++++++++++++ .../Lab.HttpClientRateLimit.sln | 16 ++++ 5 files changed, 222 insertions(+) create mode 100644 Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/ClientSideRateLimitedHandler.cs create mode 100644 Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/Lab.HttpClientLimit.csproj create mode 100644 Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/Program.cs create mode 100644 Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/RateLimitHttpMessageHandler.cs create mode 100644 Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientRateLimit.sln diff --git a/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/ClientSideRateLimitedHandler.cs b/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/ClientSideRateLimitedHandler.cs new file mode 100644 index 00000000..26e19488 --- /dev/null +++ b/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/ClientSideRateLimitedHandler.cs @@ -0,0 +1,50 @@ +using System.Globalization; +using System.Net; +using System.Threading.RateLimiting; + +namespace Lab.HttpClientLimit; + +internal sealed class ClientSideRateLimitedHandler(RateLimiter limiter) + : DelegatingHandler(new HttpClientHandler()), IAsyncDisposable +{ + protected override async Task SendAsync( + HttpRequestMessage request, CancellationToken cancellationToken) + { + using var lease = await limiter.AcquireAsync( + permitCount: 1, cancellationToken); + if (lease.IsAcquired) + { + return await base.SendAsync(request, cancellationToken); + } + + var response = new HttpResponseMessage(HttpStatusCode.TooManyRequests); + if (lease.TryGetMetadata( + MetadataName.RetryAfter, out TimeSpan retryAfter)) + { + response.Headers.Add( + "Retry-After", + ((int)retryAfter.TotalSeconds).ToString( + NumberFormatInfo.InvariantInfo)); + } + + return response; + } + + async ValueTask IAsyncDisposable.DisposeAsync() + { + await limiter.DisposeAsync().ConfigureAwait(false); + + Dispose(disposing: false); + GC.SuppressFinalize(this); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + limiter.Dispose(); + } + } +} \ No newline at end of file diff --git a/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/Lab.HttpClientLimit.csproj b/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/Lab.HttpClientLimit.csproj new file mode 100644 index 00000000..70997f62 --- /dev/null +++ b/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/Lab.HttpClientLimit.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/Program.cs b/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/Program.cs new file mode 100644 index 00000000..2d2349ba --- /dev/null +++ b/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/Program.cs @@ -0,0 +1,84 @@ +using System.Threading.RateLimiting; +using Lab.HttpClientLimit; + +var tokenBucketRateLimiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions +{ + TokenLimit = 1000, + QueueProcessingOrder = QueueProcessingOrder.OldestFirst, + QueueLimit = 1000, + ReplenishmentPeriod = TimeSpan.FromSeconds(1), + TokensPerPeriod = 17, + AutoReplenishment = true +}); +var slidingWindowRateLimiter = new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions +{ + Window = TimeSpan.FromSeconds(10), + SegmentsPerWindow = 100, + AutoReplenishment = true, + PermitLimit = 10, + QueueProcessingOrder = QueueProcessingOrder.OldestFirst, + QueueLimit = 1 +}); +var fixedWindowRateLimiter = new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions +{ + Window = TimeSpan.FromSeconds(10), + AutoReplenishment = true, + PermitLimit = 10, + QueueProcessingOrder = QueueProcessingOrder.OldestFirst, + QueueLimit = 1 +}); + +// Create an HTTP client with the client-side rate limited handler. +var limiter = fixedWindowRateLimiter; + +using HttpClient client = new( + handler: new ClientSideRateLimitedHandler(limiter: limiter)); + +Console.WriteLine($"{DateTime.Now.ToString()},Start"); + +var count = 0; +while (false) +{ + var lease = await limiter.AcquireAsync(permitCount: 1, cancellationToken: default); + if (lease.IsAcquired == false) + { + Console.WriteLine("Rate limit exceeded. Pausing requests for 1 minute."); + await Task.Delay(TimeSpan.FromMinutes(1)); + continue; + } + + var tasks = new List() + { + Task.Run(async () => { await Task.Delay(TimeSpan.FromMilliseconds(100)); }), + Task.Run(async () => { await Task.Delay(TimeSpan.FromMilliseconds(120)); }), + }; + await Task.WhenAll(tasks); + count++; + Console.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss}, Run Count: {count}"); +} + +var oneHundredUrls = Enumerable.Range(0, 100).Select( + i => $"https://example.com?iteration={i:0#}"); + +// Flood the HTTP client with requests. +var floodOneThroughFortyNineTask = Parallel.ForEachAsync( + source: oneHundredUrls.Take(0..49), + body: (url, cancellationToken) => GetAsync(client, url, cancellationToken)); + +var floodFiftyThroughOneHundredTask = Parallel.ForEachAsync( + source: oneHundredUrls.Take(^50..), + body: (url, cancellationToken) => GetAsync(client, url, cancellationToken)); + +await Task.WhenAll( + floodOneThroughFortyNineTask, + floodFiftyThroughOneHundredTask); + +static async ValueTask GetAsync( + HttpClient client, string url, CancellationToken cancellationToken) +{ + using var response = + await client.GetAsync(url, cancellationToken); + + Console.WriteLine( + $"URL: {url}, HTTP status code: {response.StatusCode} ({(int)response.StatusCode})"); +} \ No newline at end of file diff --git a/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/RateLimitHttpMessageHandler.cs b/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/RateLimitHttpMessageHandler.cs new file mode 100644 index 00000000..c48080e7 --- /dev/null +++ b/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientLimit/RateLimitHttpMessageHandler.cs @@ -0,0 +1,58 @@ +namespace Lab.HttpClientLimit; + +public class RateLimitHttpMessageHandler : DelegatingHandler +{ + private readonly List _callLog = + new List(); + private readonly TimeSpan _limitTime; + private readonly int _limitCount; + + public RateLimitHttpMessageHandler(int limitCount, TimeSpan limitTime) + { + _limitCount = limitCount; + _limitTime = limitTime; + } + + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + var now = DateTimeOffset.UtcNow; + + lock (_callLog) + { + _callLog.Add(now); + + while (_callLog.Count > _limitCount) + _callLog.RemoveAt(0); + } + + await LimitDelay(now); + + return await base.SendAsync(request, cancellationToken); + } + + private async Task LimitDelay(DateTimeOffset now) + { + if (_callLog.Count < _limitCount) + return; + + var limit = now.Add(-_limitTime); + + var lastCall = DateTimeOffset.MinValue; + var shouldLock = false; + + lock (_callLog) + { + lastCall = _callLog.FirstOrDefault(); + shouldLock = _callLog.Count(x => x >= limit) >= _limitCount; + } + + var delayTime = shouldLock && (lastCall > DateTimeOffset.MinValue) + ? (limit - lastCall) + : TimeSpan.Zero; + + if (delayTime > TimeSpan.Zero) + await Task.Delay(delayTime); + } +} \ No newline at end of file diff --git a/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientRateLimit.sln b/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientRateLimit.sln new file mode 100644 index 00000000..e229aa88 --- /dev/null +++ b/Rate Limit/Lab.HttpClientRateLimit/Lab.HttpClientRateLimit.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.HttpClientLimit", "Lab.HttpClientLimit\Lab.HttpClientLimit.csproj", "{04DE6CBF-1595-4859-9F4D-B46AE2A7712B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {04DE6CBF-1595-4859-9F4D-B46AE2A7712B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04DE6CBF-1595-4859-9F4D-B46AE2A7712B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04DE6CBF-1595-4859-9F4D-B46AE2A7712B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04DE6CBF-1595-4859-9F4D-B46AE2A7712B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From 230c796da18e14955197ab31be9cfd499dc43bfb Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 28 Feb 2024 10:36:01 +0800 Subject: [PATCH 367/424] feat: mock server example --- .../Mock Server/Lab.MockServer/.gitignore | 670 ++++++++++++++++++ .../Lab.MockServer.Test/GlobalUsings.cs | 1 + .../Lab.MockServer.Test.csproj | 29 + .../Lab.MockServer.Test/UnitTest1.cs | 195 +++++ .../Lab.MockServer.Test/UnitTest2.cs | 48 ++ .../Lab.MockServer/Lab.MockServer.sln | 21 + .../Mock Server/Lab.MockServer/Taslfile.yml | 0 .../Lab.MockServer/docker-compose.yml | 9 + 8 files changed, 973 insertions(+) create mode 100644 WebAPI/Swagger/Mock Server/Lab.MockServer/.gitignore create mode 100644 WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/GlobalUsings.cs create mode 100644 WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/Lab.MockServer.Test.csproj create mode 100644 WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/UnitTest1.cs create mode 100644 WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/UnitTest2.cs create mode 100644 WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.sln create mode 100644 WebAPI/Swagger/Mock Server/Lab.MockServer/Taslfile.yml create mode 100644 WebAPI/Swagger/Mock Server/Lab.MockServer/docker-compose.yml diff --git a/WebAPI/Swagger/Mock Server/Lab.MockServer/.gitignore b/WebAPI/Swagger/Mock Server/Lab.MockServer/.gitignore new file mode 100644 index 00000000..f2c4b13c --- /dev/null +++ b/WebAPI/Swagger/Mock Server/Lab.MockServer/.gitignore @@ -0,0 +1,670 @@ +### ASPNETCore template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/ + +### Csharp template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + diff --git a/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/GlobalUsings.cs b/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/GlobalUsings.cs new file mode 100644 index 00000000..8c927eb7 --- /dev/null +++ b/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/Lab.MockServer.Test.csproj b/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/Lab.MockServer.Test.csproj new file mode 100644 index 00000000..f21206f7 --- /dev/null +++ b/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/Lab.MockServer.Test.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + Linux + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/UnitTest1.cs b/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/UnitTest1.cs new file mode 100644 index 00000000..a6bff948 --- /dev/null +++ b/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/UnitTest1.cs @@ -0,0 +1,195 @@ +using System.Net; +using System.Text; +using System.Text.Json.JsonDiffPatch; +using FluentAssertions; + +namespace Lab.MockServer.Test; + +public class UnitTest1 +{ + static readonly HttpClient Client = new() + { + BaseAddress = new Uri("http://localhost:1080/"), + }; + + [Fact] + public void 動態建立假端點() + { + //建立假的端點 + var url = "mockserver/expectation"; + var body = """ + { + "httpRequest": { + "method": "GET", + "path": "/view/cart" + }, + "httpResponse": { + "body": "some_response_body" + } + } + """; + var request = new HttpRequestMessage(HttpMethod.Put, url); + request.Content = new StringContent(body, Encoding.UTF8, "application/json"); + var response = Client.SendAsync(request).Result; + response.StatusCode.Should().Be(HttpStatusCode.Created); + + //呼叫假的端點 + var getCartResult = Client.GetStringAsync("view/cart?cartId=055CA455-1DF7-45BB-8535-4F83E7266092").Result; + getCartResult.Should().Be("some_response_body"); + } + + [Fact] + public void 匯入OpenApi() + { + //建立假的端點 + var url = "mockserver/openapi"; + + var yaml = @" +openapi: '3.0.0' +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + maximum: 100 + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Pets' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + post: + summary: Create a pet + operationId: createPets + tags: + - pets + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + maxItems: 100 + items: + $ref: '#/components/schemas/Pet' + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string"; + + var httpFile = "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml"; + var jsonPayload = new + { + specUrlOrPayload = httpFile + }; + + var body = System.Text.Json.JsonSerializer.Serialize(jsonPayload); + var request = new HttpRequestMessage(HttpMethod.Put, url); + request.Content = new StringContent(body, Encoding.UTF8, "application/json"); + var response = Client.SendAsync(request).Result; + response.StatusCode.Should().Be(HttpStatusCode.Created); + + //呼叫假的端點 + var getCartResult = Client.GetStringAsync("/v1/pets").Result; + + var expected = """ + [ + { + "id": 0, + "name": "some_string_value", + "tag": "some_string_value" + } + ] + """; + var diff = JsonDiffPatcher.Diff(expected, getCartResult); + Assert.Null(diff); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/UnitTest2.cs b/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/UnitTest2.cs new file mode 100644 index 00000000..d992816a --- /dev/null +++ b/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.Test/UnitTest2.cs @@ -0,0 +1,48 @@ +using System.Net; +using System.Text; +using System.Text.Json.JsonDiffPatch; +using DotNet.Testcontainers.Builders; +using FluentAssertions; + +namespace Lab.MockServer.Test; + +public class UnitTest2 +{ + [Fact] + public async Task 動態建立假端點_TestContainers() + { + //建立假的端點 + var url = "mockserver/expectation"; + var body = """ + { + "httpRequest": { + "method": "GET", + "path": "/view/cart" + }, + "httpResponse": { + "body": "some_response_body" + } + } + """; + + var container = new ContainerBuilder() + .WithImage("mockserver/mockserver") + .WithPortBinding(1080, assignRandomHostPort: true) + .Build(); + await container.StartAsync(); + var hostname = container.Hostname; + var port = container.GetMappedPublicPort(1080); + var httpClient = new HttpClient + { + BaseAddress = new Uri($"http://{hostname}:{port}/") + }; + var request = new HttpRequestMessage(HttpMethod.Put, url); + request.Content = new StringContent(body, Encoding.UTF8, "application/json"); + var response = httpClient.SendAsync(request).Result; + response.StatusCode.Should().Be(HttpStatusCode.Created); + + //呼叫假的端點 + var getCartResult = httpClient.GetStringAsync("view/cart?cartId=055CA455-1DF7-45BB-8535-4F83E7266092").Result; + getCartResult.Should().Be("some_response_body"); + } +} \ No newline at end of file diff --git a/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.sln b/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.sln new file mode 100644 index 00000000..3d802178 --- /dev/null +++ b/WebAPI/Swagger/Mock Server/Lab.MockServer/Lab.MockServer.sln @@ -0,0 +1,21 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.MockServer.Test", "Lab.MockServer.Test\Lab.MockServer.Test.csproj", "{F9475396-EE48-49F7-B36F-32A6053F31A4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EB3E2200-3D1A-4778-A98A-254F59AF0238}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F9475396-EE48-49F7-B36F-32A6053F31A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F9475396-EE48-49F7-B36F-32A6053F31A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F9475396-EE48-49F7-B36F-32A6053F31A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F9475396-EE48-49F7-B36F-32A6053F31A4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/WebAPI/Swagger/Mock Server/Lab.MockServer/Taslfile.yml b/WebAPI/Swagger/Mock Server/Lab.MockServer/Taslfile.yml new file mode 100644 index 00000000..e69de29b diff --git a/WebAPI/Swagger/Mock Server/Lab.MockServer/docker-compose.yml b/WebAPI/Swagger/Mock Server/Lab.MockServer/docker-compose.yml new file mode 100644 index 00000000..16e62d7c --- /dev/null +++ b/WebAPI/Swagger/Mock Server/Lab.MockServer/docker-compose.yml @@ -0,0 +1,9 @@ +services: + mockServer: + image: mockserver/mockserver:latest + container_name: mockServer + ports: + - 1080:1080 +# environment: +# MOCKSERVER_MAX_EXPECTATIONS: 100 +# MOCKSERVER_MAX_HEADER_SIZE: 8192 \ No newline at end of file From 0d109fca68f6a776816a5be181476e5267eaedcb Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 2 Mar 2024 23:42:03 +0800 Subject: [PATCH 368/424] add ratelimit+redis --- .../Lab.ClientRateLimitAndRedis.sln | 21 +++++ .../ClientSideRateLimitedHandler.cs | 50 +++++++++++ .../Lab.ClientRateLimitAndRedis.csproj | 16 ++++ .../Lab.ClientRateLimitAndRedis/Program.cs | 85 +++++++++++++++++++ .../RateLimitHttpMessageHandler.cs | 58 +++++++++++++ .../docker-compose.yml | 15 ++++ 6 files changed, 245 insertions(+) create mode 100644 Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis.sln create mode 100644 Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/ClientSideRateLimitedHandler.cs create mode 100644 Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis.csproj create mode 100644 Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/Program.cs create mode 100644 Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/RateLimitHttpMessageHandler.cs create mode 100644 Rate Limit/Lab.ClientRateLimitAndRedis/docker-compose.yml diff --git a/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis.sln b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis.sln new file mode 100644 index 00000000..ae1865f2 --- /dev/null +++ b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis.sln @@ -0,0 +1,21 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ClientRateLimitAndRedis", "Lab.ClientRateLimitAndRedis\Lab.ClientRateLimitAndRedis.csproj", "{04DE6CBF-1595-4859-9F4D-B46AE2A7712B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A0A316CD-39E6-4456-96D7-4B6EB1D491D5}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {04DE6CBF-1595-4859-9F4D-B46AE2A7712B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04DE6CBF-1595-4859-9F4D-B46AE2A7712B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04DE6CBF-1595-4859-9F4D-B46AE2A7712B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04DE6CBF-1595-4859-9F4D-B46AE2A7712B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/ClientSideRateLimitedHandler.cs b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/ClientSideRateLimitedHandler.cs new file mode 100644 index 00000000..4b6ab362 --- /dev/null +++ b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/ClientSideRateLimitedHandler.cs @@ -0,0 +1,50 @@ +using System.Globalization; +using System.Net; +using System.Threading.RateLimiting; + +namespace Lab.ClientRateLimitAndRedis; + +internal sealed class ClientSideRateLimitedHandler(RateLimiter limiter) + : DelegatingHandler(new HttpClientHandler()), IAsyncDisposable +{ + protected override async Task SendAsync( + HttpRequestMessage request, CancellationToken cancellationToken) + { + using var lease = await limiter.AcquireAsync( + permitCount: 1, cancellationToken); + if (lease.IsAcquired) + { + return await base.SendAsync(request, cancellationToken); + } + + var response = new HttpResponseMessage(HttpStatusCode.TooManyRequests); + if (lease.TryGetMetadata( + MetadataName.RetryAfter, out TimeSpan retryAfter)) + { + response.Headers.Add( + "Retry-After", + ((int)retryAfter.TotalSeconds).ToString( + NumberFormatInfo.InvariantInfo)); + } + + return response; + } + + async ValueTask IAsyncDisposable.DisposeAsync() + { + await limiter.DisposeAsync().ConfigureAwait(false); + + this.Dispose(disposing: false); + GC.SuppressFinalize(this); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + limiter.Dispose(); + } + } +} \ No newline at end of file diff --git a/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis.csproj b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis.csproj new file mode 100644 index 00000000..c4c12ea9 --- /dev/null +++ b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + Linux + + + + + + + + diff --git a/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/Program.cs b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/Program.cs new file mode 100644 index 00000000..bae82fbe --- /dev/null +++ b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/Program.cs @@ -0,0 +1,85 @@ +using Lab.ClientRateLimitAndRedis; +using RedisRateLimiting; +using StackExchange.Redis; + +var redisTokenBucketRateLimiter = new RedisTokenBucketRateLimiter("demo-redis-token-bucket", + new RedisTokenBucketRateLimiterOptions + { + ConnectionMultiplexerFactory = () => ConnectionMultiplexer.Connect("localhost"), + TokenLimit = 50, + TokensPerPeriod = 50, + ReplenishmentPeriod = TimeSpan.FromSeconds(10) + } +); + +var redisSlidingWindowRateLimiter = new RedisSlidingWindowRateLimiter("demo-redis-sliding-window", + new RedisSlidingWindowRateLimiterOptions + { + ConnectionMultiplexerFactory = () => ConnectionMultiplexer.Connect("localhost"), + Window = TimeSpan.FromSeconds(10), + PermitLimit = 50 + } +); +var redisFixedWindowRateLimiter = new RedisFixedWindowRateLimiter("demo-redis-fixed-window", + new RedisFixedWindowRateLimiterOptions + { + ConnectionMultiplexerFactory = () => ConnectionMultiplexer.Connect("localhost"), + Window = TimeSpan.FromSeconds(10), + PermitLimit = 50 + } +); + +// Create an HTTP client with the client-side rate limited handler. +var limiter = redisSlidingWindowRateLimiter; + +using HttpClient client = new( + handler: new ClientSideRateLimitedHandler(limiter: limiter)); + +Console.WriteLine($"{DateTime.Now.ToString()},Start"); + +var count = 0; +while (true) +{ + var lease = await limiter.AcquireAsync(permitCount: 1, cancellationToken: default); + if (lease.IsAcquired == false) + { + Console.WriteLine("Rate limit exceeded. Pausing requests for 1 sec."); + await Task.Delay(TimeSpan.FromSeconds(1)); + continue; + } + + var tasks = new List() + { + Task.Run(async () => { await Task.Delay(TimeSpan.FromMilliseconds(100)); }), + Task.Run(async () => { await Task.Delay(TimeSpan.FromMilliseconds(120)); }), + }; + await Task.WhenAll(tasks); + count++; + Console.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss}, Run Count: {count}"); +} + +var oneHundredUrls = Enumerable.Range(0, 100).Select( + i => $"https://example.com?iteration={i:0#}"); + +// Flood the HTTP client with requests. +var floodOneThroughFortyNineTask = Parallel.ForEachAsync( + source: oneHundredUrls.Take(0..49), + body: (url, cancellationToken) => GetAsync(client, url, cancellationToken)); + +var floodFiftyThroughOneHundredTask = Parallel.ForEachAsync( + source: oneHundredUrls.Take(^50..), + body: (url, cancellationToken) => GetAsync(client, url, cancellationToken)); + +await Task.WhenAll( + floodOneThroughFortyNineTask, + floodFiftyThroughOneHundredTask); + +static async ValueTask GetAsync( + HttpClient client, string url, CancellationToken cancellationToken) +{ + using var response = + await client.GetAsync(url, cancellationToken); + + Console.WriteLine( + $"URL: {url}, HTTP status code: {response.StatusCode} ({(int)response.StatusCode})"); +} \ No newline at end of file diff --git a/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/RateLimitHttpMessageHandler.cs b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/RateLimitHttpMessageHandler.cs new file mode 100644 index 00000000..29193862 --- /dev/null +++ b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis/RateLimitHttpMessageHandler.cs @@ -0,0 +1,58 @@ +namespace Lab.ClientRateLimitAndRedis; + +public class RateLimitHttpMessageHandler : DelegatingHandler +{ + private readonly List _callLog = + new List(); + private readonly TimeSpan _limitTime; + private readonly int _limitCount; + + public RateLimitHttpMessageHandler(int limitCount, TimeSpan limitTime) + { + this._limitCount = limitCount; + this._limitTime = limitTime; + } + + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + var now = DateTimeOffset.UtcNow; + + lock (this._callLog) + { + this._callLog.Add(now); + + while (this._callLog.Count > this._limitCount) + this._callLog.RemoveAt(0); + } + + await this.LimitDelay(now); + + return await base.SendAsync(request, cancellationToken); + } + + private async Task LimitDelay(DateTimeOffset now) + { + if (this._callLog.Count < this._limitCount) + return; + + var limit = now.Add(-this._limitTime); + + var lastCall = DateTimeOffset.MinValue; + var shouldLock = false; + + lock (this._callLog) + { + lastCall = this._callLog.FirstOrDefault(); + shouldLock = this._callLog.Count(x => x >= limit) >= this._limitCount; + } + + var delayTime = shouldLock && (lastCall > DateTimeOffset.MinValue) + ? (limit - lastCall) + : TimeSpan.Zero; + + if (delayTime > TimeSpan.Zero) + await Task.Delay(delayTime); + } +} \ No newline at end of file diff --git a/Rate Limit/Lab.ClientRateLimitAndRedis/docker-compose.yml b/Rate Limit/Lab.ClientRateLimitAndRedis/docker-compose.yml new file mode 100644 index 00000000..ffdbef3b --- /dev/null +++ b/Rate Limit/Lab.ClientRateLimitAndRedis/docker-compose.yml @@ -0,0 +1,15 @@ +services: + redis: + image: redis + ports: + - 6379:6379 + +# # 在登入頁面 +# # host:redis +# # port:6379 +# redis-admin: +# image: marian/rebrow +# ports: +# - 5001:5001 +# depends_on: +# - redis \ No newline at end of file From ddd1a23cce8636d541287fb3feb25eeaecd6a6ed Mon Sep 17 00:00:00 2001 From: yao Date: Sun, 3 Mar 2024 11:55:04 +0800 Subject: [PATCH 369/424] add app2 --- .../Lab.ClientRateLimitAndRedis.sln | 6 ++ .../ClientSideRateLimitedHandler.cs | 50 +++++++++++ .../Lab.ClientRateLimitAndRedis2.csproj | 16 ++++ .../Lab.ClientRateLimitAndRedis2/Program.cs | 85 +++++++++++++++++++ .../RateLimitHttpMessageHandler.cs | 58 +++++++++++++ 5 files changed, 215 insertions(+) create mode 100644 Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/ClientSideRateLimitedHandler.cs create mode 100644 Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/Lab.ClientRateLimitAndRedis2.csproj create mode 100644 Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/Program.cs create mode 100644 Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/RateLimitHttpMessageHandler.cs diff --git a/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis.sln b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis.sln index ae1865f2..fcb3aa2f 100644 --- a/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis.sln +++ b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis.sln @@ -7,6 +7,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution docker-compose.yml = docker-compose.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab.ClientRateLimitAndRedis2", "Lab.ClientRateLimitAndRedis2\Lab.ClientRateLimitAndRedis2.csproj", "{C9E118ED-55B0-43CD-A87C-6CD138ADE088}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -17,5 +19,9 @@ Global {04DE6CBF-1595-4859-9F4D-B46AE2A7712B}.Debug|Any CPU.Build.0 = Debug|Any CPU {04DE6CBF-1595-4859-9F4D-B46AE2A7712B}.Release|Any CPU.ActiveCfg = Release|Any CPU {04DE6CBF-1595-4859-9F4D-B46AE2A7712B}.Release|Any CPU.Build.0 = Release|Any CPU + {C9E118ED-55B0-43CD-A87C-6CD138ADE088}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9E118ED-55B0-43CD-A87C-6CD138ADE088}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9E118ED-55B0-43CD-A87C-6CD138ADE088}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9E118ED-55B0-43CD-A87C-6CD138ADE088}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/ClientSideRateLimitedHandler.cs b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/ClientSideRateLimitedHandler.cs new file mode 100644 index 00000000..e7204064 --- /dev/null +++ b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/ClientSideRateLimitedHandler.cs @@ -0,0 +1,50 @@ +using System.Globalization; +using System.Net; +using System.Threading.RateLimiting; + +namespace Lab.ClientRateLimitAndRedis2; + +internal sealed class ClientSideRateLimitedHandler(RateLimiter limiter) + : DelegatingHandler(new HttpClientHandler()), IAsyncDisposable +{ + protected override async Task SendAsync( + HttpRequestMessage request, CancellationToken cancellationToken) + { + using var lease = await limiter.AcquireAsync( + permitCount: 1, cancellationToken); + if (lease.IsAcquired) + { + return await base.SendAsync(request, cancellationToken); + } + + var response = new HttpResponseMessage(HttpStatusCode.TooManyRequests); + if (lease.TryGetMetadata( + MetadataName.RetryAfter, out TimeSpan retryAfter)) + { + response.Headers.Add( + "Retry-After", + ((int)retryAfter.TotalSeconds).ToString( + NumberFormatInfo.InvariantInfo)); + } + + return response; + } + + async ValueTask IAsyncDisposable.DisposeAsync() + { + await limiter.DisposeAsync().ConfigureAwait(false); + + this.Dispose(disposing: false); + GC.SuppressFinalize(this); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + limiter.Dispose(); + } + } +} \ No newline at end of file diff --git a/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/Lab.ClientRateLimitAndRedis2.csproj b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/Lab.ClientRateLimitAndRedis2.csproj new file mode 100644 index 00000000..c4c12ea9 --- /dev/null +++ b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/Lab.ClientRateLimitAndRedis2.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + Linux + + + + + + + + diff --git a/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/Program.cs b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/Program.cs new file mode 100644 index 00000000..92e130be --- /dev/null +++ b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/Program.cs @@ -0,0 +1,85 @@ +using Lab.ClientRateLimitAndRedis2; +using RedisRateLimiting; +using StackExchange.Redis; + +var redisTokenBucketRateLimiter = new RedisTokenBucketRateLimiter("demo-redis-token-bucket", + new RedisTokenBucketRateLimiterOptions + { + ConnectionMultiplexerFactory = () => ConnectionMultiplexer.Connect("localhost"), + TokenLimit = 50, + TokensPerPeriod = 50, + ReplenishmentPeriod = TimeSpan.FromSeconds(10) + } +); + +var redisSlidingWindowRateLimiter = new RedisSlidingWindowRateLimiter("demo-redis-sliding-window", + new RedisSlidingWindowRateLimiterOptions + { + ConnectionMultiplexerFactory = () => ConnectionMultiplexer.Connect("localhost"), + Window = TimeSpan.FromSeconds(10), + PermitLimit = 50 + } +); +var redisFixedWindowRateLimiter = new RedisFixedWindowRateLimiter("demo-redis-fixed-window", + new RedisFixedWindowRateLimiterOptions + { + ConnectionMultiplexerFactory = () => ConnectionMultiplexer.Connect("localhost"), + Window = TimeSpan.FromSeconds(10), + PermitLimit = 50 + } +); + +// Create an HTTP client with the client-side rate limited handler. +var limiter = redisSlidingWindowRateLimiter; + +using HttpClient client = new( + handler: new ClientSideRateLimitedHandler(limiter: limiter)); + +Console.WriteLine($"{DateTime.Now.ToString()},Start"); + +var count = 0; +while (true) +{ + var lease = await limiter.AcquireAsync(permitCount: 1, cancellationToken: default); + if (lease.IsAcquired == false) + { + Console.WriteLine("Rate limit exceeded. Pausing requests for 1 sec."); + await Task.Delay(TimeSpan.FromSeconds(1)); + continue; + } + + var tasks = new List() + { + Task.Run(async () => { await Task.Delay(TimeSpan.FromMilliseconds(100)); }), + Task.Run(async () => { await Task.Delay(TimeSpan.FromMilliseconds(120)); }), + }; + await Task.WhenAll(tasks); + count++; + Console.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss}, Run Count: {count}"); +} + +var oneHundredUrls = Enumerable.Range(0, 100).Select( + i => $"https://example.com?iteration={i:0#}"); + +// Flood the HTTP client with requests. +var floodOneThroughFortyNineTask = Parallel.ForEachAsync( + source: oneHundredUrls.Take(0..49), + body: (url, cancellationToken) => GetAsync(client, url, cancellationToken)); + +var floodFiftyThroughOneHundredTask = Parallel.ForEachAsync( + source: oneHundredUrls.Take(^50..), + body: (url, cancellationToken) => GetAsync(client, url, cancellationToken)); + +await Task.WhenAll( + floodOneThroughFortyNineTask, + floodFiftyThroughOneHundredTask); + +static async ValueTask GetAsync( + HttpClient client, string url, CancellationToken cancellationToken) +{ + using var response = + await client.GetAsync(url, cancellationToken); + + Console.WriteLine( + $"URL: {url}, HTTP status code: {response.StatusCode} ({(int)response.StatusCode})"); +} \ No newline at end of file diff --git a/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/RateLimitHttpMessageHandler.cs b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/RateLimitHttpMessageHandler.cs new file mode 100644 index 00000000..3871cb29 --- /dev/null +++ b/Rate Limit/Lab.ClientRateLimitAndRedis/Lab.ClientRateLimitAndRedis2/RateLimitHttpMessageHandler.cs @@ -0,0 +1,58 @@ +namespace Lab.ClientRateLimitAndRedis2; + +public class RateLimitHttpMessageHandler : DelegatingHandler +{ + private readonly List _callLog = + new List(); + private readonly TimeSpan _limitTime; + private readonly int _limitCount; + + public RateLimitHttpMessageHandler(int limitCount, TimeSpan limitTime) + { + this._limitCount = limitCount; + this._limitTime = limitTime; + } + + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + var now = DateTimeOffset.UtcNow; + + lock (this._callLog) + { + this._callLog.Add(now); + + while (this._callLog.Count > this._limitCount) + this._callLog.RemoveAt(0); + } + + await this.LimitDelay(now); + + return await base.SendAsync(request, cancellationToken); + } + + private async Task LimitDelay(DateTimeOffset now) + { + if (this._callLog.Count < this._limitCount) + return; + + var limit = now.Add(-this._limitTime); + + var lastCall = DateTimeOffset.MinValue; + var shouldLock = false; + + lock (this._callLog) + { + lastCall = this._callLog.FirstOrDefault(); + shouldLock = this._callLog.Count(x => x >= limit) >= this._limitCount; + } + + var delayTime = shouldLock && (lastCall > DateTimeOffset.MinValue) + ? (limit - lastCall) + : TimeSpan.Zero; + + if (delayTime > TimeSpan.Zero) + await Task.Delay(delayTime); + } +} \ No newline at end of file From 9d9228bc7327a1b9ca3a7355e039a1209ed9cf3b Mon Sep 17 00:00:00 2001 From: yao Date: Sat, 9 Mar 2024 16:33:42 +0800 Subject: [PATCH 370/424] refactor --- .../10\350\220\254.xlsx" | Bin 0 -> 3721315 bytes .../Lab.MiniExcelQuery.Test/Import.xlsx | Bin 14212 -> 14261 bytes .../Lab.MiniExcelQuery.Test.csproj | 5 +- .../Template/Export.xltx | Bin 13493 -> 0 bytes .../Template/ImportWithError.xlsx | Bin 13580 -> 0 bytes .../Template/ImportWithError.xltx | Bin 13549 -> 0 bytes .../Template/Member.xlsx | Bin 13584 -> 13647 bytes .../Lab.MiniExcelQuery.Test/UnitTest1.cs | 80 +++++++++--------- ...76\213_20230518093153_10\350\220\254.xlsx" | Bin 3958685 -> 0 bytes 9 files changed, 45 insertions(+), 40 deletions(-) create mode 100644 "Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/10\350\220\254.xlsx" delete mode 100644 Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/Export.xltx delete mode 100644 Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/ImportWithError.xlsx delete mode 100644 Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/Template/ImportWithError.xltx delete mode 100644 "Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/\344\277\256\346\224\271\347\255\226\347\225\245\347\257\204\344\276\213_20230518093153_10\350\220\254.xlsx" diff --git "a/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/10\350\220\254.xlsx" "b/Excel/Lab.MiniExcelQuery/Lab.MiniExcelQuery.Test/10\350\220\254.xlsx" new file mode 100644 index 0000000000000000000000000000000000000000..a8330222de53e2a8dc25cfd734ed1f4eb1532cf1 GIT binary patch literal 3721315 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

|0dA%@SVwa=f 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

{tuvw7y?+hZnUqL5b?5;n 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�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�P{fq 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%0D4EZ`5F3b{rtdKPTJ_0#Afdtc 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%0Dag}s~$Xwae74=h;j8` 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 z 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⪼X$M?bcRW`%I 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

Yd3#eACy+r)rY^(!!$tV`m6);J5{YE4l<%rqwoX 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%0A__nQd?#Up@8k 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

nDin6F-;BA2g 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�e2_U-?P);a2XbIx8int;Q_>{m|iI2SJL;YB+8 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